fix: Fix styles, refactoring

This commit is contained in:
Maksim Eltyshev
2024-08-12 23:17:17 +02:00
parent c594e8bd71
commit 12f05adde7
20 changed files with 229 additions and 202 deletions
+104 -72
View File
@@ -1,9 +1,10 @@
import upperFirst from 'lodash/upperFirst';
import React, { useCallback } from 'react';
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Checkbox } from 'semantic-ui-react';
import { Icon } from 'semantic-ui-react';
import { useForceUpdate } from '../../lib/hooks';
import getDateFormat from '../../utils/get-date-format';
@@ -15,6 +16,12 @@ const SIZES = {
MEDIUM: 'medium',
};
const STATUSES = {
DUE_SOON: 'dueSoon',
OVERDUE: 'overdue',
COMPLETED: 'completed',
};
const LONG_DATE_FORMAT_BY_SIZE = {
tiny: 'longDate',
small: 'longDate',
@@ -27,96 +34,121 @@ const FULL_DATE_FORMAT_BY_SIZE = {
medium: 'fullDateTime',
};
const getDueClass = (value) => {
const now = new Date();
const tomorrow = new Date(now).setDate(now.getDate() + 1);
const STATUS_ICON_PROPS_BY_STATUS = {
[STATUSES.DUE_SOON]: {
name: 'hourglass half',
color: 'orange',
},
[STATUSES.OVERDUE]: {
name: 'hourglass end',
color: 'red',
},
[STATUSES.COMPLETED]: {
name: 'checkmark',
color: 'green',
},
};
const getStatus = (dateTime, isCompleted) => {
if (isCompleted) {
return STATUSES.COMPLETED;
}
const secondsLeft = Math.floor((dateTime.getTime() - new Date().getTime()) / 1000);
if (secondsLeft <= 0) {
return STATUSES.OVERDUE;
}
if (secondsLeft <= 24 * 60 * 60) {
return STATUSES.DUE_SOON;
}
if (now > value) return styles.overdue;
if (tomorrow > value) return styles.soon;
return null;
};
const DueDate = React.memo(
({ value, completed, size, isDisabled, onClick, onUpdateCompletion }) => {
const [t] = useTranslation();
const DueDate = React.memo(({ value, size, isCompleted, isDisabled, withStatusIcon, onClick }) => {
const [t] = useTranslation();
const forceUpdate = useForceUpdate();
const dateFormat = getDateFormat(
value,
LONG_DATE_FORMAT_BY_SIZE[size],
FULL_DATE_FORMAT_BY_SIZE[size],
);
const statusRef = useRef(null);
statusRef.current = getStatus(value, isCompleted);
const classes = [
styles.wrapper,
styles[`wrapper${upperFirst(size)}`],
onClick && styles.wrapperHoverable,
completed ? styles.completed : getDueClass(value),
];
const intervalRef = useRef(null);
const handleToggleChange = useCallback(
(event) => {
event.preventDefault();
event.stopPropagation();
if (!isDisabled) onUpdateCompletion(!completed);
},
[onUpdateCompletion, completed, isDisabled],
);
const dateFormat = getDateFormat(
value,
LONG_DATE_FORMAT_BY_SIZE[size],
FULL_DATE_FORMAT_BY_SIZE[size],
);
return onClick ? (
<div className={styles.wrapperGroup}>
<button
type="button"
aria-label="Toggle completion"
className={classNames(...classes, styles.wrapperCheckbox)}
onClick={handleToggleChange}
>
<Checkbox
className={styles.checkbox}
checked={completed}
disabled={isDisabled}
onChange={handleToggleChange}
/>
</button>
<button
type="button"
disabled={isDisabled}
className={classNames(...classes, styles.wrapperButton)}
onClick={onClick}
>
<span>
{t(`format:${dateFormat}`, {
value,
postProcess: 'formatDate',
})}
</span>
</button>
</div>
) : (
<span className={classNames(...classes)}>
{t(`format:${dateFormat}`, {
value,
postProcess: 'formatDate',
})}
</span>
);
},
);
useEffect(() => {
if ([null, STATUSES.DUE_SOON].includes(statusRef.current)) {
intervalRef.current = setInterval(() => {
const status = getStatus(value, isCompleted);
if (status !== statusRef.current) {
forceUpdate();
}
if (status === STATUSES.OVERDUE) {
clearInterval(intervalRef.current);
}
}, 1000);
}
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [value, isCompleted, forceUpdate]);
const contentNode = (
<span
className={classNames(
styles.wrapper,
styles[`wrapper${upperFirst(size)}`],
!withStatusIcon && statusRef.current && styles[`wrapper${upperFirst(statusRef.current)}`],
onClick && styles.wrapperHoverable,
)}
>
{t(`format:${dateFormat}`, {
value,
postProcess: 'formatDate',
})}
{withStatusIcon && statusRef.current && (
// eslint-disable-next-line react/jsx-props-no-spreading
<Icon {...STATUS_ICON_PROPS_BY_STATUS[statusRef.current]} className={styles.statusIcon} />
)}
</span>
);
return onClick ? (
<button type="button" disabled={isDisabled} className={styles.button} onClick={onClick}>
{contentNode}
</button>
) : (
contentNode
);
});
DueDate.propTypes = {
value: PropTypes.instanceOf(Date).isRequired,
size: PropTypes.oneOf(Object.values(SIZES)),
isCompleted: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool,
completed: PropTypes.bool,
withStatusIcon: PropTypes.bool,
onClick: PropTypes.func,
onUpdateCompletion: PropTypes.func,
onCompletionToggle: PropTypes.func,
};
DueDate.defaultProps = {
size: SIZES.MEDIUM,
isDisabled: false,
completed: false,
withStatusIcon: false,
onClick: undefined,
onUpdateCompletion: undefined,
onCompletionToggle: undefined,
};
export default DueDate;