Changeset bc20307


Ignore:
Timestamp:
02/14/22 01:41:41 (2 years ago)
Author:
Tasevski2 <39170279+Tasevski2@…>
Branches:
master
Children:
747e0ab
Parents:
e8b1076
Message:

Push before video

Files:
90 added
5 deleted
57 edited
2 moved

Legend:

Unmodified
Added
Removed
  • sources/app/.idea/misc.xml

    re8b1076 rbc20307  
    88    </option>
    99  </component>
    10   <component name="ProjectRootManager" version="2" languageLevel="JDK_16" default="true" project-jdk-name="16" project-jdk-type="JavaSDK">
     10  <component name="ProjectRootManager" version="2" languageLevel="JDK_14" default="false" project-jdk-name="16" project-jdk-type="JavaSDK">
    1111    <output url="file://$PROJECT_DIR$/classes" />
    1212  </component>
  • sources/app/src/main/java/parkup/configs/CustomAuthenticationFilter.java

    re8b1076 rbc20307  
    8080            }
    8181            case "Guest":{
    82                 Guest user = (Guest) authentication.getAuthorities();
     82                Guest user = (Guest) authentication.getPrincipal();
    8383                email = user.getEmail();
    8484                fullName="GuestUser";
  • sources/app/src/main/java/parkup/configs/webConfigs/WebSecurityConfig.java

    re8b1076 rbc20307  
    4242            auth.userDetailsService(workerService).passwordEncoder(bCryptPasswordEncoder);
    4343            auth.userDetailsService(administratorService).passwordEncoder(bCryptPasswordEncoder);
    44             auth.userDetailsService(guestService);
     44            auth.userDetailsService(guestService).passwordEncoder(bCryptPasswordEncoder);
    4545        }
    4646
  • sources/app/src/main/java/parkup/controllers/GuestController.java

    re8b1076 rbc20307  
    3838
    3939    @PostMapping({"/guest"})
    40     public void addGuest(@RequestBody Guest guest) {
    41         this.guestService.addGuest(guest);
     40    public Guest addGuest(@RequestBody Guest guest) {
     41        Guest guestToReturn = this.guestService.addGuest(guest);
     42        return guestToReturn;
    4243    }
    4344
  • sources/app/src/main/java/parkup/entities/Guest.java

    re8b1076 rbc20307  
    2929    private int guestId;
    3030
    31     @OneToOne
     31    @OneToOne(cascade = {CascadeType.ALL})
    3232    @JoinColumn(name = "tablickaId", nullable = false)
    3333    private Plate plate;
     
    3939    private String mobile;
    4040
    41     @Column(name = "password")
     41    @Column(name="password")
    4242    private String password;
    4343
     
    4949
    5050
    51     public Guest() {this.role=UserRole.ROLE_USER;}
    52 
    53     public Guest(int guestId, Plate plate,String password, String email, String mobile) {
    54         this.guestId = guestId;
    55         this.plate = plate;
    56         this.email = email;
     51    public Guest() {
     52        this.role = UserRole.ROLE_USER;
     53        this.session = null;
     54    }
     55    public Guest(Plate plate, String email,String mobile,String password,ParkingSession session){
     56        this.plate=plate;
     57        this.email=email;
     58        this.mobile=mobile;
    5759        this.password=password;
    58         this.mobile = mobile;
     60        this.session=session;
    5961        this.role=UserRole.ROLE_USER;
    6062    }
     
    6466        this.email = email;
    6567        this.mobile = mobile;
     68        this.password = "";
    6669        this.role=UserRole.ROLE_USER;
    6770    }
     
    135138    @Override
    136139    public String getPassword() {
    137         return null;
     140        return this.password;
    138141    }
    139142
     
    145148    @Override
    146149    public boolean isAccountNonExpired() {
    147         return false;
     150        return true;
    148151    }
    149152
    150153    @Override
    151154    public boolean isAccountNonLocked() {
    152         return false;
     155        return true;
    153156    }
    154157
    155158    @Override
    156159    public boolean isCredentialsNonExpired() {
    157         return false;
     160        return true;
    158161    }
    159162
    160163    @Override
    161164    public boolean isEnabled() {
    162         return false;
     165        return true;
    163166    }
    164167}
  • sources/app/src/main/java/parkup/entities/RegisteredUser.java

    re8b1076 rbc20307  
    6363        this.plates = new ArrayList<Plate>();
    6464        this.role = UserRole.ROLE_REG_USER;
    65         session=null;
     65        this.session=null;
    6666    }
    6767
  • sources/app/src/main/java/parkup/services/GuestService.java

    re8b1076 rbc20307  
    3838        } else {
    3939            double random = Math.random()*10000;
     40            guest.setPassword(passwordEncoder.encode(Integer.toString((int)random)));
     41            this.guestRepository.save(guest);
    4042            guest.setPassword(Integer.toString((int)random));
    41            return  this.guestRepository.save(guest);
     43           return  guest;
    4244        }
    4345    }
  • sources/app/src/main/resources/application.properties

    re8b1076 rbc20307  
    11spring.datasource.url=jdbc:postgresql://localhost:5432/parkupdb
    22spring.datasource.username=postgres
    3 spring.datasource.password=1234
     3spring.datasource.password=Lampion123
    44spring.jpa.hibernate.ddl-auto=create-drop
    55spring.jpa.show-sql=true
  • sources/app/target/classes/application.properties

    re8b1076 rbc20307  
    11spring.datasource.url=jdbc:postgresql://localhost:5432/parkupdb
    22spring.datasource.username=postgres
    3 spring.datasource.password=1234
     3spring.datasource.password=Lampion123
    44spring.jpa.hibernate.ddl-auto=create-drop
    55spring.jpa.show-sql=true
  • sources/client/package.json

    re8b1076 rbc20307  
    11{
    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"
    4958}
  • sources/client/src/App.js

    re8b1076 rbc20307  
    1 import './App.css';
    2 import { Route, Switch, Redirect } from 'react-router-dom';
     1import AdminEmployeeHomeScreen from './screens/AdminHomeScreen';
     2import UserAndNotAuthScreen from './screens/UserAndNotAuthScreen';
     3import Alert from './components/Alert';
     4import BackgropLoader from './components/Loaders/BackdropLoader';
     5import { roles } from './config/enums';
     6import { UserContext } from './context/UserContext';
     7import { AccessoriesContext } from './context/AccessoriesContext';
     8import useIsMobile from './hooks/useIsMobile';
     9import { useState } from 'react';
    310
    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];
     11import useFindUser from './hooks/useFindUser';
     12import Sidedrawer from './components/Sidedrawer';
    2213
    2314function 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);
    2728
    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    }
    2935
    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    );
    5365}
    5466
  • sources/client/src/components/Auth/Login/index.js

    re8b1076 rbc20307  
    1 import { Link } from 'react-router-dom';
    2 import useForm from '../../hooks/useForm';
     1import { Link, useHistory } from 'react-router-dom';
     2import useForm from '../../../hooks/useForm';
     3import useLogin from '../../../hooks/useLogin';
    34
    45import {
     
    1516
    1617const Login = () => {
     18  const { loginUser } = useLogin();
    1719  const { data, onFormChange } = useForm({
    1820    email: '',
    1921    password: '',
    2022  });
     23  let history = useHistory();
    2124  const handleSignIn = () => {
    22     console.log(`Email: ${data.email}`);
    23     console.log(`Password: ${data.password}`);
     25    loginUser({ email: data.email, password: data.password });
    2426  };
    2527  return (
     
    2830        <Input
    2931          name='email'
     32          placeholder='Емаил'
    3033          value={data.email}
    3134          onChange={onFormChange}
     
    3740          name='password'
    3841          value={data.password}
     42          placeholder='Лозинка'
    3943          onChange={onFormChange}
    4044          InputProps={{
     
    4953          <DividerText>ИЛИ</DividerText>
    5054        </DividerButtons>
    51         <SignInButton>НАЈАВА КАКО ГОСТИН</SignInButton>{' '}
     55        <SignInButton onClick={() => history.push('/login-guest')}>
     56          НАЈАВА КАКО ГОСТИН
     57        </SignInButton>
    5258        {/* TODO Neka nosi do '/login-guest'*/}
    5359      </ButtonsWrapper>
     
    5561        {' '}
    5662        {/* TODO Neka nosi do '/register' */}
    57         Немате Сметка?
    58         <Link to='#'>Регистрирај се!</Link>
     63        Немате Профил?
     64        <Link to='/register'>Регистрирај се!</Link>
    5965      </RegisterText>
    6066    </>
  • sources/client/src/components/Auth/Login/styles.js

    re8b1076 rbc20307  
    1616  fullWidth: true,
    1717  sx: {
    18     marginTop: '35px',
     18    marginTop: '25px',
    1919  },
    2020})`
     
    2626    font-size: 1.2rem;
    2727    padding-left: 10px;
     28
     29    ::placeholder {
     30      opacity: 0.6;
     31    }
    2832  }
    2933
    3034  fieldset {
    3135    border: 0;
    32     border-bottom: 2px solid white;
     36    border-bottom: 4px solid white;
    3337    border-radius: 25px;
    3438    padding: 0 10px;
     
    5357  display: flex;
    5458  flex-direction: column;
    55   padding: 30px 25px;
     59  margin-top: 10px;
     60  padding: 30px 0px;
    5661`;
    5762export const SignInButton = styled(Button).attrs((props) => ({
     
    7782  sx: {},
    7883})`
    79   align-items: flex-start;
    80   margin-bottom: 15px;
     84  align-items: center;
     85  // margin-bottom: 15px;
    8186  ::before,
    8287  ::after {
     
    9297  position: absolute;
    9398  bottom: 0;
    94   align-self: cetner;
    9599  width: 100%;
    96100  text-align: center;
     
    100104    margin-left: 10px;
    101105    text-decoration: none;
    102     color: #f65026;
     106    color: ${(props) => props.theme.palette.third.main};
    103107    font-weight: bold;
    104108    font-size: 1.2rem;
  • sources/client/src/components/GoogleMaps/index.js

    re8b1076 rbc20307  
    11import GoogleMapReact from 'google-map-react';
     2import { useEffect, useRef } from 'react';
    23
    34import { Wrapper, Marker } from './styles';
    45
    5 const ParkingSpaceMarker = ({ text, free }) => (
    6   <Marker $free={free}>{text}</Marker>
     6const ParkingSpaceMarker = ({ text, isTaken }) => (
     7    <Marker $isTaken={isTaken}>{text}</Marker>
    78);
    89
    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   );
     10const 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    );
    4463};
    4564
  • sources/client/src/components/GoogleMaps/styles.js

    re8b1076 rbc20307  
    11import styled from 'styled-components';
     2import { mobile_max_width } from '../../config/utilities';
    23
    34export 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    }
    712`;
    813
     
    1217  height: 25px;
    1318  background-color: ${(props) =>
    14     props.$free
    15       ? `${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}`}};
    1722  display: inline-flex;
    1823  align-items: center;
    1924  justify-content: center;
    2025  border-radius: 100%;
     26  transform: translate(-50%, -100%);
    2127`;
  • sources/client/src/components/admin/EmployeeCreate/index.js

    re8b1076 rbc20307  
    44
    55import {
    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,
    2222} from './styles';
    2323
    2424import IconButton from '@mui/material/IconButton';
     25import Checkbox from '@mui/material/Checkbox';
     26import ListItemText from '@mui/material/ListItemText';
     27import AbsoluteLoader from '../../Loaders/AbsoluteLoader';
    2528
    2629import { employeeStatus } from '../../../config/enums';
     30import useGetData from '../../../hooks/useGetData';
     31import useCreateEmployee from '../../../hooks/useCreateEmployee';
    2732
    2833import { defaultUser } from '../../../config/defaultUser';
    2934
     35const MenuProps = {
     36    PaperProps: {
     37        style: {
     38            maxHeight: 220,
     39        },
     40    },
     41};
     42
    3043const 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 });
    6982    };
    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        );
    7697    };
    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    );
    199244};
    200245
  • 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";
     1import styled from 'styled-components';
     2import {
     3  Button,
     4  InputLabel,
     5  MenuItem,
     6  Select,
     7  Switch,
     8  TextField,
     9  Typography,
     10} from '@mui/material';
    311import VIcon from '@mui/icons-material/Visibility';
    412import VOffIcon from '@mui/icons-material/VisibilityOff';
    513
    614export 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  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};
    1725`;
    1826
    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;
     27export const Title = styled(Typography).attrs({})`
     28  border-bottom: 2px solid rgba(0, 0, 0, 0.12);
     29  padding-bottom: 10px;
     30  margin-bottom: 15px;
    2531`;
    2632
    2733export 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;
    3239`;
    3340
    34 export const LabelAndInputWrapper = styled.div`
     41export const LabelAndInputWrapper = styled.div``;
    3542
    36 `;
     43export const Label = styled(InputLabel).attrs({})``;
    3744
    38 export const Label = styled(InputLabel).attrs({
    39 
    40 })``;
    41 
    42 export const StandardInputField = styled(TextField).attrs({
    43 
    44 })`
    45     width: 300px;
     45export const StandardInputField = styled(TextField).attrs({})`
     46  width: 300px;
    4647`;
    4748
    4849export const VisibilityIcon = styled(VIcon).attrs({
    49     sx: {
    50         fontSize: '2rem'
    51     }
     50  sx: {
     51    fontSize: '2rem',
     52  },
    5253})``;
    5354
    5455export const VisibilityOffIcon = styled(VOffIcon).attrs({
    55     sx: {
    56         fontSize: '2rem'
    57     }
     56  sx: {
     57    fontSize: '2rem',
     58  },
    5859})``;
    5960
    60 export const Dropdown = styled(Select).attrs({
    61 
    62 })`
    63     width: 300px;
     61export const Dropdown = styled(Select).attrs({})`
     62  width: 300px;
    6463`;
    6564
    66 export const DropdownOption = styled(MenuItem).attrs({
    67 
    68 })``;
     65export 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`;
    6976
    7077export const SwitchRowWrapper = styled.div`
    71     text-align: center;
    72     margin-top: 10px;
    73     margin-bottom: 50px;
     78  text-align: center;
     79  margin-top: 10px;
     80  margin-bottom: 50px;
    7481`;
    7582
    7683export const SwitchTitle = styled.p`
    77     font-size: 18px;
    78     font-weight: 600;
    79     margin: 0;
    80     margin-bottom: 5px;
     84  font-size: 18px;
     85  font-weight: 600;
     86  margin: 0;
     87  margin-bottom: 5px;
    8188`;
    8289
    8390export 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;
    8995`;
    9096
    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     }
     97export 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  }
    105110`;
    106111
    107112export const BackButton = styled(Button).attrs({
    108     variant: 'outlined'
     113  variant: 'outlined',
    109114})`
    110     margin-right: 15px;
    111     :hover {
    112         border: 2px solid;
    113         font-weight: 600;
    114     }
     115  margin-right: 15px;
     116  :hover {
     117    border: 2px solid;
     118    font-weight: 600;
     119  }
    115120`;
    116121
    117 export const SaveChangesButton = styled(Button).attrs(props => ({
    118     variant: 'contained',
    119     sx: {
    120         backgroundColor: `${props.theme.palette.primary.main}`
    121     }
     122export const SaveChangesButton = styled(Button).attrs((props) => ({
     123  variant: 'contained',
     124  sx: {
     125    backgroundColor: `${props.theme.palette.primary.main}`,
     126  },
    122127}))`
    123     padding: 10px 16px;
    124     :hover {
    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  }
    127132`;
  • sources/client/src/components/admin/EmployeeEdit/index.js

    re8b1076 rbc20307  
    1 import { useState } from 'react';
     1import { useEffect, useState } from 'react';
    22import { useParams, useHistory } from 'react-router-dom';
    33import useForm from '../../../hooks/useForm';
     
    2121    SaveChangesButton,
    2222    VisibilityIcon,
    23     VisibilityOffIcon
     23    VisibilityOffIcon,
    2424} from './styles';
    2525
    2626import IconButton from '@mui/material/IconButton';
    27 
    28 import {
    29     employeeStatus
    30 } from '../../../config/enums';
    31 
    32 import {
    33     employees
    34 } from '../EmployeesTable/mockData';
     27import Checkbox from '@mui/material/Checkbox';
     28import ListItemText from '@mui/material/ListItemText';
     29import AbsoluteLoader from '../../Loaders/AbsoluteLoader';
     30
     31import { employeeStatus } from '../../../config/enums';
     32import useGetData from '../../../hooks/useGetData';
     33import useUpdateEmployee from '../../../hooks/useUpdateEmployee';
     34import useDeleteEmployee from '../../../hooks/useDeleteEmployee';
     35
     36const MenuProps = {
     37    PaperProps: {
     38        style: {
     39            maxHeight: 220,
     40        },
     41    },
     42};
    3543
    3644const EmployeeEdit = () => {
     
    3846    let history = useHistory();
    3947    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(
    4751        {
    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}`,
    7053        }
    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) => {
    7472        return {
    7573            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();
    7781        }
    78     });
    79 
    80     const onSaveChanges = () => {
     82        console.log(`Confirm password: ${confirmPassword}`);
     83        const mobileNumber = employeeEditableData.mobile;
     84        delete employeeEditableData.mobile;
    8185        const changedEmployee = {
    8286            ...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();
    84105        }
    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                    }}
    101135                />
    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    );
    211298};
    212299
  • 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";
     1import styled from 'styled-components';
     2import {
     3  Button,
     4  InputLabel,
     5  MenuItem,
     6  Select,
     7  Switch,
     8  TextField,
     9  Typography,
     10} from '@mui/material';
    311import VIcon from '@mui/icons-material/Visibility';
    412import VOffIcon from '@mui/icons-material/VisibilityOff';
    513
    614export 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};
    1726`;
    1827
    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;
     28export const Title = styled(Typography).attrs({})`
     29  border-bottom: 2px solid rgba(0, 0, 0, 0.12);
     30  padding-bottom: 10px;
     31  margin-bottom: 15px;
    2532`;
    2633
    2734export 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;
    3240`;
    3341
    34 export const LabelAndInputWrapper = styled.div`
     42export const LabelAndInputWrapper = styled.div``;
    3543
    36 `;
     44export const Label = styled(InputLabel).attrs({})``;
    3745
    38 export const Label = styled(InputLabel).attrs({
    39 
    40 })``;
    41 
    42 export const StandardInputField = styled(TextField).attrs({
    43 
    44 })`
    45     width: 300px;
     46export const StandardInputField = styled(TextField).attrs({})`
     47  width: 300px;
    4648`;
    4749
    4850export const VisibilityIcon = styled(VIcon).attrs({
    49     sx: {
    50         fontSize: '2rem'
    51     }
     51  sx: {
     52    fontSize: '2rem',
     53  },
    5254})``;
    5355
    5456export const VisibilityOffIcon = styled(VOffIcon).attrs({
    55     sx: {
    56         fontSize: '2rem'
    57     }
     57  sx: {
     58    fontSize: '2rem',
     59  },
    5860})``;
    5961
    60 export const Dropdown = styled(Select).attrs({
    61 
    62 })`
    63     width: 300px;
     62export const Dropdown = styled(Select).attrs({})`
     63  width: 300px;
    6464`;
    6565
    66 export const DropdownOption = styled(MenuItem).attrs({
     66export 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`;
    6777
    68 })``;
     78// export const
    6979
    7080export const SwitchRowWrapper = styled.div`
    71     text-align: center;
    72     margin-top: 10px;
    73     margin-bottom: 50px;
     81  text-align: center;
     82  margin-top: 10px;
     83  margin-bottom: 50px;
    7484`;
    7585
    7686export const SwitchTitle = styled.p`
    77     font-size: 18px;
    78     font-weight: 600;
    79     margin: 0;
    80     margin-bottom: 5px;
     87  font-size: 18px;
     88  font-weight: 600;
     89  margin: 0;
     90  margin-bottom: 5px;
    8191`;
    8292
    8393export 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;
    8998`;
    9099
    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     }
     100export 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  }
    105113`;
    106114
     115export const BackAndSaveChangesButtonsWrapper = styled.div``;
    107116
    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     }
     117export const DeleteButton = styled(Button).attrs((props) => ({
     118  variant: 'contained',
     119  sx: {
     120    backgroundColor: `${props.theme.palette.error.main}`,
     121  },
    117122}))`
    118     :hover {
    119         background-color: ${props => props.theme.palette.error.dark};
    120     }
     123  :hover {
     124    background-color: ${(props) => props.theme.palette.error.dark};
     125  }
    121126`;
    122127
    123128export const BackButton = styled(Button).attrs({
    124     variant: 'outlined'
     129  variant: 'outlined',
    125130})`
    126     margin-right: 15px;
    127     :hover {
    128         border: 2px solid;
    129         font-weight: 600;
    130     }
     131  margin-right: 15px;
     132  :hover {
     133    border: 2px solid;
     134    font-weight: 600;
     135  }
    131136`;
    132137
    133 export const SaveChangesButton = styled(Button).attrs(props => ({
    134     variant: 'contained',
    135     sx: {
    136         backgroundColor: `${props.theme.palette.primary.main}`
    137     }
     138export const SaveChangesButton = styled(Button).attrs((props) => ({
     139  variant: 'contained',
     140  sx: {
     141    backgroundColor: `${props.theme.palette.primary.main}`,
     142  },
    138143}))`
    139     padding: 10px 16px;
    140     :hover {
    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  }
    143148`;
  • sources/client/src/components/admin/EmployeesTable/index.js

    re8b1076 rbc20307  
     1import { useEffect } from 'react';
    12import { useHistory } from 'react-router';
    23
    34import {
    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,
    1819} from './styles';
    1920
    2021import InputAdornment from '@mui/material/InputAdornment';
     22import DropdownViewer from '../../DropdownViewer';
    2123
    2224import { employeeStatus, accountStatus } from '../../../config/enums';
     25import useGetData from '../../../hooks/useGetData';
     26import useToggleAccountStatus from '../../../hooks/useToggleAccountStatus';
     27import AbsoluteLoader from '../../Loaders/AbsoluteLoader';
    2328
    24 import { employees } from './mockData';
     29// import { employees } from './mockData';
    2530import { useState } from 'react';
    2631
    2732const 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    };
    3152
    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        );
    3562
    36   const onAccountStatusClick = (event, id) => {
    37     event.stopPropagation();
    38     console.log(`Disable or activate user acc with id: ${id}`);
    39   };
     63        setFilteredEmployees(filteredData);
     64    };
    4065
    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        )
    49169    );
    50 
    51     setFilteredEmployees(filteredData);
    52   };
    53 
    54   return (
    55     <TableContainer>
    56       <TableHeaderWrapper>
    57         <TableTitle variant='h5'>Вработени</TableTitle>
    58         <SearchField
    59           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             <TableRow
    89               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                 <ToggleAccoutStatusButton
    103                   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.accountActive
    110                     ? accountStatus.enabled
    111                     : accountStatus.disabled}
    112                 </ToggleAccoutStatusButton>
    113               </ButtonTableCell>
    114             </TableRow>
    115           ))}
    116         </TableBody>
    117       </Table>
    118     </TableContainer>
    119   );
    120170};
    121171
  • sources/client/src/components/admin/EmployeesTable/mockData.js

    re8b1076 rbc20307  
    11export const employees = [
    2     {
    3         id: 1,
    4         email: 'viktor-tasevski@hotmail.com',
    5         password: '123',
    6         firstName: 'Viktor',
    7         lastName: 'Tasevski',
    8         zone: 'zone1',
    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         zone: 'zone2',
    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         zone: 'zone3',
    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         zone: 'zone4',
    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         zone: 'zone5',
    53         phoneNumber: '071206205',
    54         status: 'notWorking',
    55         accountActive: false
    56     }
     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  },
    5757];
  • sources/client/src/components/admin/EmployeesTable/styles.js

    re8b1076 rbc20307  
    1818  component: Paper,
    1919})`
    20   max-width: 1000px;
     20  max-width: 1100px;
    2121  margin-top: 20px;
    2222  margin-left: 30px;
     
    3333  border-bottom: none;
    3434  padding: 10px 16px;
     35  position: relative;
    3536  background-color: ${(props) => props.theme.palette.grey[400]};
    3637`;
  • sources/client/src/components/admin/ParkingZone/index.js

    re8b1076 rbc20307  
    1 import { useState } from 'react';
     1import { useState, useContext, useEffect } from 'react';
    22import { useParams } from 'react-router-dom';
    33
     
    77import ParkingZoneSessions from '../ParkingZoneSessions';
    88import GoogleMaps from '../../GoogleMaps';
    9 
     9import CircularProgress from '@mui/material/CircularProgress';
    1010import {
    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,
    1819} from './styles';
    1920
     21import AbsoluteLoader from '../../Loaders/AbsoluteLoader';
    2022import { roles } from '../../../config/enums';
     23import { UserContext } from '../../../context/UserContext';
     24import useGetData from '../../../hooks/useGetData';
    2125
    22 import { parkingZones } from '../ParkingZones/mockData';
     26const defaultLocationObj = {
     27    centre: {
     28        lat: 42.000629,
     29        lng: 21.420525,
     30    },
     31    coords: []
     32};
     33
     34const 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];
    2349
    2450const activeComponentEnum = {
    25   MAPS: 'maps',
    26   INFO: 'info',
     51    MAPS: 'maps',
     52    INFO: 'info',
    2753};
    2854
    2955const 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    });
    3462
    35   const user = {
    36     role: 'ROLE_ADMIN',
    37   };
     63    const [zoneState, setZoneState] = useState(null);
     64    const [activeComponent, setActiveComponent] = useState(
     65        activeComponentEnum.MAPS
     66    );
    3867
    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;
    4873
    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>
    5092
    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 />
    6594
    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    );
    79138};
    80139
  • sources/client/src/components/admin/ParkingZone/styles.js

    re8b1076 rbc20307  
    33import MapOutlinedIcon from '@mui/icons-material/MapOutlined';
    44import EqualizerOutlinedIcon from '@mui/icons-material/EqualizerOutlined';
     5import { mobile_max_width } from '../../../config/utilities';
     6import CircularProgress from '@mui/material/CircularProgress';
    57
    68export const NamesWrapper = styled.div`
     
    1113  justify-content: space-between;
    1214  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  }
    1423`;
    1524
     
    1928  margin: 0,
    2029  color: `${props.theme.palette.primary.main}`,
    21 }))``;
     30}))`
     31  display: flex;
     32  align-items: center;
     33`;
     34
     35export 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`;
    2245
    2346export const DividerUnderNames = styled(Divider).attrs({
    2447  variant: 'middle',
    2548  sx: {
    26     margin: '0 160px',
     49    margin: '0 9%',
    2750    borderWidth: '2px',
    2851    borderBottomWidth: 'thin',
  • sources/client/src/components/admin/ParkingZoneInfo/index.js

    re8b1076 rbc20307  
    1 const ParkingZoneInfo = () => {
    2     return <h1>PARKING ZONE INFO</h1>
     1import { useState } from 'react';
     2
     3import { Wrapper } from './styles';
     4
     5import ParkingZoneInfoViewer from '../ParkingZoneInfoViewer';
     6import ParkingZoneInfoEdit from '../ParkingZoneInfoEdit';
     7
     8const 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  );
    323};
    424
  • sources/client/src/components/admin/ParkingZoneInfo/styles.js

    re8b1076 rbc20307  
     1import styled from 'styled-components';
     2
     3export 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  
     1import { useState } from 'react';
     2import moment from 'moment';
     3
    14import {
    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,
    1113} from './styles';
    1214
    1315import { sessionStatus } from '../../../../config/enums';
     16import { dateFormatString } from '../../../../config/utilities';
    1417import { IconButton } from '@mui/material';
    15 import { useState } from 'react';
     18import AbsoluteLoader from '../../../Loaders/AbsoluteLoader';
     19import useDeleteSession from '../../../../hooks/useDeleteSession';
     20
    1621
    1722const 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
     28const 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
     83const 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
     171const 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
     234const 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    }
    127255};
    128256
  • sources/client/src/components/admin/ParkingZoneSessions/SessionCard/styles.js

    re8b1076 rbc20307  
    22import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
    33import Button from '@mui/material/Button';
     4import { mobile_max_width } from '../../../../config/utilities';
    45
    56export const Wrapper = styled.div`
     
    1516  font-size: 1rem;
    1617  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  }
    1724`;
    1825
     
    2027  display: flex;
    2128  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  }
    2236`;
    2337
     
    3852  position: relative;
    3953
    40   button {
     54  button,
     55  > div {
    4156    padding: 0;
    4257    position: absolute;
     
    6984  }
    7085`;
    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, useState } from 'react';
     1import { useEffect, useMemo, useRef, useState } from 'react';
    22import {
    33  Wrapper,
     
    99  StatsKey,
    1010  StatsValue,
     11  SearchField,
     12  SearchIcon,
    1113} from './styles';
    1214import SessionCard from './SessionCard';
    1315import { sessionStatus } from '../../../config/enums';
    14 import { sessionsData } from './mockData';
     16import useGetSessions from '../../../hooks/useGetSessions';
     17import AbsoluteLoader from '../../Loaders/AbsoluteLoader';
     18
     19import useActivateSession from '../../../hooks/useActivateSession';
     20
     21const 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};
    1557
    1658const 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('');
    1863  const [stats, setStats] = useState({
    1964    activeSessions: 0,
     
    2166    overSessions: 0,
    2267  });
     68
    2369  const setSessionsStats = () => {
    2470    let aS = 0;
     
    2672    let oS = 0;
    2773    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        }
    4187    });
    4288    setStats({
     
    4793  };
    4894
     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
    49115  useEffect(() => {
    50116    setSessionsStats();
     
    52118
    53119  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>
    96200  );
    97201};
  • sources/client/src/components/admin/ParkingZoneSessions/mockData.js

    re8b1076 rbc20307  
    55    id: 1,
    66    start: moment().format('hh:mm - DD.MM.yyyy'),
     7    zone: 'Zona 1',
    78    plate: 'SK-8190-AV',
    89    status: 'active',
     
    1213    id: 2,
    1314    start: moment().format('hh:mm - DD.MM.yyyy'),
     15    zone: 'Zona 2',
    1416    plate: 'ST-9312-OK',
    1517    status: 'idle',
     
    2022    start: moment().format('hh:mm - DD.MM.yyyy'),
    2123    end: moment().format('hh:mm - DD.MM.yyyy'),
     24    zone: 'Zona 1',
    2225    plate: 'SK-6511-OS',
    2326    status: 'over',
  • sources/client/src/components/admin/ParkingZoneSessions/styles.js

    re8b1076 rbc20307  
    11import styled from 'styled-components';
    22import { Typography } from '@mui/material';
     3import TextField from '@mui/material/TextField';
     4import SIcon from '@mui/icons-material/Search';
     5
     6import { mobile_max_width } from '../../../config/utilities';
    37
    48export const Wrapper = styled.div`
     
    610  flex-direction: column;
    711  align-items: center;
    8   margin: 0;
    9   padding: 30px 175px 0 175px;
     12  padding: 30px 12% 0 12%;
    1013  width: 100%;
     14  max-width: 1440px;
     15  margin: auto;
    1116  height: 100%;
    1217`;
     
    2025  -webkit-background-clip: text;
    2126  -webkit-text-fill-color: transparent;
     27  text-align: center;
    2228`;
    2329
     
    2834  justify-content: space-between;
    2935  margin: 30px 0 10px 0;
     36
     37  @media (max-width: ${mobile_max_width}px) {
     38    flex-direction: column;
     39  }
    3040`;
    3141
     
    3343  display: flex;
    3444  flex-direction: column;
     45  align-items: center;
    3546`;
    3647
     
    3849  display: flex;
    3950  flex-direction: row;
    40   width: 220px;
     51  width: 280px;
    4152  justify-content: space-between;
    4253`;
     
    4758  -webkit-background-clip: text;
    4859  -webkit-text-fill-color: transparent;
     60  font-size: 1.2rem;
    4961`;
    5062
     
    5264  margin: 0;
    5365  font-weight: 600;
    54   font-size: 1.2rem;
     66  font-size: 1.4rem;
    5567  background: -webkit-linear-gradient(#333333, #4cc5a3);
    5668  -webkit-background-clip: text;
     
    5870`;
    5971
     72export 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
     92export 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
    6099export const SessionsWrapper = styled.div`
    61   height: 45vh;
     100  height: auto;
     101  min-height: 240px;
     102  max-height: 380px;
    62103  width: 100%;
    63104  overflow: auto;
    64   padding-top: 20px;
    65 
    66105  > div:not(div:first-of-type) {
    67106    margin-top: 24px;
    68107  }
     108
     109  @media (max-width: ${mobile_max_width}px) {
     110    max-height: 75vh;
     111    padding-bottom: 10px;
     112  }
    69113`;
  • sources/client/src/components/admin/ParkingZones/ParkingZoneCard/index.js

    re8b1076 rbc20307  
    22
    33import {
    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,
    1212} from './styles';
    1313
     14import DropdownViewer from '../../../DropdownViewer';
     15
    1416const ParkingZoneCard = ({ info }) => {
    15   let history = useHistory();
     17    let history = useHistory();
     18    const takenDividedByTotal = info.takenSpaces / info.capacity;
    1619
    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    );
    4546};
    4647
  • sources/client/src/components/admin/ParkingZones/ParkingZoneCard/styles.js

    re8b1076 rbc20307  
    22import Grid from '@mui/material/Grid';
    33import { Typography } from '@mui/material';
    4 
    5 import { LineProgressBar } from '@frogress/line'
    6 
     4import { LineProgressBar } from '@frogress/line';
     5import { mobile_max_width } from '../../../../config/utilities';
    76
    87export 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  }
    1014`;
    1115
    1216export const Container = styled.div`
    13     display: flex;
    14     flex-direction: column;
    15     align-items: center;
    16     width: 100%;
    17     height: 100%;
    18     box-shadow: 15px 15px 10px ${props => props.theme.palette.background.shadow};
    19     background-color: ${props => props.theme.palette.background.white};
    20     :hover {
    21         opacity: 0.8;
    22         cursor: pointer;
    23     }
    24     position: relative;
     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;
    2529`;
    2630
    2731export const ZoneName = styled(Typography).attrs({
    28     variant: 'h2'
     32  variant: 'h2',
    2933})`
    30     font-size: 2rem;
    31     font-weight: 600;
    32     margin-top: 30px;
    33     text-align: center;
     34  font-size: 2rem;
     35  font-weight: 600;
     36  margin-top: 30px;
     37  text-align: center;
    3438`;
    3539
    3640export 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;
    4246`;
    4347
    4448export const Label = styled(Typography).attrs({
    45     variant: 'h4'
     49  variant: 'h4',
    4650})`
    47     font-size: 1rem;
    48     margin-bottom: 5px;
     51  font-size: 1rem;
     52  margin-bottom: 5px;
    4953`;
    5054
    5155export const Value = styled(Typography).attrs({
    52     variant: 'h3'
     56  variant: 'h3',
    5357})`
    54     font-size: 1.25rem;
    55     font-weight: 600;
    56     margin-top: 5px;
     58  font-size: 1.25rem;
     59  font-weight: 600;
     60  margin-top: 5px;
    5761`;
    5862
    59 export const ProgressBar = styled(LineProgressBar).attrs(props => ({
    60     stripe: true,
    61     progressColor: props.theme.palette.primary.main,
    62     height: '30px',
     63export const ProgressBar = styled(LineProgressBar).attrs((props) => ({
     64  stripe: true,
     65  progressColor: props.theme.palette.primary.main,
     66  height: '30px',
    6367}))`
    64     position: absolute;
    65     bottom: 5px;
     68  position: absolute;
     69  bottom: 5px;
    6670`;
    6771
    6872export const ProgressBarLabel = styled.p`
    69     margin: 0 0 0 42.5%;
    70     line-height: 30px;
    71     position: absolute;
    72     font-weight: 500;
     73  margin: 0 0 0 42.5%;
     74  line-height: 30px;
     75  position: absolute;
     76  font-weight: 500;
    7377`;
  • sources/client/src/components/admin/ParkingZones/index.js

    re8b1076 rbc20307  
    1 import { useState } from 'react';
     1import { useState, useContext } from 'react';
    22import {
    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,
    1523} from './styles';
    1624
     25import AbsoluteLoader from '../../Loaders/AbsoluteLoader';
     26import Modal from '@mui/material/Modal';
     27import Backdrop from '@mui/material/Backdrop';
     28import { IconButton, Slide } from '@mui/material';
    1729import ParkingZoneCard from './ParkingZoneCard';
    1830
    1931import { roles } from '../../../config/enums';
     32import { UserContext } from '../../../context/UserContext';
     33import useGetData from '../../../hooks/useGetData';
     34import useCreateZone from '../../../hooks/useCreateZone';
    2035
    2136import { parkingZones } from './mockData';
    2237
    2338const 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;
    3148    }
    32     return a.zoneName - b.zoneName;
    33   }
    3449};
    3550
    3651const 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
     64const sortByName = (a, b) => {
     65    if (a.zoneName >= b.zoneName) {
     66        return 1;
     67    }
    4068    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;
    5469};
    5570
    5671const 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    );
    121240};
    122241
  • sources/client/src/components/admin/ParkingZones/mockData.js

    re8b1076 rbc20307  
    11export 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        ],
    2051    },
    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    },
    81136];
  • sources/client/src/components/admin/ParkingZones/styles.js

    re8b1076 rbc20307  
    77import ClearIcon from '@mui/icons-material/Clear';
    88import { ParkingZoneWrapper, Container } from './ParkingZoneCard/styles';
     9import Box from '@mui/material/Box';
     10import Button from '@mui/material/Button';
     11import TextField from '@mui/material/TextField';
     12import CIcon from '@mui/icons-material/Close';
     13import { mobile_max_width } from '../../../config/utilities';
    914
    1015export 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    }
    1827`;
    1928
    2029export 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    }
    2538`;
    2639
    2740export 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    }
    3448`;
    3549
    3650export 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    }
    4459`;
    4560
    4661export 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    }
    5570`;
    5671
    5772export 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    },
    6479})``;
    6580
    6681export 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}`,
    7186}))``;
    7287
    7388export 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}`,
    7893}))``;
    7994
    8095export 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    }
    85105`;
    86106
     
    88108
    89109export 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;
    93113`;
    94114
    95115export const AddIcon = styled(Add).attrs({
    96   sx: {
    97     fontWeight: 500,
    98   },
     116    sx: {
     117        fontWeight: 500,
     118    },
    99119})`
    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};
    103123`;
     124
     125export 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
     136export const ModalTitle = styled(Typography).attrs({
     137    variant: 'h4',
     138    fontWeight: 600,
     139    textAlign: 'center',
     140})``;
     141
     142export const ModalInputAndLabelWrapper = styled.div`
     143    text-align: center;
     144    padding: 0 20px;
     145    margin-top: 40px;
     146    margin-bottom: 70px;
     147`;
     148
     149export const ModalInputLabel = styled(Typography).attrs({
     150    variant: 'h6',
     151})``;
     152
     153export 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
     175export const ButtonWrapper = styled.div`
     176    width: 100%;
     177    text-align: center;
     178`;
     179
     180export 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
     192export const CloseIcon = styled(CIcon).attrs({
     193    sx: {
     194        color: 'red',
     195        fontSize: '2.5rem',
     196    },
     197})``;
  • sources/client/src/config/defaultUser.js

    re8b1076 rbc20307  
    44    firstName: '',
    55    lastName: '',
    6     zone: 'none',
    7     phoneNumber: '',
    8     status: 'notWorking',
    9     accountActive: false
     6    parkingZones: ['NONE'],
     7    mobile: '',
     8    status: 'NOT_WORKING',
     9    locked: true,
    1010};
  • sources/client/src/config/enums.js

    re8b1076 rbc20307  
    11export const employeeStatus = {
    2   notWorking: 'Не работи',
    3   working: 'Работи',
    4   vacation: 'На одмор',
     2    NOT_WORKING: 'Не работи',
     3    WORKING: 'Работи',
     4    VACATION: 'На одмор',
     5    PAID_LEAVE: 'На боледување',
    56};
    67
    78export const accountStatus = {
    8   enabled: 'Активен',
    9   disabled: 'Неактивен',
     9    enabled: 'Активен',
     10    disabled: 'Неактивен',
    1011};
    1112
    1213export const sessionStatus = {
    13   active: 'active',
    14   idle: 'idle',
    15   over: 'over',
     14    idle: 'STARTED_UNVERIFIED',
     15    active: 'STARTED_VERIFIED',
     16    over: 'ENDED_UNPAID',
    1617};
    1718
    1819export 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',
    2224};
  • sources/client/src/hooks/useForm.js

    re8b1076 rbc20307  
    1010    });
    1111  };
    12 
     12  const setNewData = (newData) => {
     13    setData({...newData});
     14  }
    1315  return {
    1416    data,
    1517    onFormChange,
     18    setNewData
    1619  };
    1720};
  • sources/client/src/index.css

    re8b1076 rbc20307  
    22*::before,
    33*::after {
    4   box-sizing: border-box;
     4    box-sizing: border-box;
    55}
    66html,
    77body,
    88#root {
    9   height: 100%;
     9    height: 100%;
    1010}
    1111body {
    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
     21td,
     22input {
     23    overflow: hidden;
    1924}
    2025
    2126code {
    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;
    2429}
    2530
    2631#root {
    27   height: 100%;
     32    height: 100%;
    2833}
  • sources/client/src/index.js

    re8b1076 rbc20307  
    44import App from './App';
    55import reportWebVitals from './reportWebVitals';
    6 
     6import axios from 'axios';
    77import { BrowserRouter } from 'react-router-dom';
    88import { ThemeProvider } from 'styled-components';
    99
    1010import { StyledEngineProvider } from '@mui/material/styles';
    11 
    1211import theme from './theme/index';
    1312
     13axios.defaults.headers.post['Content-Type'] = 'application/json';
     14axios.defaults.headers.post['Content-Type'] = 'application/json';
     15axios.defaults.headers.post['Access-Control-Allow-Origin'] = '*';
     16axios.defaults.headers.patch['Access-Control-Allow-Origin'] = '*';
     17axios.defaults.headers.delete['Access-Control-Allow-Origin'] = '*';
     18
    1419ReactDOM.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')
    2930);
    3031
  • sources/client/src/screens/AdminHomeScreen/index.js

    re8b1076 rbc20307  
     1import { useContext, useState } from 'react';
    12import { Route, Switch, Redirect } from 'react-router-dom';
    23import { useHistory } from 'react-router-dom';
     
    1920  DividerUnderHeader,
    2021  UserIcon,
     22  MenuBurgerIcon,
    2123} from './styles';
    2224
    2325import IconButton from '@mui/material/IconButton';
    2426
    25 import logo from '../../resources/logo_2_transparent_bg.jpg';
     27import logo from '../../resources/logo_2_transparent_bg.png';
    2628import { roles } from '../../config/enums';
    2729import DestinationComponent from '../../utils/DestinationComponent';
    2830import onClickRouting from '../../utils/onClickRouting';
     31import AbsoluteLoader from '../../components/Loaders/AbsoluteLoader';
    2932
    3033import ParkingZones from '../../components/admin/ParkingZones';
     
    3336import EmployeeCreate from '../../components/admin/EmployeeCreate';
    3437import ParkingZone from '../../components/admin/ParkingZone';
     38
     39import { UserContext } from '../../context/UserContext';
     40import { AccessoriesContext } from '../../context/AccessoriesContext';
     41import useLogoutUser from '../../hooks/useLogoutUser';
    3542
    3643const ToParkingZones = new DestinationComponent('/', ParkingZones, true);
     
    6774
    6875const AdminHomeScreen = (props) => {
     76  const { user, isLoadingUser } = useContext(UserContext);
     77  const { isMobile, setIsOpenDrawer } = useContext(AccessoriesContext);
    6978  let history = useHistory();
    70   const user = {
    71     role: 'ROLE_ADMIN',
    72   };
    73 
     79  const { logoutUser } = useLogoutUser();
    7480  let routes = user.role === roles.admin ? adminRoutes : employeeRoutes;
    7581
    7682  return (
    7783    <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}
    88108
    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>
    97125
    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      )}
    121155    </Container>
    122156  );
  • sources/client/src/screens/AdminHomeScreen/styles.js

    re8b1076 rbc20307  
    77import SupervisorAccount from '@mui/icons-material/SupervisorAccount';
    88import AccountCircleIcon from '@mui/icons-material/AccountCircle';
     9import Box from '@mui/material/Box';
     10import MenuIcon from '@mui/icons-material/Menu';
     11import CloseI from '@mui/icons-material/Close';
     12import { mobile_max_width } from '../../config/utilities';
     13import backgroundImage from '../../resources/login_background.jpg';
    914
    1015export const Container = styled.div`
    1116  display: flex;
    1217  flex-direction: row;
    13   height: 100%;
     18  min-height: 100vh;
    1419  flex: 1;
    1520`;
     
    2025  flex-direction: row;
    2126  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  }
    2334`;
    2435
     
    2637  variant: 'middle',
    2738  sx: {
    28     margin: '0 25px',
     39    margin: '0 5%',
     40    marginTop: '75px',
    2941    borderWidth: '2px',
    3042    borderBottomWidth: 'thin',
     
    7587  sx: {
    7688    fontSize: 30,
    77     color: `${props.theme.palette.error.light}`,
     89    color: `${props.theme.palette.error.main}`,
    7890  },
    7991}))``;
     
    8294  display: flex;
    8395  flex-direction: column;
    84   height: 100%;
    8596  width: 100%;
     97  padding-left: 70px;
    8698  background-color: ${(props) => props.theme.palette.background.whiteSmoke};
     99
     100  @media (max-width: ${mobile_max_width}px) {
     101    padding-left: 0;
     102  }
    87103`;
    88104
    89105export const SideMenu = styled.div`
    90106  flex-direction: column;
    91   height: 100%;
    92107  width: 70px;
    93108  padding: 20px 10px 0 10px;
    94109  border-right: 1px solid grey;
     110  position: fixed;
     111  height: 100%;
    95112  background-color: ${(props) => props.theme.palette.primary.main};
    96113`;
     
    124141  width: 100%;
    125142  height: 100%;
    126   padding: 20px 0px;
     143  padding-top: 20px;
    127144  display: flex;
    128145  flex-direction: column;
    129146`;
     147
     148export 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";
     1import { createTheme } from '@mui/material';
    22
    33const 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',
    209    },
    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  // }
    2831});
    2932
  • sources/client/yarn.lock

    re8b1076 rbc20307  
    11641164    regenerator-runtime "^0.13.4"
    11651165
    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":
    11671167  version "7.15.4"
    11681168  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
     
    16491649  integrity sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI=
    16501650
     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
    16511715"@mui/core@5.0.0-alpha.47":
    16521716  version "5.0.0-alpha.47"
     
    18111875  integrity sha512-HnUhk1Sy9IuKrxEMdIRCxpIqPw6BFsbYSEUO9p/hNw5sMld/+3OLMWQP80F8/db9qsv3qUjs7ZR5bS/R+iinXw==
    18121876
     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
    18131892"@rollup/plugin-node-resolve@^7.1.1":
    18141893  version "7.1.3"
     
    21682247    "@types/react" "*"
    21692248
    2170 "@types/react-transition-group@^4.4.2":
     2249"@types/react-transition-group@^4.2.0", "@types/react-transition-group@^4.4.2":
    21712250  version "4.4.3"
    21722251  resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.3.tgz#b0994da0a7023d67dbb4a8910a62112bc00d5688"
     
    29122991  resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.2.tgz#7cf783331320098bfbef620df3b3c770147bc224"
    29132992  integrity sha512-V+Nq70NxKhYt89ArVcaNL9FDryB3vQOd+BFXZIfO3RP6rwtj+2yqqqdHEkacutglPaZLkJeuXKCjCJDMGPtPqg==
     2993
     2994axios@^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"
    29143000
    29153001axobject-query@^2.2.0:
     
    36843770    wrap-ansi "^6.2.0"
    36853771
    3686 clsx@^1.1.1:
     3772clsx@^1.0.4, clsx@^1.1.1:
    36873773  version "1.1.1"
    36883774  resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
     
    42934379    cssom "~0.3.6"
    42944380
     4381csstype@^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
    42954386csstype@^3.0.2, csstype@^3.0.8:
    42964387  version "3.0.9"
     
    45114602  dependencies:
    45124603    path-type "^4.0.0"
     4604
     4605dnd-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"
    45134613
    45144614dns-equal@^1.0.0:
     
    55005600  integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==
    55015601
     5602follow-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
    55025607for-in@^1.0.2:
    55035608  version "1.0.2"
     
    72777382    verror "1.10.0"
    72787383
     7384jss-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
    72797393jss-plugin-camel-case@^10.7.1:
    72807394  version "10.8.0"
     
    72867400    jss "10.8.0"
    72877401
     7402jss-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
    72887410jss-plugin-default-unit@^10.7.1:
    72897411  version "10.8.0"
     
    72947416    jss "10.8.0"
    72957417
     7418jss-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
    72967426jss-plugin-global@^10.7.1:
    72977427  version "10.8.0"
     
    73027432    jss "10.8.0"
    73037433
     7434jss-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
    73047443jss-plugin-nested@^10.7.1:
    73057444  version "10.8.0"
     
    73117450    tiny-warning "^1.0.2"
    73127451
     7452jss-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
    73137460jss-plugin-props-sort@^10.7.1:
    73147461  version "10.8.0"
     
    73197466    jss "10.8.0"
    73207467
     7468jss-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
    73217477jss-plugin-rule-value-function@^10.7.1:
    73227478  version "10.8.0"
     
    73287484    tiny-warning "^1.0.2"
    73297485
     7486jss-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
    73307495jss-plugin-vendor-prefixer@^10.7.1:
    73317496  version "10.8.0"
     
    73477512    tiny-warning "^1.0.2"
    73487513
     7514jss@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
    73497524"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0:
    73507525  version "3.2.0"
     
    73557530    object.assign "^4.1.2"
    73567531
     7532jwt-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
    73577537killable@^1.0.1:
    73587538  version "1.0.1"
     
    76237803  dependencies:
    76247804    object-visit "^1.0.0"
     7805
     7806material-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==
    76257810
    76267811md5.js@^1.3.4:
     
    86318816  dependencies:
    86328817    ts-pnp "^1.1.6"
     8818
     8819popper.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==
    86338823
    86348824portfinder@^1.0.26:
     
    96069796    text-table "0.2.0"
    96079797
     9798react-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
     9805react-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
     9812react-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
     9820react-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
    96089831react-dom@^17.0.2:
    96099832  version "17.0.2"
     
    96259848  integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
    96269849
     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
    96279855react-is@^17.0.1:
    96289856  version "17.0.1"
    96299857  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
    96309858  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==
    96369859
    96379860react-refresh@^0.8.3:
     
    97359958    fsevents "^2.1.3"
    97369959
    9737 react-transition-group@^4.4.2:
     9960react-transition-group@^4.4.0, react-transition-group@^4.4.2:
    97389961  version "4.4.2"
    97399962  resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
     
    984110064    indent-string "^4.0.0"
    984210065    strip-indent "^3.0.0"
     10066
     10067redux@^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"
    984310073
    984410074regenerate-unicode-properties@^8.2.0:
Note: See TracChangeset for help on using the changeset viewer.