Skip to content

Commit

Permalink
Pull request 1907: 951-blocked-services-schedule-api
Browse files Browse the repository at this point in the history
Updates #951.

Squashed commit of the following:

commit 6b840fd
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Aug 29 19:53:03 2023 +0300

    client: imp docs more

commit 7fc8f03
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Aug 29 19:40:00 2023 +0300

    client: imp docs

commit 00bc14d
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Aug 28 18:43:49 2023 +0300

    try to fix lock file

commit d749df7
Merge: c69f923 e1f6229
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Aug 28 18:14:02 2023 +0300

    Merge branch 'master' into 951-blocked-services-schedule-api

commit c69f923
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Aug 28 17:16:20 2023 +0300

    revert eslintrc

commit b37916c
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Aug 28 12:02:39 2023 +0300

    fix translations

commit f5bb67d
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Aug 28 11:43:57 2023 +0300

    fix helpers

commit 13ec6a8
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Aug 28 11:24:57 2023 +0300

    remove todo

commit 23724ec
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Aug 28 09:56:56 2023 +0300

    add clients schedule form

commit 84d29e5
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Aug 25 17:44:40 2023 +0300

    fix schedule form

commit 83e4017
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Aug 18 12:58:16 2023 +0300

    remove unused

commit ef2b68e
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Aug 18 12:57:37 2023 +0300

    client: fix translation string

commit 32ea80c
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Aug 18 12:26:26 2023 +0300

    wip schedule

commit 9b77087
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jul 21 14:29:50 2023 +0300

    all: imp naming

commit ea4e951
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jul 19 18:09:27 2023 +0300

    all: imp code

commit 98a705b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jul 18 18:23:26 2023 +0300

    all: imp naming

commit 4f84b55
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jul 14 15:01:17 2023 +0300

    all: add global schedule api

commit 87cf164
Merge: cabb80a 2adc862
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jul 14 12:09:29 2023 +0300

    Merge branch 'master' into 951-blocked-services-schedule-api

commit cabb80a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 13 13:37:23 2023 +0300

    openapi: fix typo

commit 2279b03
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 13 12:26:19 2023 +0300

    all: imp docs

... and 3 more commits
  • Loading branch information
schzhn committed Aug 29, 2023
1 parent e1f6229 commit aac36a2
Show file tree
Hide file tree
Showing 54 changed files with 1,506 additions and 209 deletions.
15 changes: 14 additions & 1 deletion client/.eslintrc.json
Expand Up @@ -81,6 +81,19 @@
}
],
"import/prefer-default-export": "off",
"no-alert": "off"
"no-alert": "off",
"arrow-body-style": "off",
"max-len": [
"error",
120,
2,
{
"ignoreUrls": true,
"ignoreComments": false,
"ignoreRegExpLiterals": true,
"ignoreStrings": true,
"ignoreTemplateLiterals": true
}
]
}
}
2 changes: 1 addition & 1 deletion client/dev.eslintrc
@@ -1,6 +1,6 @@
{
"extends": ".eslintrc",
"rules": {
"no-debugger":"warn",
"no-debugger":"warn"
}
}
5 changes: 5 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Expand Up @@ -43,6 +43,7 @@
"redux-form": "^8.3.5",
"redux-thunk": "^2.3.0",
"string-length": "^5.0.1",
"timezones-list": "^3.0.2",
"url-polyfill": "^1.1.9"
},
"devDependencies": {
Expand Down
34 changes: 33 additions & 1 deletion client/src/__locales/en.json
Expand Up @@ -680,5 +680,37 @@
"protection_section_label": "Protection",
"log_and_stats_section_label": "Query log and statistics",
"ignore_query_log": "Ignore this client in query log",
"ignore_statistics": "Ignore this client in statistics"
"ignore_statistics": "Ignore this client in statistics",
"schedule_services": "Pause service blocking",
"schedule_services_desc": "Configure the pause schedule of the service-blocking filter",
"schedule_services_desc_client": "Configure the pause schedule of the service-blocking filter for this client",
"schedule_desc": "Set inactivity periods for blocked services",
"schedule_invalid_select": "Start time must be before end time",
"schedule_select_days": "Select days",
"schedule_timezone": "Select a time zone",
"schedule_current_timezone": "Current time zone: {{value}}",
"schedule_time_all_day": "All day",
"schedule_modal_description": "This schedule will replace any existing schedules for the same day of the week. Each day of the week can have only one inactivity period.",
"schedule_modal_time_off": "No service blocking:",
"schedule_new": "New schedule",
"schedule_edit": "Edit schedule",
"schedule_save": "Save schedule",
"schedule_add": "Add schedule",
"schedule_remove": "Remove schedule",
"schedule_from": "From",
"schedule_to": "To",
"sunday": "Sunday",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday_short": "Sun",
"monday_short": "Mon",
"tuesday_short": "Tue",
"wednesday_short": "Wed",
"thursday_short": "Thu",
"friday_short": "Fri",
"saturday_short": "Sat"
}
16 changes: 8 additions & 8 deletions client/src/actions/services.js
Expand Up @@ -32,19 +32,19 @@ export const getAllBlockedServices = () => async (dispatch) => {
}
};

export const setBlockedServicesRequest = createAction('SET_BLOCKED_SERVICES_REQUEST');
export const setBlockedServicesFailure = createAction('SET_BLOCKED_SERVICES_FAILURE');
export const setBlockedServicesSuccess = createAction('SET_BLOCKED_SERVICES_SUCCESS');
export const updateBlockedServicesRequest = createAction('UPDATE_BLOCKED_SERVICES_REQUEST');
export const updateBlockedServicesFailure = createAction('UPDATE_BLOCKED_SERVICES_FAILURE');
export const updateBlockedServicesSuccess = createAction('UPDATE_BLOCKED_SERVICES_SUCCESS');

export const setBlockedServices = (values) => async (dispatch) => {
dispatch(setBlockedServicesRequest());
export const updateBlockedServices = (values) => async (dispatch) => {
dispatch(updateBlockedServicesRequest());
try {
await apiClient.setBlockedServices(values);
dispatch(setBlockedServicesSuccess());
await apiClient.updateBlockedServices(values);
dispatch(updateBlockedServicesSuccess());
dispatch(getBlockedServices());
dispatch(addSuccessToast('blocked_services_saved'));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setBlockedServicesFailure());
dispatch(updateBlockedServicesFailure());
}
};
10 changes: 5 additions & 5 deletions client/src/api/Api.js
Expand Up @@ -489,9 +489,9 @@ class Api {
}

// Blocked services
BLOCKED_SERVICES_LIST = { path: 'blocked_services/list', method: 'GET' };
BLOCKED_SERVICES_GET = { path: 'blocked_services/get', method: 'GET' };

BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' };
BLOCKED_SERVICES_UPDATE = { path: 'blocked_services/update', method: 'PUT' };

BLOCKED_SERVICES_ALL = { path: 'blocked_services/all', method: 'GET' };

Expand All @@ -501,12 +501,12 @@ class Api {
}

getBlockedServices() {
const { path, method } = this.BLOCKED_SERVICES_LIST;
const { path, method } = this.BLOCKED_SERVICES_GET;
return this.makeRequest(path, method);
}

setBlockedServices(config) {
const { path, method } = this.BLOCKED_SERVICES_SET;
updateBlockedServices(config) {
const { path, method } = this.BLOCKED_SERVICES_UPDATE;
const parameters = {
data: config,
};
Expand Down
220 changes: 220 additions & 0 deletions client/src/components/Filters/Services/ScheduleForm/Modal.js
@@ -0,0 +1,220 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import ReactModal from 'react-modal';

import { Timezone } from './Timezone';
import { TimeSelect } from './TimeSelect';
import { TimePeriod } from './TimePeriod';
import { getFullDayName, getShortDayName } from './helpers';
import { LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants';

export const DAYS_OF_WEEK = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];

const INITIAL_START_TIME_MS = 0;
const INITIAL_END_TIME_MS = 86340000;

export const Modal = ({
isOpen,
currentDay,
schedule,
onClose,
onSubmit,
}) => {
const [t] = useTranslation();

const intialTimezone = schedule.time_zone === LOCAL_TIMEZONE_VALUE
? Intl.DateTimeFormat().resolvedOptions().timeZone
: schedule.time_zone;

const [timezone, setTimezone] = useState(intialTimezone);
const [days, setDays] = useState(new Set());

const [startTime, setStartTime] = useState(INITIAL_START_TIME_MS);
const [endTime, setEndTime] = useState(INITIAL_END_TIME_MS);

const [wrongPeriod, setWrongPeriod] = useState(true);

useEffect(() => {
if (currentDay) {
const newDays = new Set([currentDay]);
setDays(newDays);

setStartTime(schedule[currentDay].start);
setEndTime(schedule[currentDay].end);
}
}, [currentDay]);

useEffect(() => {
if (startTime >= endTime) {
setWrongPeriod(true);
} else {
setWrongPeriod(false);
}
}, [startTime, endTime]);

const addDays = (day) => {
const newDays = new Set(days);

if (newDays.has(day)) {
newDays.delete(day);
} else {
newDays.add(day);
}

setDays(newDays);
};

const activeDay = (day) => {
return days.has(day);
};

const onFormSubmit = (e) => {
e.preventDefault();

if (currentDay) {
const newSchedule = schedule;

Array.from(days).forEach((day) => {
newSchedule[day] = {
start: startTime,
end: endTime,
};
});

onSubmit(newSchedule);
} else {
const newSchedule = {
time_zone: timezone,
};

Array.from(days).forEach((day) => {
newSchedule[day] = {
start: startTime,
end: endTime,
};
});

onSubmit(newSchedule);
}
};

return (
<ReactModal
className="Modal__Bootstrap modal-dialog modal-dialog-centered modal-dialog--schedule"
closeTimeoutMS={0}
isOpen={isOpen}
onRequestClose={onClose}
>
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">
{currentDay ? t('schedule_edit') : t('schedule_new')}
</h4>
<button type="button" className="close" onClick={onClose}>
<span className="sr-only">Close</span>
</button>
</div>
<form onSubmit={onFormSubmit}>
<div className="modal-body">
<Timezone
timezone={timezone}
setTimezone={setTimezone}
/>

<div className="schedule__days">
{DAYS_OF_WEEK.map((day) => (
<button
type="button"
key={day}
className="btn schedule__button-day"
data-active={activeDay(day)}
onClick={() => addDays(day)}
>
{getShortDayName(t, day)}
</button>
)) }
</div>

<div className="schedule__time-wrap">
<div className="schedule__time-row">
<TimeSelect
value={startTime}
onChange={(v) => setStartTime(v)}
/>

<TimeSelect
value={endTime}
onChange={(v) => setEndTime(v)}
/>
</div>

{wrongPeriod && (
<div className="schedule__error">
{t('schedule_invalid_select')}
</div>
)}
</div>

<div className="schedule__info">
<div className="schedule__info-title">
{t('schedule_modal_time_off')}
</div>
<div className="schedule__info-row">
<svg className="icons schedule__info-icon">
<use xlinkHref="#calendar" />
</svg>
{days.size ? (
Array.from(days).map((day) => getFullDayName(t, day)).join(', ')
) : (
<span>
</span>
)}
</div>
<div className="schedule__info-row">
<svg className="icons schedule__info-icon">
<use xlinkHref="#watch" />
</svg>
{wrongPeriod ? (
<span>
</span>
) : (
<TimePeriod
startTimeMs={startTime}
endTimeMs={endTime}
/>
)}
</div>
</div>

<div className="schedule__notice">
{t('schedule_modal_description')}
</div>
</div>
<div className="modal-footer">
<div className="btn-list">
<button
type="button"
className="btn btn-success btn-standard"
disabled={days.size === 0 || wrongPeriod}
onClick={onFormSubmit}
>
{currentDay ? t('schedule_save') : t('schedule_add')}
</button>
</div>
</div>
</form>
</div>
</ReactModal>
);
};

Modal.propTypes = {
schedule: PropTypes.object.isRequired,
currentDay: PropTypes.string,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
};
25 changes: 25 additions & 0 deletions client/src/components/Filters/Services/ScheduleForm/TimePeriod.js
@@ -0,0 +1,25 @@
import React from 'react';
import PropTypes from 'prop-types';

import { getTimeFromMs } from './helpers';

export const TimePeriod = ({
startTimeMs,
endTimeMs,
}) => {
const startTime = getTimeFromMs(startTimeMs);
const endTime = getTimeFromMs(endTimeMs);

return (
<div className="schedule__time">
<time>{startTime.hours}:{startTime.minutes}</time>
&nbsp;–&nbsp;
<time>{endTime.hours}:{endTime.minutes}</time>
</div>
);
};

TimePeriod.propTypes = {
startTimeMs: PropTypes.number.isRequired,
endTimeMs: PropTypes.number.isRequired,
};

0 comments on commit aac36a2

Please sign in to comment.