Skip to content

Commit

Permalink
Fix/handle schedule abscence (#460)
Browse files Browse the repository at this point in the history
* fix

* fixed

* screen

* fixed deps

* optimized
  • Loading branch information
EduardZaydler authored Nov 20, 2023
1 parent 9cdc596 commit 1022a61
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 135 deletions.
1 change: 1 addition & 0 deletions src/Components/ScheduleEdit/ScheduleEdit.less
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
}

.days {
width: min-content;
margin-bottom: 15px;
display: flex;
align-items: center;
Expand Down
245 changes: 124 additions & 121 deletions src/Components/ScheduleEdit/ScheduleEdit.tsx
Original file line number Diff line number Diff line change
@@ -1,138 +1,141 @@
import * as React from "react";
import React, { useState, useMemo, FC } from "react";
import { Input } from "@skbkontur/react-ui/components/Input";
import { Radio } from "@skbkontur/react-ui/components/Radio";
import { Checkbox } from "@skbkontur/react-ui/components/Checkbox";
import { Schedule } from "../../Domain/Schedule";
import HelpTooltip from "../HelpTooltip/HelpTooltip";
import classNames from "classnames/bind";
import { Schedule, defaultSchedule } from "../../Domain/Schedule";

import styles from "./ScheduleEdit.less";

const cn = classNames.bind(styles);

type Props = {
schedule: Schedule;
interface IProps {
schedule?: Schedule;
error?: boolean;
onBlur?: React.FocusEventHandler<HTMLDivElement>;
onChange: (schedule: Schedule) => void;
};
}

type State = {
allDay: boolean;
};
const ScheduleEdit: FC<IProps> = React.forwardRef<HTMLDivElement, IProps>(function ScheduleEdit(
{ schedule, error, onChange, onBlur },
validationRef
) {
const defaultSched = useMemo(() => defaultSchedule(schedule), [schedule]);
const [allDay, setAllDay] = useState(
defaultSched.startOffset === 0 && defaultSched.endOffset === 1439
);

export default class ScheduleEdit extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
const { schedule } = props;
const { startOffset, endOffset } = schedule;
this.state = {
allDay: startOffset === 0 && endOffset === 1439,
};
}
const time = useMemo(() => {
const start = formatTime(defaultSched.startOffset);
const end = formatTime(defaultSched.endOffset);
return { start, end };
}, []);

return (
<>
<div onFocus={onBlur} ref={validationRef} className={cn("days")}>
{defaultSched.days.map(({ name, enabled }, i) => (
<Checkbox
error={error}
key={name}
checked={enabled}
onValueChange={(checked) =>
onChange({
...defaultSched,
days: [
...defaultSched.days.slice(0, i),
{ name, enabled: checked },
...defaultSched.days.slice(i + 1),
],
})
}
>
{name}
</Checkbox>
))}
</div>

static formatTime(time: number): string {
const HOUR_IN_DAY = 24;
const MIN_IN_HOUR = 60;
const hours =
Math.floor(time / MIN_IN_HOUR) < HOUR_IN_DAY ? Math.floor(time / MIN_IN_HOUR) : 0;
const minutes = time % MIN_IN_HOUR < MIN_IN_HOUR ? time % MIN_IN_HOUR : 0;
return `${hours > 9 ? hours : `0${hours}`}:${minutes > 9 ? minutes : `0${minutes}`}`;
}
<div className={cn("group")}>
<span className={cn("radio")}>
<Radio
checked={allDay}
onValueChange={() => {
onChange({
...defaultSched,
startOffset: 0,
endOffset: 1439,
});
setAllDay(true);
}}
value="all_day"
>
All day
</Radio>
</span>
<span className={cn("radio")}>
<Radio
checked={!allDay}
value="specific_interval"
onValueChange={() => setAllDay(false)}
>
At specific interval
</Radio>
<Input
value={time.start}
width={60}
mask="99:99"
disabled={allDay}
onValueChange={(value) =>
onChange({
...defaultSched,
startOffset: parseTime(value),
})
}
/>
<span></span>
<Input
value={time.end}
width={60}
mask="99:99"
disabled={allDay}
onValueChange={(value) =>
onChange({
...defaultSched,
endOffset: parseTime(value),
})
}
/>
<HelpTooltip>
<div className={cn("time-range-description-title")}>
Either negative and positive intervals are allowed.
</div>
<div>
For example: 23:00 - 06:00 specifies interval between 23:00 <br />
of the current day to the 06:00 of the next day.
</div>
</HelpTooltip>
</span>
</div>
</>
);
});

static parseTime(time: string): number {
const HOUR_IN_DAY = 24;
const MIN_IN_HOUR = 60;
const [hours, minutes] = time.split(":");
const parsedHours = parseInt(hours, 10) < HOUR_IN_DAY ? parseInt(hours, 10) : 0;
const parsedMinutes = parseInt(minutes, 10) < MIN_IN_HOUR ? parseInt(minutes, 10) : 0;
return parsedHours * MIN_IN_HOUR + parsedMinutes;
}
const formatTime = (time: number): string => {
const HOUR_IN_DAY = 24;
const MIN_IN_HOUR = 60;
const hours = Math.floor(time / MIN_IN_HOUR) < HOUR_IN_DAY ? Math.floor(time / MIN_IN_HOUR) : 0;
const minutes = time % MIN_IN_HOUR < MIN_IN_HOUR ? time % MIN_IN_HOUR : 0;
return `${hours > 9 ? hours : `0${hours}`}:${minutes > 9 ? minutes : `0${minutes}`}`;
};

render(): React.ReactNode {
const { allDay } = this.state;
const { schedule, onChange } = this.props;
const { days, startOffset, endOffset } = schedule;
const parseTime = (time: string): number => {
const HOUR_IN_DAY = 24;
const MIN_IN_HOUR = 60;
const [hours, minutes] = time.split(":");
const parsedHours = parseInt(hours, 10) < HOUR_IN_DAY ? parseInt(hours, 10) : 0;
const parsedMinutes = parseInt(minutes, 10) < MIN_IN_HOUR ? parseInt(minutes, 10) : 0;
return parsedHours * MIN_IN_HOUR + parsedMinutes;
};

return (
<div>
<div className={cn("days")}>
{days.map(({ name, enabled }, i) => (
<Checkbox
key={name}
checked={enabled}
onValueChange={(checked) =>
onChange({
...schedule,
days: [
...days.slice(0, i),
{ name, enabled: checked },
...days.slice(i + 1),
],
})
}
>
{name}
</Checkbox>
))}
</div>
<div className={cn("group")}>
<span className={cn("radio")}>
<Radio
checked={allDay}
onValueChange={() => {
onChange({
...schedule,
startOffset: 0,
endOffset: 1439,
});
this.setState({ allDay: true });
}}
value="all_day"
>
All day
</Radio>
</span>
<span className={cn("radio")}>
<Radio
checked={!allDay}
value="specific_interval"
onValueChange={() => this.setState({ allDay: false })}
>
At specific interval
</Radio>
<Input
value={ScheduleEdit.formatTime(startOffset)}
width={60}
mask="99:99"
disabled={allDay}
onValueChange={(value) =>
onChange({
...schedule,
startOffset: ScheduleEdit.parseTime(value),
})
}
/>
<span></span>
<Input
value={ScheduleEdit.formatTime(endOffset)}
width={60}
mask="99:99"
disabled={allDay}
onValueChange={(value) =>
onChange({ ...schedule, endOffset: ScheduleEdit.parseTime(value) })
}
/>
<HelpTooltip>
<div className={cn("time-range-description-title")}>
Either negative and positive intervals are allowed.
</div>
<div>
For example: 23:00 - 06:00 specifies interval between 23:00 <br />
of the current day to the 06:00 of the next day.
</div>
</HelpTooltip>
</span>
</div>
</div>
);
}
}
export default ScheduleEdit;
5 changes: 3 additions & 2 deletions src/Components/TagDropdownSelect/TagDropdownSelect.less
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@
}

&.error {
border-color: #da0902;
box-shadow: 0 0 0 1px #da0902;
border-color: #dd473b;
box-shadow: 0 0 0 1px #dd473b;
}
}

.input-area-disabled{
.input-area;
background-color: #F2F2F2;
Expand Down
4 changes: 3 additions & 1 deletion src/Components/TagDropdownSelect/TagDropdownSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type Props = {
onChange: (tagList: Array<string>) => void;
availableTags: Array<string>;
error?: boolean;
onBlur?: React.FocusEventHandler<HTMLDivElement>;
isDisabled?: boolean;
width: number;
allowCreateNewTags?: boolean;
Expand Down Expand Up @@ -48,7 +49,7 @@ export default class TagDropdownSelect extends React.Component<Props, State> {
}

render(): React.ReactElement {
const { width, value, availableTags, allowCreateNewTags } = this.props;
const { width, value, availableTags, allowCreateNewTags, onBlur } = this.props;
const { inputValue, focusedIndex, isFocused: opened } = this.state;
const filtredTags = this.filterTags(difference(availableTags, value));

Expand All @@ -72,6 +73,7 @@ export default class TagDropdownSelect extends React.Component<Props, State> {
className={cn("tags-menu")}
style={{ width }}
ref={this.tagsRef}
onBlur={onBlur}
>
{filtredTags.length > 0 || allowCreateNewTags ? (
<div className={cn("tag-list")}>
Expand Down
19 changes: 10 additions & 9 deletions src/Components/TriggerEditForm/TriggerEditForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, FC } from "react";
import { ValidationWrapperV1, tooltip } from "@skbkontur/react-ui-validations";
import { validateRequiredString, validateTTL } from "./Validations/validations";
import { validateRequiredString, validateSched, validateTTL } from "./Validations/validations";
import { Remarkable } from "remarkable";
import { sanitize } from "dompurify";
import { Checkbox, Input, Textarea, Tabs } from "@skbkontur/react-ui";
Expand Down Expand Up @@ -65,10 +65,6 @@ const TriggerEditForm: FC<IProps> = ({
alone_metrics: aloneMetrics,
} = data;

if (sched == null) {
throw new Error("InvalidProgramState");
}

const triggerModeEditorValue: ValueType = {
error_value: data.error_value ?? null,
warn_value: data.warn_value ?? null,
Expand Down Expand Up @@ -191,10 +187,15 @@ const TriggerEditForm: FC<IProps> = ({
</HelpTooltip>
</FormRow>
<FormRow label="Watch time">
<ScheduleEdit
schedule={sched}
onChange={(schedule) => onChange({ sched: schedule })}
/>
<ValidationWrapperV1
validationInfo={validateSched(sched)}
renderMessage={tooltip("top left")}
>
<ScheduleEdit
schedule={sched}
onChange={(schedule) => onChange({ sched: schedule })}
/>
</ValidationWrapperV1>
</FormRow>
<FormRow label="Tags" useTopAlignForLabel>
<ValidationWrapperV1
Expand Down
19 changes: 17 additions & 2 deletions src/Components/TriggerEditForm/Validations/validations.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { ValidationInfo } from "@skbkontur/react-ui-validations";
import { LOW_TRIGGER_TTL } from "../../../Domain/Trigger";
import { Schedule } from "../../../Domain/Schedule";

export const validateRequiredString = (
value: string,
message?: string
): ValidationInfo | null | undefined => {
return value.trim().length === 0
const isEmptyValue = !value.trim().length;
return isEmptyValue
? {
type: value.trim().length === 0 ? "submit" : "lostfocus",
type: isEmptyValue ? "submit" : "lostfocus",
message: message || "Can't be empty",
}
: null;
Expand Down Expand Up @@ -38,3 +40,16 @@ export const validateTTL = (value?: number): ValidationInfo | null => {

return null;
};

export const validateSched = (schedule: Schedule | undefined): ValidationInfo | null => {
if (!schedule) {
return null;
}

return schedule?.days.every((day) => !day.enabled)
? {
type: "submit",
message: "Schedule can't be empty",
}
: null;
};
11 changes: 11 additions & 0 deletions src/Domain/Schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,14 @@ export function createSchedule(days: DaysOfWeek[]): Schedule {
endOffset: 1439,
};
}

export const defaultSchedule = (schedule: Schedule | undefined) => {
return {
startOffset: schedule?.startOffset || 0,
endOffset: schedule?.endOffset || 1439,
tzOffset: schedule?.tzOffset || new Date().getTimezoneOffset(),
days:
schedule?.days ||
Object.values(DaysOfWeek).map((day) => ({ name: day, enabled: false })),
};
};
Loading

0 comments on commit 1022a61

Please sign in to comment.