Skip to content

Commit

Permalink
fix(Estimate): resolve all conversations
Browse files Browse the repository at this point in the history
  • Loading branch information
DenisVorop committed Aug 29, 2023
1 parent 28c73a5 commit b752980
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 154 deletions.
133 changes: 65 additions & 68 deletions src/components/Estimate/Estimate.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { ReactNode, useState } from 'react';
import React, { ComponentProps, ReactNode, useMemo, useState } from 'react';

import { isPastDate, parseLocaleDate, quarterFromDate } from '../../utils/dateTime';
import { PopupProps } from '../OutlinePopup';
import { EstimateDate } from '../EstimateDate';
import { EstimateQuarter } from '../EstimateQuarter';
import { EstimateYear } from '../EstimateYear';
Expand All @@ -15,93 +14,91 @@ interface EstimateProps {
mask: string;
placeholder: string;
value?: EstimateType;
placement?: PopupProps['placement'];
placement?: ComponentProps<typeof EstimatePopup>['placement'];
error?: { message?: string };
renderTrigger: (values: { onClick: () => void }) => ReactNode;
onChange?: (value?: EstimateType) => void;
onClose?: () => void;
}

export const Estimate: React.FC<EstimateProps> = ({
renderTrigger,
onChange,
onClose,
value,
mask,
placeholder,
value,
placement,
error,
renderTrigger,
onChange,
onClose,
}) => {
const [readOnly, setReadOnly] = useState({ year: false, quarter: true, date: true });
const locale = useLocale();
const options = [
{
title: tr('Year title'),
clue: tr('Year clue'),
renderItem: (option: Option) => (
<EstimateYear
key={option.title}
option={option}
value={value}
readOnly={readOnly.year}
onChange={onChange}
setReadOnly={setReadOnly}
/>
),
},
{
title: tr('Quarter title'),
clue: `${tr('Quarter clue')} ${quarterFromDate(new Date())}.`,
renderItem: (option: Option) => (
<EstimateQuarter
key={option.title}
option={option}
value={value}
readOnly={readOnly.quarter}
onChange={onChange}
setReadOnly={setReadOnly}
/>
),
},
{
title: tr('Date title'),
clue: null,
renderItem: (option: Option) => (
<EstimateDate
key={option.title}
mask={mask}
placeholder={placeholder}
option={option}
value={value}
readOnly={readOnly.date}
onChange={onChange}
setReadOnly={setReadOnly}
/>
),
},
];

type ReduceOption = (typeof options)[number];
const optionsMap = options.reduce<Record<string, ReduceOption>>((acc, cur) => {
acc[cur.title] = cur;
return acc;
}, {});

const items = options.map(({ renderItem: _, ...rest }) => rest);
const options = useMemo(
() => [
{
title: tr('Year title'),
clue: tr('Year clue'),
renderItem: (option: Option) => (
<EstimateYear
key={option.title}
option={option}
value={value}
readOnly={readOnly.year}
onChange={onChange}
setReadOnly={setReadOnly}
/>
),
},
{
title: tr('Quarter title'),
clue: `${tr('Quarter clue')} ${quarterFromDate(new Date())}.`,
renderItem: (option: Option) => (
<EstimateQuarter
key={option.title}
option={option}
value={value}
readOnly={readOnly.quarter}
onChange={onChange}
setReadOnly={setReadOnly}
/>
),
},
{
title: tr('Date title'),
clue: null,
renderItem: (option: Option) => (
<EstimateDate
key={option.title}
mask={mask}
placeholder={placeholder}
option={option}
value={value}
readOnly={readOnly.date}
onChange={onChange}
setReadOnly={setReadOnly}
/>
),
},
],
[value, mask, placeholder, readOnly, onChange],
);

const warning =
(value && +value.y < new Date().getFullYear()) ||
(value?.date && isPastDate(parseLocaleDate(value.date, { locale })))
? { message: tr('Date is past') }
: undefined;
const warning = useMemo(
() =>
(value && +value.y < new Date().getFullYear()) ||
(value?.date && isPastDate(parseLocaleDate(value.date, { locale })))
? { message: tr('Date is past') }
: undefined,
[locale, value],
);

return (
<EstimatePopup
items={items}
items={options}
placement={placement}
error={error}
warning={warning}
renderItem={(option: Option) => optionsMap[option.title].renderItem(option)}
renderItem={({ renderItem, ...option }) => renderItem?.(option)}
renderTrigger={renderTrigger}
onClose={onClose}
/>
Expand Down
16 changes: 11 additions & 5 deletions src/components/EstimateDate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ interface EstimateDateProps {
>;
}

const expectedLength = 8;
const isDateFullyFilled = (date: string) => {
const cleanedDate = date.replace(/[^0-9]/g, '');
return cleanedDate.length === expectedLength;
};

export const EstimateDate: React.FC<EstimateDateProps> = ({
mask,
placeholder,
Expand All @@ -40,7 +46,7 @@ export const EstimateDate: React.FC<EstimateDateProps> = ({
const ref = useRef(null);

useClickOutside(ref, () => {
if (!fullDate.includes('_')) return;
if (isDateFullyFilled(fullDate)) return;

setFullDate(currentDate);
setReadOnly((prev) => {
Expand All @@ -53,7 +59,7 @@ export const EstimateDate: React.FC<EstimateDateProps> = ({
});

useEffect(() => {
if (fullDate.includes('_') || fullDate === currentDate) return;
if (!isDateFullyFilled(fullDate) || fullDate === currentDate) return;

const values = createValue(fullDate, locale);
onChange?.(values);
Expand All @@ -73,7 +79,7 @@ export const EstimateDate: React.FC<EstimateDateProps> = ({
(e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;

if (value.includes('_')) {
if (!isDateFullyFilled(value)) {
setFullDate(value);
return;
}
Expand Down Expand Up @@ -105,7 +111,7 @@ export const EstimateDate: React.FC<EstimateDateProps> = ({
onChange?.(value);
}, [currentDate, onChange, setReadOnly, value]);

const onClickIcon = useCallback(() => {
const onClick = useCallback(() => {
const estimate = onCreateEstimate(currentDate);

setReadOnly({
Expand All @@ -121,7 +127,7 @@ export const EstimateDate: React.FC<EstimateDateProps> = ({
<EstimateOption
title={option.title}
readOnly={readOnly}
onClickIcon={onClickIcon}
onClick={onClick}
renderTrigger={() => (
<InputMask mask={mask} placeholder={placeholder} onChange={onChangeDate} value={fullDate}>
{/* @ts-ignore incorrect type in react-input-mask */}
Expand Down
16 changes: 9 additions & 7 deletions src/components/EstimateOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,23 @@ const StyledContent = styled.div`
gap: ${gapS};
`;

const StyledIconPlusCircleSolid = styled(IconPlusCircleSolid)`
cursor: pointer;
`;

interface EstimateOptionProps {
title?: string;
clue?: string | null;
readOnly?: boolean;
onClickIcon?: () => void;
onClick?: () => void;
renderTrigger?: () => ReactNode;
}

export const EstimateOption: React.FC<EstimateOptionProps> = ({
title,
clue,
readOnly,
onClickIcon,
onClick: onClickIcon,
renderTrigger,
}) => {
const onClick = useCallback(() => {
Expand All @@ -39,18 +43,16 @@ export const EstimateOption: React.FC<EstimateOptionProps> = ({
<StyledWrapper>
<StyledContent>
{nullable(title, (t) => (
<Text weight="regular" size="s">
{t}
</Text>
<Text size="s">{t}</Text>
))}

{nullable(readOnly, () => (
<IconPlusCircleSolid size="xs" color={gray9} onClick={onClick} style={{ cursor: 'pointer' }} />
<StyledIconPlusCircleSolid size="xs" color={gray9} onClick={onClick} />
))}
</StyledContent>

{nullable(clue, (c) => (
<Text weight="regular" size="xxs" color={gray7}>
<Text size="xxs" color={gray7}>
{c}
</Text>
))}
Expand Down
24 changes: 15 additions & 9 deletions src/components/EstimatePopup.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { useClickOutside, Popup, nullable, Text } from '@taskany/bricks';
import { gapS, danger10, warn0, gapXs } from '@taskany/colors';
import { ReactNode, useRef, useState, useCallback } from 'react';
import { gapS, danger10, warn0, gapXs, radiusM } from '@taskany/colors';
import { ReactNode, useRef, useState, useCallback, ComponentProps } from 'react';
import styled from 'styled-components';
import { IconExclamationSmallOutline } from '@taskany/icons';

import { Option } from '../types/estimate';

import { PopupProps, OutlinePopup } from './OutlinePopup';

const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -40,12 +38,20 @@ const StyledWarningWrapper = styled.div`
gap: ${gapXs};
`;

const StyledPopup = styled(Popup)`
border-radius: ${radiusM};
padding: ${gapS};
`;

interface EstimateOption extends Option {
renderItem?: (option: Option) => ReactNode;
}
interface EstimatePopupProps {
renderItem: (option: Option) => ReactNode;
renderItem: (option: EstimateOption) => ReactNode;
renderTrigger: (values: { onClick: () => void }) => ReactNode;
onClose?: () => void;
items: Option[];
placement?: PopupProps['placement'];
items: EstimateOption[];
placement?: ComponentProps<typeof Popup>['placement'];
error?: { message?: string };
warning?: { message?: string };
}
Expand Down Expand Up @@ -93,7 +99,7 @@ export const EstimatePopup: React.FC<EstimatePopupProps> = ({

<div ref={triggerRef}>{renderTrigger({ onClick: onToggleVisible })}</div>

<OutlinePopup
<StyledPopup
visible={visible}
placement={placement}
reference={triggerRef}
Expand All @@ -114,7 +120,7 @@ export const EstimatePopup: React.FC<EstimatePopupProps> = ({
))}
{items.map((item) => renderItem?.(item))}
</StyledWrapper>
</OutlinePopup>
</StyledPopup>
</div>
);
};
4 changes: 2 additions & 2 deletions src/components/EstimateQuarter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const EstimateQuarter: React.FC<EstimateQuarterProps> = ({ option, value,
[locale, onChange, value],
);

const onClickIcon = useCallback(() => {
const onClick = useCallback(() => {
setReadOnly({
quarter: false,
year: false,
Expand All @@ -93,7 +93,7 @@ export const EstimateQuarter: React.FC<EstimateQuarterProps> = ({ option, value,
title={option.title}
clue={option.clue}
readOnly={readOnly}
onClickIcon={onClickIcon}
onClick={onClick}
renderTrigger={() => (
<StyledQuarters ref={ref}>
{quartersList.map((quarter) => {
Expand Down
Loading

0 comments on commit b752980

Please sign in to comment.