Skip to content

Commit d55ea6d

Browse files
authored
feat(segment): Segment history (#43)
* feature: add segment history
1 parent 654783b commit d55ea6d

File tree

44 files changed

+1712
-604
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1712
-604
lines changed

src/components/FormItem/name.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SyntheticEvent } from 'react';
1+
import { ReactNode, SyntheticEvent } from 'react';
22
import { Form, FormInputProps, InputOnChangeData } from 'semantic-ui-react';
33
import { FormattedMessage, useIntl } from 'react-intl';
44
import { UseFormRegister, FieldValues, FieldErrors } from 'react-hook-form';
@@ -11,18 +11,19 @@ interface IProps extends FormInputProps {
1111
className?: string;
1212
register: UseFormRegister<FieldValues>;
1313
onChange(e: SyntheticEvent, detail: InputOnChangeData): void;
14+
labelRender?: ReactNode;
1415
}
1516

1617
const FormItemName = (props: IProps) => {
1718
const intl = useIntl();
18-
const { value, errors, size, className, register, onChange } = props;
19+
const { value, errors, size, className, register, onChange, labelRender } = props;
1920

2021
return (
2122
<div className={className}>
2223
<Form.Field>
2324
<label>
2425
<span className={styles['label-required']}>*</span>
25-
<FormattedMessage id='common.name.text' />
26+
{labelRender ?? <FormattedMessage id='common.name.text' />}
2627
</label>
2728
<Form.Input
2829
size={size}

src/pages/targeting/components/History/index.tsx renamed to src/components/History/index.tsx

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
21
import { SyntheticEvent } from 'react';
32
import { Dimmer, Loader, Popup } from 'semantic-ui-react';
43
import { FormattedMessage } from 'react-intl';
54
import InfiniteScroll from 'react-infinite-scroll-component';
5+
import TextLimit from 'components/TextLimit';
66
import classNames from 'classnames';
77
import dayjs from 'dayjs';
88
import { IVersion } from 'interfaces/targeting';
@@ -16,7 +16,6 @@ interface IProps {
1616
isHistoryLoading: boolean;
1717
loadMore(): void;
1818
reviewHistory(version: IVersion): void;
19-
setHistoryOpen(open: boolean): void;
2019
}
2120

2221
const History = (props: IProps) => {
@@ -50,7 +49,7 @@ const History = (props: IProps) => {
5049
}
5150
>
5251
{
53-
versions.length > 0 && versions.map((item: IVersion) => {
52+
versions.length > 0 && versions.map((item) => {
5453
const clsRight = classNames(
5554
styles['version-right'],
5655
{
@@ -130,7 +129,7 @@ const History = (props: IProps) => {
130129
/>
131130
</div>
132131
{
133-
item.approvalStatus === 'PASS' && (
132+
item.approvalStatus === 'PASS' && typeof item.approvalTime === 'string' && (
134133
<div className={styles.modifyBy}>
135134
<span className={styles['version-title']}>
136135
<FormattedMessage id='approvals.reviewed.by' />
@@ -180,23 +179,10 @@ const History = (props: IProps) => {
180179
{
181180
item.comment && <div className={styles.comment}>
182181
<span className={styles['version-title']}>
183-
<FormattedMessage id='targeting.publish.modal.comment' />:
182+
<FormattedMessage id='targeting.publish.modal.comment' />
184183
</span>
185184
:
186-
<Popup
187-
inverted
188-
style={{opacity: '0.8'}}
189-
className={styles.popup}
190-
trigger={
191-
<span className={styles['tooltip-text']}>{ item.comment }</span>
192-
}
193-
content={
194-
<div className={styles.tooltip}>
195-
{item.comment}
196-
</div>
197-
}
198-
position='top left'
199-
/>
185+
<span className={styles['tooltip-text']}><TextLimit text={ item.comment } /></span>
200186
</div>
201187
}
202188
</div>

src/components/Rule/index.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { useState, MouseEvent, useCallback } from 'react';
2-
import { Accordion, AccordionTitleProps } from 'semantic-ui-react';
1+
import { useState, useCallback } from 'react';
2+
import { Accordion } from 'semantic-ui-react';
33
import { Draggable } from 'react-beautiful-dnd';
44
import RuleTitle from './RuleTitle';
55
import RuleContent from './RuleContent';
@@ -19,6 +19,7 @@ interface IProps {
1919
ruleContainer: IContainer;
2020
hooksFormContainer: IContainer;
2121
segmentContainer?: IContainer;
22+
active?: boolean;
2223
}
2324

2425
const Rule = (props: IProps) => {
@@ -32,10 +33,14 @@ const Rule = (props: IProps) => {
3233
variationContainer,
3334
hooksFormContainer,
3435
segmentContainer,
36+
active,
3537
} = props;
3638

3739
const [ isHover, setHover ] = useState<boolean>(false);
38-
const [ active, setActive ] = useState<boolean>(false);
40+
41+
const {
42+
handleChangeActive,
43+
} = ruleContainer.useContainer();
3944

4045
const handleMouseEnter = useCallback(() => {
4146
setHover(true);
@@ -45,9 +50,9 @@ const Rule = (props: IProps) => {
4550
setHover(false);
4651
}, []);
4752

48-
const handleTitleClick = useCallback((e: MouseEvent, data: AccordionTitleProps) => {
49-
setActive(data.active || false);
50-
}, []);
53+
const handleTitleClick = useCallback(() => {
54+
handleChangeActive(index);
55+
}, [index]);
5156

5257
return (
5358
<Draggable draggableId={`rule_${rule.id}`} index={index} isDragDisabled={disabled}>
@@ -59,12 +64,12 @@ const Rule = (props: IProps) => {
5964
>
6065
<div className={styles.rule} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
6166
<Accordion
62-
defaultActiveIndex={0}
67+
activeIndex={active ? 0 : -1}
6368
panels={[{
6469
title: {
6570
className: styles['rule-accordion-title'],
6671
icon: (
67-
active
72+
!active
6873
? <Icon customClass={styles['icon-accordion']} type='angle-right' />
6974
: <Icon customClass={styles['icon-accordion']} type='angle-down' />
7075
),

src/components/Serve/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Icon from 'components/Icon';
66
import { IServe, IVariation } from 'interfaces/targeting';
77
import { IContainer } from 'interfaces/provider';
88
import { VariationColors } from 'constants/colors';
9+
import { useFormErrorScrollIntoView } from 'hooks';
910
import styles from './index.module.scss';
1011

1112
interface IAttr {
@@ -54,6 +55,7 @@ const Serve = (props: IProps) => {
5455
setError,
5556
clearErrors,
5657
} = hooksFormContainer.useContainer();
58+
const { registerErrorName } = useFormErrorScrollIntoView();
5759

5860
useEffect(() => {
5961
setVariationsInUse(cloneDeep(variations));
@@ -245,7 +247,7 @@ const Serve = (props: IProps) => {
245247
</div>
246248
{
247249
total !== TOTAL && (
248-
<div id={`rule_${id}_serve_total`} className={styles.message}>
250+
<div {...registerErrorName(id ? `rule_${id}_serve_total` : 'defaultServe_total')} className={styles.message}>
249251
<Icon customClass={styles['message-iconfont']} type='remove-circle' />
250252
{
251253
intl.formatMessage({

src/components/TextLimit/index.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,11 @@ const TextLimit: React.FC<IProps> = (props) => {
2727
return false;
2828
}
2929
}
30-
}, [ref.current]);
30+
}, [ref.current, text]);
3131

3232
useEffect(() => {
33-
if(ref.current) {
34-
if(ref.current.clientWidth === 0 && ref.current.clientHeight === 0) {
35-
setTimeout(() => {
36-
setIsLong(judgeLength());
37-
}, 500);
38-
} else {
39-
setIsLong(judgeLength());
40-
}
41-
}
42-
}, [ref.current]);
33+
setIsLong(judgeLength());
34+
}, [ref.current, text]);
4335

4436
return (
4537
<Popup

src/components/Variations/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Icon from 'components/Icon';
66
import uniqBy from 'lodash/uniqBy';
77
import { IVariation } from 'interfaces/targeting';
88
import { IContainer } from 'interfaces/provider';
9+
import { useFormErrorScrollIntoView } from 'hooks';
910
import styles from './index.module.scss';
1011
interface IProps {
1112
disabled?: boolean;
@@ -34,6 +35,8 @@ const Variations = (props: IProps) => {
3435
clearErrors,
3536
} = hooksFormContainer.useContainer();
3637

38+
const { registerErrorName } = useFormErrorScrollIntoView();
39+
3740
const handleDeleteVariation = useCallback((index: number, variationId?: string) => {
3841
unregister(`variation_${variationId}_name`);
3942
unregister(`variation_${variationId}`);
@@ -124,7 +127,7 @@ const Variations = (props: IProps) => {
124127
</Button>
125128
</div>
126129
{
127-
isError && <div className={styles['error-text']}>
130+
isError && <div {...registerErrorName('variations_duplicated')} className={styles['error-text']}>
128131
{
129132
intl.formatMessage({
130133
id: 'variations.duplicated.error.text'

src/constants/api/segment.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ const SegmentURI = {
55
segmentExistURI: `${origin}/projects/:projectKey/segments/exists`,
66
getSegmentURI: `${origin}/projects/:projectKey/segments/:segmentKey`,
77
getSegmentToggleURI: `${origin}/projects/:projectKey/segments/:segmentKey/toggles`,
8+
getSegmentVersionsURI: `${origin}/projects/:projectKey/segments/:segmentKey/versions`,
9+
publishSegmentURI: `${origin}/projects/:projectKey/segments/:segmentKey/publish`,
810
};
911

1012
export default SegmentURI;

src/hooks.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { useCallback, useRef } from 'react';
1+
import { useCallback, useEffect, useRef } from 'react';
2+
import { FieldErrors } from 'react-hook-form';
23
import { createContainer } from 'unstated-next';
34
import { useLocalStorage } from 'utils/hooks';
45

@@ -31,3 +32,42 @@ export const useRequestTimeCheck = () => {
3132

3233
return creatRequestTimeCheck;
3334
};
35+
36+
export const useFormErrorScrollIntoView = (errors?: FieldErrors) => {
37+
const errorRef = useRef(errors);
38+
const beforeScroll = useRef<(names: string[]) => void>();
39+
40+
useEffect(() => {
41+
errorRef.current = errors;
42+
}, [errors]);
43+
44+
const scrollToError = useCallback(() => {
45+
if(errorRef.current) {
46+
const names = Object.keys(errorRef.current);
47+
if(beforeScroll.current) {
48+
beforeScroll.current(names);
49+
}
50+
setTimeout(() => {
51+
document.querySelector(`[name=${names[0]}]`)?.scrollIntoView({ behavior: 'smooth', block: 'center' });
52+
});
53+
}
54+
}, [errorRef]);
55+
56+
const registerErrorName = useCallback((name: string) => {
57+
return {
58+
ref: (ref: unknown) => {
59+
(ref as HTMLElement)?.setAttribute('name', name);
60+
}
61+
};
62+
}, []);
63+
64+
const setBeforeScrollCallback = useCallback((fc: (names: string[]) => void) => {
65+
beforeScroll.current = fc;
66+
}, [beforeScroll]);
67+
68+
return {
69+
scrollToError,
70+
registerErrorName,
71+
setBeforeScrollCallback,
72+
};
73+
};

src/iconfont/iconfont.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
@font-face {
22
font-family: "iconfont"; /* Project id 3325204 */
3-
src: url('iconfont.woff2?t=1665652605834') format('woff2'),
4-
url('iconfont.woff?t=1665652605834') format('woff'),
5-
url('iconfont.ttf?t=1665652605834') format('truetype');
3+
src: url('iconfont.woff2?t=1666258537198') format('woff2'),
4+
url('iconfont.woff?t=1666258537198') format('woff'),
5+
url('iconfont.ttf?t=1666258537198') format('truetype');
66
}
77

88
.iconfont {

0 commit comments

Comments
 (0)