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

Location:
sources/client/src/components
Files:
50 added
23 edited
2 moved

Legend:

Unmodified
Added
Removed
  • 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})``;
Note: See TracChangeset for help on using the changeset viewer.