vendredi 28 mai 2021

Updating time from DateTimeRangePicker does not update immediately

Issue:

  • Updating time from DateTimeRangePicker does not update instantly,

(ie. it flickers and returns to a previous value, multiple clicks are required to update time, the number of clicks required does not behave consistently)

Platform Tested On:

  1. Mozilla Firefox (Ubuntu 20.04.2 LTS)

Solutions Attempted:

  • Tried to debounce by creating a useDebounce hook which added timeout of 10, 50, 500 ms before allowing next update. (No difference to outcome)

Potential Lead:

  • Similar implementation seems to work on another screen without error.
  • I suspect that something from the render order might be causing the issue. However, looking through the entire code, cant seem to find.

Screenshot of Time Picker

import moment from 'moment';
import React, { useEffect, useMemo, useState } from 'react';
import { Badge, Button, Col, Container, Form, Image, Row } from 'react-bootstrap';
import { DateRangePicker } from 'react-dates';
import DatetimeRangePicker from 'react-datetime-range-picker';
import { useDispatch, useSelector } from 'react-redux';
import Select from 'react-select';
import { PageTitle, ReactTable, SearchBar } from '../../../components';
import { getAttendanceActivity, getJobList, getLocations } from '../../../redux/actions';
import { convertAttendanceToLocalTimezone } from './service/convertAttendanceToLocalTimezone';
import useDebounce from '../../../hooks/useDebounceHook';

export default function EmployeeList() {
    const dispatch = useDispatch();
    const rsAttendance = useSelector(state => state.attendance.attendance);
    const rsLocations = useSelector(state => state.locations.locations);
    const rsJobs = useSelector(state => state.jobs.jobList);
    const rsUserTimezone = useSelector(state => state.timezone.userTimezone)

    const columns = useMemo(() => [
        {
            Header: '#',
            accessor: 'row',
            Cell: ({ row }) => row.index + 1
        }
        , {
            Header: '',
            accessor: 'avatar',
            disableSortBy: true,
            style: { width: '1%' },
            Cell: ({ value }) => ( // { value } will be avatar image url
                <Image
                    roundedCircle
                    className='shadow-sm'
                    width='40px'
                    src={require('../../../assets/images/avatar.svg')}
                />
            )
        }
        , {
            Header: 'Name',
            accessor: 'email',
        }
        , {
            Header: 'Location',
            accessor: 'location_name',
            Cell: ({ value }) => <Badge pill variant='info' className='location-badge'>{value}</Badge>
        }
        , {
            Header: 'Job Name',
            accessor: 'job_name',
            Cell: ({ value }) => <Badge pill variant='info' className='location-badge'>{value}</Badge>
        }
        , {
            Header: 'Date',
            accessor: 'attendance_date',
            Cell: ({ value }) =>  moment(value).format('DD MMM')
        }
        , {
            Header: 'Time In',
            accessor: 'first_check_in.attendance_check_in_time',
            Cell: ({ value }) => value ? moment(value, 'HH:mm:ss').format('hh:mm A') : '-'
        }
        , {
            Header: 'Time Out',
            accessor: 'last_check_out.attendance_check_out_time',
            Cell: ({ value }) => value ? moment(value, 'HH:mm:ss').format('hh:mm A') : '-'
        }
        , {
            Header: 'Status',
            accessor: 'status',
            Cell: ({ value }) => {
                if (value === 'on time') {
                    return <><span className='dot success'></span>On time</>
                } else if (value === 'late') {
                    return <><span className='dot danger'></span>Late</>
                }
            }
        }
    ], []);

    const [data, setData] = useState([]);
    const [locationOptions, setLocationOptions] = useState([]);
    const [focused, setFocused] = useState(null); // react-dates
    const [timeFocus, setTimeFocus] = useState(false); // react-datetime-range-picker
    const [startDate, setStartDate] = useState(null);
    const [endDate, setEndDate] = useState(null);
    const [startTime, setStartTime] = useState(moment().startOf('day'));
    const [endTime, setEndTime] = useState(moment().endOf('day'));
    const [location, setLocation] = useState(null);
    const [job, setJob] = useState(null);
    const [jobOptions, setJobOptions] = useState([]);

    // const debouncedStartTime = useDebounce(startTime, 1);
    // const debouncedEndTime = useDebounce(endTime, 1);

    useEffect(() => {
        dispatch(getLocations());
        dispatch(getJobList())
    },[])

    useEffect(() => {
        let dispatchOptions = {
            startDate: '2000-01-01',
            endDate: '2050-01-01',
        };
        if (startDate) dispatchOptions.startDate = moment.tz(startDate, rsUserTimezone).utc().format('YYYY-MM-DD');
        if (endDate) dispatchOptions.endDate = moment(endDate, rsUserTimezone).utc().format('YYYY-MM-DD');
        if (job) dispatchOptions.jobId = job.value;
        dispatch(getAttendanceActivity({...dispatchOptions}))
    }, [startDate, endDate, job, location]);

    useEffect(() => {
        if (rsLocations) {
            let options = [];

            rsLocations.map(el => {
                options.push({ value: el.id, label: el.location_name });
            });

            setLocationOptions(options);
        }
    }, [rsLocations]);

    useEffect(() => {
        if (rsJobs) {
            let options = [];

            rsJobs.map(el => {
                options.push({ value: el.id, label: el.job_name })
            })
            
            setJobOptions(options)
        }
    }, [rsJobs])

    useEffect(() => {
        if (rsAttendance) {
            const udpateAttendanceTable = async () => {
                const attendanceList = await convertAttendanceToLocalTimezone(rsAttendance);
                setData(attendanceList);
            };
            udpateAttendanceTable();
        }
    }, [rsAttendance]);

    const clearFilters = () => {
        setStartDate(null);
        setEndDate(null);
        setFocused(null);
        setStartTime(moment().startOf('day'));
        setEndTime(moment().endOf('day'));
        setLocation(null);
        setJob(null);
    }

    const onDatesChange = ({ startDate, endDate }) => {
        setStartDate(startDate);
        setEndDate(endDate);
    }

    const onTimesChange = ({ start, end }) => {
            setStartTime(start);
            setEndTime(end);
    }

    const onLocationChange = (option) => {
        setLocation(option);
        // dispatch get company members with location param 
        // ...
    }

    const onJobChange = (option) => {
        setJob(option);
    }

    return (
        <Container>
            <Row className='mb-4'>
                <Col>
                    <PageTitle
                        icon={<i className='bx bx-paper-plane' ></i>}
                        title='Activity'
                        description='Keep track of your employee activities at a glance.'
                    />
                </Col>
            </Row>

            {rsAttendance &&
                <Row>
                    <Col sm={12} xl={3} className='mb-3'>
                        <Col className='segment filter-bar'>
                            <Row className='title'>
                                <Col className='d-flex align-items-center'>
                                    <i className='bx bx-slider-alt mr-2'></i>
                                    <p className='mb-0 font-weight-bold'>Filter</p>
                                </Col>
                                <Col className='col-auto'>
                                    <Button
                                        variant='link'
                                        size='sm'
                                        className='text-muted'
                                        onClick={clearFilters}
                                    >Clear all</Button>
                                </Col>
                            </Row>
                            <Row>
                                <Col>
                                    <Form.Group controlId='search'>
                                        <SearchBar
                                            searchItem={data}
                                            searchQuery={['email', 'role']}
                                            onSearch={({ filteredItems }) => setData(filteredItems)}
                                        />
                                    </Form.Group>
                                    <Form.Row>
                                        <Form.Group as={Col} sm={4} xl={12}>
                                            <Form.Label>Date</Form.Label>
                                            <div className={`custom-daterange-picker ${focused ? 'focused' : ''}`}>
                                                <Form.Control
                                                    as={DateRangePicker}
                                                    startDate={startDate}
                                                    startDateId='startDate'
                                                    endDate={endDate}
                                                    endDateId='endDate'
                                                    onDatesChange={onDatesChange}
                                                    focusedInput={focused}
                                                    onFocusChange={focused => setFocused(focused)}
                                                    isOutsideRange={() => false}
                                                    displayFormat='DD/MM/YYYY'
                                                    showClearDates
                                                    customCloseIcon={<i className='bx bx-x' style=></i>}
                                                    customArrowIcon={'-'}
                                                    inputIconPosition='after'
                                                    readOnly={true}
                                                    hideKeyboardShortcutsPanel
                                                    block
                                                />
                                            </div>
                                        </Form.Group>
                                        <Form.Group as={Col} sm={4} xl={12} controlId='time'>
                                            <Form.Label>Time</Form.Label>
                                            <Form.Control
                                                as={DatetimeRangePicker}
                                                className={`time-range-picker ${timeFocus ? 'focus' : ''}`}
                                                startDate={startTime}
                                                endDate={endTime}
                                                dateFormat={false}
                                                timeFormat='hh:mm A'
                                                inputProps=
                                                onChange={onTimesChange}
                                                onFocus={() => setTimeFocus(true)}
                                                onBlur={() => setTimeFocus(false)}
                                            />
                                        </Form.Group>

                                        {/* <Form.Group as={Col} sm={4} xl={12} controlId='location'>
                                            <Form.Label>Location</Form.Label>
                                            <Select
                                                classNamePrefix='form-select'
                                                className='form-select-container'
                                                menuPlacement='auto'
                                                isClearable
                                                options={locationOptions}
                                                value={location}
                                                onChange={onLocationChange}
                                            />
                                        </Form.Group> */}
                                        <Form.Group as={Col} sm={4} xl={12} controlId='job'>
                                            <Form.Label>Job</Form.Label>
                                            <Select 
                                                classNamePrefix='form-select'
                                                className='form-select-container'
                                                menuPlacement='auto'
                                                isClearable
                                                options={jobOptions}
                                                value={job}
                                                onChange={onJobChange}
                                            />
                                        </Form.Group>
                                    </Form.Row>
                                </Col>
                            </Row>
                        </Col>
                    </Col>
                    <Col sm={12} xl={9} style=>
                        <ReactTable
                            className='table-detach'
                            columns={columns}
                            data={data}
                        />
                    </Col>
                </Row>
            }
        </Container>
    );
}



Aucun commentaire:

Enregistrer un commentaire