From 4ce2c85c98d5380b037e0ade6bbf5cdb828b562a Mon Sep 17 00:00:00 2001 From: notSAINT <60464016+not-SAINT@users.noreply.github.com> Date: Mon, 16 Nov 2020 18:03:02 +0300 Subject: [PATCH 01/68] feat: add new schedule table (#13) * feat: add new schedule table * chore: refactor cells renderers * chore: refactor renderers --- client/src/components/Schedule/TableView.tsx | 102 ++++++++++++++ client/src/components/Schedule/model.ts | 47 +++++++ client/src/components/Table/renderers.tsx | 57 +++++++- client/src/pages/course/schedule.tsx | 141 +------------------ client/src/services/course.ts | 2 + 5 files changed, 208 insertions(+), 141 deletions(-) create mode 100644 client/src/components/Schedule/TableView.tsx create mode 100644 client/src/components/Schedule/model.ts diff --git a/client/src/components/Schedule/TableView.tsx b/client/src/components/Schedule/TableView.tsx new file mode 100644 index 0000000000..e3ccf1298b --- /dev/null +++ b/client/src/components/Schedule/TableView.tsx @@ -0,0 +1,102 @@ +import { SettingOutlined } from '@ant-design/icons'; +import { Table, Typography } from 'antd'; +import { ColumnsType } from 'antd/lib/table'; +import { GithubUserLink } from 'components'; +import { + dateSorter, + getColumnSearchProps, + tagsRenderer, + dateWithTimeZoneRenderer, + urlRenderer, + placeRenderer, + renderTag, +} from 'components/Table'; +import { CourseEvent } from 'services/course'; +import { TaskTypes } from './model'; +import { EventTypeColor, EventTypeToName } from 'components/Schedule/model'; + +const { Text } = Typography; + +type Props = { + data: CourseEvent[]; + timeZone: string; +}; + +const getColumns = (timeZone: string) => + [ + { + title: , + width: 20, + dataIndex: '#', + render: (_text: string, _record: CourseEvent, index: number) => index + 1, + }, + { + title: 'Date', + width: 120, + dataIndex: 'dateTime', + render: dateWithTimeZoneRenderer(timeZone, 'MMM Do YYYY'), + sorter: dateSorter('dateTime'), + sortDirections: ['descend', 'ascend'], + defaultSortOrder: 'descend', + }, + { title: 'Time', width: 60, dataIndex: 'dateTime', render: dateWithTimeZoneRenderer(timeZone, 'HH:mm') }, + { + title: 'Type', + width: 120, + dataIndex: ['event', 'type'], + render: (value: keyof typeof EventTypeColor) => renderTag(EventTypeToName[value] || value, EventTypeColor[value]), + }, + { + title: 'Special', + width: 150, + dataIndex: 'special', + render: (tags: string) => !!tags && tagsRenderer(tags.split(',')), + }, + { + title: 'Name', + dataIndex: ['event', 'name'], + render: (value: string) => {value}, + ...getColumnSearchProps('event.name'), + }, + { + title: 'Url', + width: 30, + dataIndex: ['event', 'descriptionUrl'], + render: urlRenderer, + }, + { title: 'Duration', width: 60, dataIndex: 'duration' }, + { + title: 'Organizer', + width: 140, + dataIndex: ['organizer', 'githubId'], + render: (value: string) => !!value && , + ...getColumnSearchProps('organizer.githubId'), + }, + { + title: 'Place', + dataIndex: 'place', + render: placeRenderer, + onCell: () => { + return { + style: { + whiteSpace: 'nowrap', + maxWidth: 250, + }, + }; + }, + }, + ] as ColumnsType; + +export function TableView({ data, timeZone }: Props) { + return ( + (record.event.type === TaskTypes.deadline ? `${record.id}d` : record.id).toString()} + pagination={false} + dataSource={data} + size="middle" + columns={getColumns(timeZone)} + /> + ); +} + +export default TableView; diff --git a/client/src/components/Schedule/model.ts b/client/src/components/Schedule/model.ts new file mode 100644 index 0000000000..7ae7fb7e2b --- /dev/null +++ b/client/src/components/Schedule/model.ts @@ -0,0 +1,47 @@ +export enum EventTypeColor { + deadline = 'red', + test = '#63ab91', + jstask = 'green', + htmltask = 'green', + selfeducation = 'green', + externaltask = 'green', + codewars = 'green', + codejam = 'green', + newtask = 'green', + lecture = 'blue', + lecture_online = 'blue', + lecture_offline = 'blue', + lecture_mixed = 'blue', + lecture_self_study = 'blue', + info = '#ff7b00', + warmup = '#63ab91', + meetup = '#bde04a', + workshop = '#bde04a', + interview = '#63ab91', +} + +export const TaskTypes = { + deadline: 'deadline', + test: 'test', + newtask: 'newtask', + lecture: 'lecture', +}; + +export const EventTypeToName: Record = { + lecture_online: 'online lecture', + lecture_offline: 'offline lecture', + lecture_mixed: 'mixed lecture', + lecture_self_study: 'self study', + warmup: 'warm-up', + jstask: 'js task', + kotlintask: 'kotlin task', + objctask: 'objc task', + htmltask: 'html task', + codejam: 'code jam', + externaltask: 'external task', + codewars: 'codewars', + selfeducation: 'self education', + // TODO: Left hardcoded (codewars:stage1|codewars:stage2) configs only for backward compatibility. Delete them in the future. + 'codewars:stage1': 'codewars', + 'codewars:stage2': 'codewars', +}; diff --git a/client/src/components/Table/renderers.tsx b/client/src/components/Table/renderers.tsx index 6303ddfe79..ec160b5141 100644 --- a/client/src/components/Table/renderers.tsx +++ b/client/src/components/Table/renderers.tsx @@ -1,6 +1,13 @@ -import moment from 'moment'; -import { CheckCircleFilled, MinusCircleOutlined } from '@ant-design/icons'; -import { Tag } from 'antd'; +import moment from 'moment-timezone'; +import { + CheckCircleFilled, + MinusCircleOutlined, + YoutubeOutlined, + ChromeOutlined, + GithubOutlined, + QuestionCircleOutlined, +} from '@ant-design/icons'; +import { Tag, Tooltip } from 'antd'; export function dateRenderer(value: string | null) { return value ? moment(value).format('YYYY-MM-DD') : ''; @@ -18,6 +25,9 @@ export function shortDateTimeRenderer(value: string) { return value ? moment(value).format('DD.MM HH:mm') : ''; } +export const dateWithTimeZoneRenderer = (timeZone: string, format: string) => (value: string) => + value ? moment(value, 'YYYY-MM-DD HH:mmZ').tz(timeZone).format(format) : ''; + export function boolRenderer(value: string) { return value != null ? value.toString() : ''; } @@ -41,7 +51,7 @@ export function tagsRenderer(values: (number | string)[]) { return {values.map(v => renderTag(v))}; } -function renderTag(value: number | string, color?: string) { +export function renderTag(value: number | string, color?: string) { return ( {value} @@ -57,3 +67,42 @@ export const idFromArrayRenderer = (data const item = data.find(d => d.id === value); return item ? item.name : '(Empty)'; }; + +const getUrlIcon = (url: string) => { + const lowerUrl = url.toLowerCase(); + const isGithubLink = lowerUrl.includes('github'); + const isYoutubeLink = lowerUrl.includes('youtube'); + const isYoutubeLink2 = lowerUrl.includes('youtu.be'); + + if (isGithubLink) { + return ; + } + + if (isYoutubeLink || isYoutubeLink2) { + return ; + } + + return ; +}; + +export const urlRenderer = (url: string) => + !!url && ( + + + {getUrlIcon(url)} + + + ); + +export const placeRenderer = (value: string) => { + return value === 'Youtube Live' ? ( +
+ {value}{' '} + + + +
+ ) : ( + {
{value}
}
+ ); +}; diff --git a/client/src/pages/course/schedule.tsx b/client/src/pages/course/schedule.tsx index a9f070d1b4..cc746721c1 100644 --- a/client/src/pages/course/schedule.tsx +++ b/client/src/pages/course/schedule.tsx @@ -1,38 +1,14 @@ -import { QuestionCircleOutlined, YoutubeOutlined } from '@ant-design/icons'; -import { Table, Tag, Row, Tooltip, Select } from 'antd'; -import { withSession, GithubUserLink, PageLayout } from 'components'; +import { Row, Select } from 'antd'; +import { withSession, PageLayout } from 'components'; +import TableView from 'components/Schedule/TableView'; import withCourseData from 'components/withCourseData'; import { useState, useMemo } from 'react'; import { CourseEvent, CourseService, CourseTaskDetails } from 'services/course'; import { CoursePageProps } from 'services/models'; -import css from 'styled-jsx/css'; -import moment from 'moment-timezone'; import { TIMEZONES } from '../../configs/timezones'; import { useAsync } from 'react-use'; import { useLoading } from 'components/useLoading'; -enum EventTypeColor { - deadline = 'red', - test = '#63ab91', - jstask = 'green', - htmltask = 'green', - selfeducation = 'green', - externaltask = 'green', - codewars = 'green', - codejam = 'green', - newtask = 'green', - lecture = 'blue', - lecture_online = 'blue', - lecture_offline = 'blue', - lecture_mixed = 'blue', - lecture_self_study = 'blue', - info = '#ff7b00', - warmup = '#63ab91', - meetup = '#bde04a', - workshop = '#bde04a', - interview = '#63ab91', -} - const TaskTypes = { deadline: 'deadline', test: 'test', @@ -40,28 +16,11 @@ const TaskTypes = { lecture: 'lecture', }; -const EventTypeToName: Record = { - lecture_online: 'online lecture', - lecture_offline: 'offline lecture', - lecture_mixed: 'mixed lecture', - lecture_self_study: 'self study', - warmup: 'warm-up', - jstask: 'js task', - kotlintask: 'kotlin task', - objctask: 'objc task', - htmltask: 'html task', - codejam: 'code jam', - externaltask: 'external task', - codewars: 'codewars', - selfeducation: 'self education', -}; - export function SchedulePage(props: CoursePageProps) { const [loading, withLoading] = useLoading(false); const [data, setData] = useState([]); const [timeZone, setTimeZone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone); const courseService = useMemo(() => new CourseService(props.course.id), [props.course.id]); - const startOfToday = moment().startOf('day'); useAsync( withLoading(async () => { @@ -91,97 +50,11 @@ export function SchedulePage(props: CoursePageProps) { ))} -
(record.event.type === TaskTypes.deadline ? `${record.id}d` : record.id).toString()} - pagination={false} - size="small" - dataSource={data} - rowClassName={record => (moment(record.dateTime).isBefore(startOfToday) ? 'rs-table-row-disabled' : '')} - columns={[ - { title: 'Date', width: 120, dataIndex: 'dateTime', render: dateRenderer(timeZone) }, - { title: 'Time', width: 60, dataIndex: 'dateTime', render: timeRenderer(timeZone) }, - { - title: 'Type', - width: 100, - dataIndex: ['event', 'type'], - render: (value: keyof typeof EventTypeColor) => ( - {EventTypeToName[value] || value} - ), - }, - { - title: 'Place', - dataIndex: 'place', - render: (value: string) => { - return value === 'Youtube Live' ? ( -
- {value}{' '} - - - -
- ) : ( - value - ); - }, - }, - { - title: 'Name', - dataIndex: ['event', 'name'], - render: (value: string, record) => { - return record.event.descriptionUrl ? ( - - {value} - - ) : ( - value - ); - }, - }, - { - title: 'Broadcast Url', - width: 140, - dataIndex: 'broadcastUrl', - render: (url: string) => - url ? ( - - Link - - ) : ( - '' - ), - }, - { - title: 'Organizer', - width: 140, - dataIndex: ['organizer', 'githubId'], - render: (value: string) => (value ? : ''), - }, - { - title: 'Details Url', - dataIndex: 'detailsUrl', - render: (url: string) => - url ? ( - - Details - - ) : ( - '' - ), - }, - { title: 'Comment', dataIndex: 'comment' }, - ]} - /> - + ); } -const dateRenderer = (timeZone: string) => (value: string) => - value ? moment(value, 'YYYY-MM-DD HH:mmZ').tz(timeZone).format('YYYY-MM-DD') : ''; - -const timeRenderer = (timeZone: string) => (value: string) => - value ? moment(value, 'YYYY-MM-DD HH:mmZ').tz(timeZone).format('HH:mm') : ''; - const tasksToEvents = (tasks: CourseTaskDetails[]) => { return tasks.reduce((acc: Array, task: CourseTaskDetails) => { if (task.type !== TaskTypes.test) { @@ -207,10 +80,4 @@ const createCourseEventFromTask = (task: CourseTaskDetails, type: string): Cours } as CourseEvent; }; -const styles = css` - :global(.rs-table-row-disabled) { - opacity: 0.5; - } -`; - export default withCourseData(withSession(SchedulePage)); diff --git a/client/src/services/course.ts b/client/src/services/course.ts index 3e70daa278..53b2184a43 100644 --- a/client/src/services/course.ts +++ b/client/src/services/course.ts @@ -70,6 +70,8 @@ export interface CourseEvent { organizer: UserBasic; detailsUrl: string; broadcastUrl: string; + special?: string; + duration?: number; } export interface CourseUser { From 05b1785fea9bd8318cd8d9b4e5d0cb3fb1fb980d Mon Sep 17 00:00:00 2001 From: Aleksandr Sharikov Date: Wed, 18 Nov 2020 21:04:10 +0300 Subject: [PATCH 02/68] feat: add switcher schedule view mode --- client/package-lock.json | 5 ++ client/package.json | 1 + .../src/components/Schedule/CalendarView.tsx | 17 +++++ client/src/components/Schedule/ListView.tsx | 17 +++++ client/src/components/Schedule/index.ts | 3 + client/src/components/Schedule/model.ts | 6 ++ client/src/pages/course/schedule.tsx | 72 ++++++++++++++----- 7 files changed, 104 insertions(+), 17 deletions(-) create mode 100644 client/src/components/Schedule/CalendarView.tsx create mode 100644 client/src/components/Schedule/ListView.tsx create mode 100644 client/src/components/Schedule/index.ts diff --git a/client/package-lock.json b/client/package-lock.json index a36281fc81..3f72e50924 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -12050,6 +12050,11 @@ "minimist": "^1.2.5" } }, + "mobile-device-detect": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/mobile-device-detect/-/mobile-device-detect-0.4.3.tgz", + "integrity": "sha512-SN9EBE9SoJgkb83kuUVoIp3R9OGYE5dYEnLEz2oLooh0DzgtQ72BJmpNGqrgFvmfE4iLR2CaVJ3RjUcStheVZg==" + }, "moment": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", diff --git a/client/package.json b/client/package.json index 71c4308ae7..979c6af802 100644 --- a/client/package.json +++ b/client/package.json @@ -25,6 +25,7 @@ "chart.js": "^2.9.3", "csvtojson": "~2.0.10", "lodash": "~4.17.19", + "mobile-device-detect": "^0.4.3", "moment": "~2.24.0", "moment-timezone": "~0.5.28", "next": "^9.5.4", diff --git a/client/src/components/Schedule/CalendarView.tsx b/client/src/components/Schedule/CalendarView.tsx new file mode 100644 index 0000000000..8b642aa872 --- /dev/null +++ b/client/src/components/Schedule/CalendarView.tsx @@ -0,0 +1,17 @@ +import { CourseEvent } from 'services/course'; + +type Props = { + data: CourseEvent[]; + timeZone: string; +}; + +export function CalendarView({ data, timeZone }: Props) { + return ( +
+

Calendar

+ Data length = {data.length}. Timezone = {timeZone} +
+ ); +} + +export default CalendarView; diff --git a/client/src/components/Schedule/ListView.tsx b/client/src/components/Schedule/ListView.tsx new file mode 100644 index 0000000000..429da6e9bd --- /dev/null +++ b/client/src/components/Schedule/ListView.tsx @@ -0,0 +1,17 @@ +import { CourseEvent } from 'services/course'; + +type Props = { + data: CourseEvent[]; + timeZone: string; +}; + +export function ListView({ data, timeZone }: Props) { + return ( +
+

List

+ Data length = {data.length}. Timezone = {timeZone} +
+ ); +} + +export default ListView; diff --git a/client/src/components/Schedule/index.ts b/client/src/components/Schedule/index.ts new file mode 100644 index 0000000000..842fe38b30 --- /dev/null +++ b/client/src/components/Schedule/index.ts @@ -0,0 +1,3 @@ +export { TableView } from './TableView'; +export { CalendarView } from './CalendarView'; +export { ListView } from './ListView'; diff --git a/client/src/components/Schedule/model.ts b/client/src/components/Schedule/model.ts index 7ae7fb7e2b..98ad473676 100644 --- a/client/src/components/Schedule/model.ts +++ b/client/src/components/Schedule/model.ts @@ -45,3 +45,9 @@ export const EventTypeToName: Record = { 'codewars:stage1': 'codewars', 'codewars:stage2': 'codewars', }; + +export enum ViewMode { + TABLE = 'TABLE', + LIST = 'LIST', + CALENDAR = 'CALENDAR', +} diff --git a/client/src/pages/course/schedule.tsx b/client/src/pages/course/schedule.tsx index cc746721c1..085689f21a 100644 --- a/client/src/pages/course/schedule.tsx +++ b/client/src/pages/course/schedule.tsx @@ -1,13 +1,18 @@ -import { Row, Select } from 'antd'; +import { Col, Row, Select } from 'antd'; import { withSession, PageLayout } from 'components'; -import TableView from 'components/Schedule/TableView'; +import { TableView, CalendarView, ListView } from 'components/Schedule'; import withCourseData from 'components/withCourseData'; import { useState, useMemo } from 'react'; import { CourseEvent, CourseService, CourseTaskDetails } from 'services/course'; import { CoursePageProps } from 'services/models'; import { TIMEZONES } from '../../configs/timezones'; -import { useAsync } from 'react-use'; +import { useAsync, useLocalStorage } from 'react-use'; import { useLoading } from 'components/useLoading'; +import { isMobileOnly } from 'mobile-device-detect'; +import { ViewMode } from 'components/Schedule/model'; + +const { Option } = Select; +const LOCAL_VIEW_MODE = 'scheduleViewMode'; const TaskTypes = { deadline: 'deadline', @@ -20,6 +25,7 @@ export function SchedulePage(props: CoursePageProps) { const [loading, withLoading] = useLoading(false); const [data, setData] = useState([]); const [timeZone, setTimeZone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone); + const [scheduleViewMode, setScheduleViewMode] = useLocalStorage(LOCAL_VIEW_MODE, getDefaultViewMode()); const courseService = useMemo(() => new CourseService(props.course.id), [props.course.id]); useAsync( @@ -34,23 +40,41 @@ export function SchedulePage(props: CoursePageProps) { [courseService], ); + const mapScheduleViewToComponent = { + [ViewMode.TABLE]: TableView, + [ViewMode.LIST]: ListView, + [ViewMode.CALENDAR]: CalendarView, + }; + + const viewMode = scheduleViewMode as ViewMode; + const ScheduleView = mapScheduleViewToComponent[viewMode] || TableView; + return ( - - + +
+ + + + + - + ); } @@ -80,4 +104,18 @@ const createCourseEventFromTask = (task: CourseTaskDetails, type: string): Cours } as CourseEvent; }; +const getDefaultViewMode = () => { + const localView = localStorage.getItem(LOCAL_VIEW_MODE); + + if (localView) { + return localView; + } + + if (isMobileOnly) { + return ViewMode.LIST; + } + + return ViewMode.TABLE; +}; + export default withCourseData(withSession(SchedulePage)); From d6f11a9d8ab11d5f3d7931e47a8aafdea93ee49d Mon Sep 17 00:00:00 2001 From: keksik77 Date: Wed, 2 Dec 2020 03:58:45 +0300 Subject: [PATCH 03/68] feat: implement desktop calendar --- client/package.json | 2 + .../src/components/Schedule/CalendarView.tsx | 2 + .../Schedule/CalendarView/Calendar.tsx | 19 +++++ .../components/DesktopCalendar.tsx | 84 +++++++++++++++++++ .../CalendarView/components/Modal.tsx | 52 ++++++++++++ .../Schedule/CalendarView/index.tsx | 1 + .../Schedule/CalendarView/utils/DataFuncs.ts | 16 ++++ .../Schedule/CalendarView/utils/DateFuncs.ts | 21 +++++ .../CalendarView/utils/useWindowDimensions.ts | 24 ++++++ client/src/components/Schedule/index.ts | 2 +- 10 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 client/src/components/Schedule/CalendarView/Calendar.tsx create mode 100644 client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx create mode 100644 client/src/components/Schedule/CalendarView/components/Modal.tsx create mode 100644 client/src/components/Schedule/CalendarView/index.tsx create mode 100644 client/src/components/Schedule/CalendarView/utils/DataFuncs.ts create mode 100644 client/src/components/Schedule/CalendarView/utils/DateFuncs.ts create mode 100644 client/src/components/Schedule/CalendarView/utils/useWindowDimensions.ts diff --git a/client/package.json b/client/package.json index 979c6af802..377525aac3 100644 --- a/client/package.json +++ b/client/package.json @@ -19,6 +19,7 @@ "coverage": "jest --coverage --config=./src/jest.config.js" }, "dependencies": { + "@types/react-custom-scrollbars": "^4.0.7", "algolia-places-react": "^1.5.1", "antd": "^4.6.6", "axios": "~0.20.0", @@ -33,6 +34,7 @@ "qs": "^6.9.4", "react": "~16.13.1", "react-chartjs-2": "^2.9.0", + "react-custom-scrollbars": "^4.2.1", "react-dom": "~16.13.1", "react-gauge-chart": "git+https://github.com/BossBele/react-gauge-chart.git", "react-masonry-css": "^1.0.14", diff --git a/client/src/components/Schedule/CalendarView.tsx b/client/src/components/Schedule/CalendarView.tsx index 8b642aa872..1956266cf9 100644 --- a/client/src/components/Schedule/CalendarView.tsx +++ b/client/src/components/Schedule/CalendarView.tsx @@ -5,7 +5,9 @@ type Props = { timeZone: string; }; + export function CalendarView({ data, timeZone }: Props) { + console.log(data); return (

Calendar

diff --git a/client/src/components/Schedule/CalendarView/Calendar.tsx b/client/src/components/Schedule/CalendarView/Calendar.tsx new file mode 100644 index 0000000000..9851a4fad9 --- /dev/null +++ b/client/src/components/Schedule/CalendarView/Calendar.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +//import MiniCalendar from './components/MobileCalendar'; +//import useWindowDimensions from './utils/useWindowDimensions'; +import DesktopCalendar from './components/DesktopCalendar'; +import { CourseEvent } from 'services/course'; + +type Props = { + data: CourseEvent[]; + timeZone: string; +}; + +export const CalendarView: React.FC = ({data, timeZone}) => { + //const { width } = useWindowDimensions(); + + return <> + + +}; + diff --git a/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx b/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx new file mode 100644 index 0000000000..96afa367b8 --- /dev/null +++ b/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx @@ -0,0 +1,84 @@ +import React, { useState } from 'react'; +import { Calendar, Badge } from 'antd'; +import { getMonthValue } from '../utils/DateFuncs'; +import { getListData } from '../utils/DataFuncs'; +import { Scrollbars } from 'react-custom-scrollbars'; +import { ModalWindow } from './Modal'; +import { CourseEvent } from 'services/course'; +import { Moment } from 'moment'; + +type Props = { + data: CourseEvent[]; + timeZone: string; +}; + + +const DesktopCalendar: React.FC = ({ data, timeZone }) => { + const [modalWindowData, setModalWindowData] = useState(data[0]); + const [showWindow, setShowWindow] = useState(false); + + const handleOnClose = () => { + setShowWindow(false); + }; + + function showModalWindow(id: number) { + setModalWindowData(() => { + setShowWindow(true); + return data.filter((event) => event.id === id)[0]; + }); + } + + const dateCellRender = (date: unknown | Moment) => { + return ( + +
    + {getListData(date as unknown as Moment, data, timeZone).map((coloredEvent) => { + return( +
  • showModalWindow(coloredEvent.key)} + > + +
  • + )})} +
+
+ ); + } + + const monthCellRender = (date: unknown | Moment) => { + const num = getMonthValue(date as unknown as Moment, data, timeZone); + + return num ? ( +
+ Number of events +
{num}
+
+ ) : null; + } + + + + return ( +
+ { modalWindowData && + + } + +
+ ); +}; + +export default DesktopCalendar; diff --git a/client/src/components/Schedule/CalendarView/components/Modal.tsx b/client/src/components/Schedule/CalendarView/components/Modal.tsx new file mode 100644 index 0000000000..686447c77e --- /dev/null +++ b/client/src/components/Schedule/CalendarView/components/Modal.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Modal} from 'antd'; +import moment from 'moment'; +import {GithubUserLink} from 'components'; +import { renderTag, urlRenderer } from 'components/Table/renderers'; +import { EventTypeColor, EventTypeToName } from 'components/Schedule/model'; +import { CourseEvent } from 'services/course'; + +type Props = { + isOpen: boolean; + dataEvent: CourseEvent; + handleOnClose: Function; + timeZone: string; +}; + +export function ModalWindow({ isOpen, dataEvent, handleOnClose, timeZone }: Props) { + + //!plug + const newTimeZone = timeZone === 'Europe/Yekaterinburg' ? 'Asia/Yekaterinburg' : timeZone; + + return ( + <> + handleOnClose()} + onCancel={() => handleOnClose()} + visible={isOpen} + > +
Date: {moment(dataEvent.dateTime, 'YYYY-MM-DD HH:mmZ').tz(newTimeZone).format('LLL')}
+ {dataEvent.event.descriptionUrl && +
Url: {urlRenderer(dataEvent.event.descriptionUrl)}
+ } + {dataEvent.event.description && +
{dataEvent.event.description}
+ } +
+ {renderTag(EventTypeToName[dataEvent.event.type] || dataEvent.event.type, EventTypeColor[dataEvent.event.type as keyof typeof EventTypeColor])} +
+ {!!dataEvent.organizer.githubId && +
Organizer:
+ } + +
+ + ); +} diff --git a/client/src/components/Schedule/CalendarView/index.tsx b/client/src/components/Schedule/CalendarView/index.tsx new file mode 100644 index 0000000000..181690fa1d --- /dev/null +++ b/client/src/components/Schedule/CalendarView/index.tsx @@ -0,0 +1 @@ +export {CalendarView} from './Calendar'; diff --git a/client/src/components/Schedule/CalendarView/utils/DataFuncs.ts b/client/src/components/Schedule/CalendarView/utils/DataFuncs.ts new file mode 100644 index 0000000000..21e4f55640 --- /dev/null +++ b/client/src/components/Schedule/CalendarView/utils/DataFuncs.ts @@ -0,0 +1,16 @@ +import { filterByDate } from './DateFuncs'; +import { CourseEvent } from 'services/course'; +import { EventTypeColor} from 'components/Schedule/model'; +import {Moment} from 'moment'; + + +export function getListData(calendarCellDate: Moment, data: CourseEvent[], timeZone: string) { + const listData: { color: string; name: string; key: number }[] = []; + + filterByDate(calendarCellDate, data, timeZone).forEach((el: CourseEvent) => { + const tagColor = EventTypeColor[el.event.type as keyof typeof EventTypeColor]; + listData.push({ color: tagColor ? tagColor : '#d9d9d9' , name: el.event.name, key: el.id }); + }); + + return listData; +} diff --git a/client/src/components/Schedule/CalendarView/utils/DateFuncs.ts b/client/src/components/Schedule/CalendarView/utils/DateFuncs.ts new file mode 100644 index 0000000000..6f60e3dba4 --- /dev/null +++ b/client/src/components/Schedule/CalendarView/utils/DateFuncs.ts @@ -0,0 +1,21 @@ +import moment, { Moment } from 'moment'; +import { CourseEvent } from 'services/course'; + + +export function filterByDate(calendarCellDate: Moment, data: CourseEvent[], timeZone: string) { + //plug! + const newTimeZone = timeZone === 'Europe/Yekaterinburg' ? 'Asia/Yekaterinburg' : timeZone; + + return data.filter( + (event) => calendarCellDate.format('YYYY-MM-DD') === moment(event.dateTime, 'YYYY-MM-DD HH:mmZ').tz(newTimeZone).format('YYYY-MM-DD'), + ); +} + +export function getMonthValue(calendarCellDate: Moment, data: CourseEvent[], timeZone: string) { + //plug! + const newTimeZone = timeZone === 'Europe/Yekaterinburg' ? 'Asia/Yekaterinburg' : timeZone; + + return data.filter( + (event) => calendarCellDate.format('MM-YYYY') === moment(event.dateTime, 'YYYY-MM-DD HH:mmZ').tz(newTimeZone).format('MM-YYYY'), + ).length; +} diff --git a/client/src/components/Schedule/CalendarView/utils/useWindowDimensions.ts b/client/src/components/Schedule/CalendarView/utils/useWindowDimensions.ts new file mode 100644 index 0000000000..9444027ba6 --- /dev/null +++ b/client/src/components/Schedule/CalendarView/utils/useWindowDimensions.ts @@ -0,0 +1,24 @@ +import { useState, useEffect } from 'react'; + +function getWindowDimensions() { + const { innerWidth: width, innerHeight: height } = window; + return { + width, + height, + }; +} + +export default function useWindowDimensions() { + const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); + + useEffect(() => { + function handleResize() { + setWindowDimensions(getWindowDimensions()); + } + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + return windowDimensions; +} diff --git a/client/src/components/Schedule/index.ts b/client/src/components/Schedule/index.ts index 842fe38b30..2addb63e9e 100644 --- a/client/src/components/Schedule/index.ts +++ b/client/src/components/Schedule/index.ts @@ -1,3 +1,3 @@ export { TableView } from './TableView'; -export { CalendarView } from './CalendarView'; +export { CalendarView } from './CalendarView/'; export { ListView } from './ListView'; From 3b18b11a280593ca077333157d6c105ec4a1daeb Mon Sep 17 00:00:00 2001 From: keksik77 Date: Wed, 2 Dec 2020 22:52:22 +0300 Subject: [PATCH 04/68] feat: implement mobile calendar --- .../Schedule/CalendarView/Calendar.tsx | 11 +- .../components/DesktopCalendar.tsx | 3 +- .../components/MobileCalendar.tsx | 121 ++++++++++++++++++ 3 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 client/src/components/Schedule/CalendarView/components/MobileCalendar.tsx diff --git a/client/src/components/Schedule/CalendarView/Calendar.tsx b/client/src/components/Schedule/CalendarView/Calendar.tsx index 9851a4fad9..b79cd85675 100644 --- a/client/src/components/Schedule/CalendarView/Calendar.tsx +++ b/client/src/components/Schedule/CalendarView/Calendar.tsx @@ -1,6 +1,6 @@ import React from 'react'; -//import MiniCalendar from './components/MobileCalendar'; -//import useWindowDimensions from './utils/useWindowDimensions'; +import MobileCalendar from './components/MobileCalendar'; +import useWindowDimensions from './utils/useWindowDimensions'; import DesktopCalendar from './components/DesktopCalendar'; import { CourseEvent } from 'services/course'; @@ -10,10 +10,13 @@ type Props = { }; export const CalendarView: React.FC = ({data, timeZone}) => { - //const { width } = useWindowDimensions(); + const { width } = useWindowDimensions(); return <> - + {width > 750 + ? + : + } }; diff --git a/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx b/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx index 96afa367b8..14f22628b9 100644 --- a/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx +++ b/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx @@ -6,6 +6,7 @@ import { Scrollbars } from 'react-custom-scrollbars'; import { ModalWindow } from './Modal'; import { CourseEvent } from 'services/course'; import { Moment } from 'moment'; +import style from 'styled-jsx'; type Props = { data: CourseEvent[]; @@ -14,7 +15,7 @@ type Props = { const DesktopCalendar: React.FC = ({ data, timeZone }) => { - const [modalWindowData, setModalWindowData] = useState(data[0]); + const [modalWindowData, setModalWindowData] = useState(null); const [showWindow, setShowWindow] = useState(false); const handleOnClose = () => { diff --git a/client/src/components/Schedule/CalendarView/components/MobileCalendar.tsx b/client/src/components/Schedule/CalendarView/components/MobileCalendar.tsx new file mode 100644 index 0000000000..b9397b5a62 --- /dev/null +++ b/client/src/components/Schedule/CalendarView/components/MobileCalendar.tsx @@ -0,0 +1,121 @@ +import React, { useState} from 'react'; +import { Calendar, Badge, List } from 'antd'; +import { getMonthValue } from '../utils/DateFuncs'; +import {ModalWindow} from './Modal'; +import { getListData } from '../utils/DataFuncs'; +import { CourseEvent } from 'services/course'; +import { Moment } from 'moment'; +import {dateWithTimeZoneRenderer} from 'components/Table'; + +type Props = { + data: CourseEvent[]; + timeZone: string; +}; + +const MobileCalendar: React.FC = ({ data, timeZone }) => { + const [modalWindowData, setModalWindowData] = useState<{color: string, name: string, key: number}[] | undefined>(); + const [currentItem, setCurrentItem] = useState(null); + const [showWindow, setShowWindow] = useState(false); + const [calendarMode, setCalendarMode] = useState('month'); + + const handleOnClose = () => { + setShowWindow(false); + }; + + function showModalWindow(id: number) { + setCurrentItem(() => { + setShowWindow(true); + return data.filter((event) => event.id === id)[0]; + }) + } + + function onSelect(date: unknown | Moment) { + if (calendarMode === 'month') { + setModalWindowData(getListData(date as unknown as Moment, data, timeZone)); + } + } + + function onPanelChange(_: any, mode: any) { + console.log(mode) + setCalendarMode(mode); + setModalWindowData([]); + } + + function dateCellRender(date: unknown | Moment) { + const numberEvents = getListData(date as unknown as Moment, data, timeZone).length; + return numberEvents > 0 ? ( + <> +
{numberEvents}
+ + + ) : null; + } + + const monthCellRender = (date: unknown | Moment) => { + const num = getMonthValue(date as unknown as Moment, data, timeZone); + return num ? ( + <> +
{num}
+ + + ) : null; + } + + return ( + <> + + { + const dateTime = data.filter((event) => event.id === item.key)[0].dateTime; + return ( + showModalWindow(item.key)}>more]} + > + } + /> +
Time: {dateWithTimeZoneRenderer(timeZone, 'h:mm')(dateTime)}
+
+ ) + }}/> + { currentItem && + + } + + ); +} + +export default MobileCalendar; From e28250841bf5b45ff0aa6bd73fbf6f972679b975 Mon Sep 17 00:00:00 2001 From: keksik77 Date: Wed, 2 Dec 2020 22:56:47 +0300 Subject: [PATCH 05/68] fix: remove console.log --- .../Schedule/CalendarView/components/DesktopCalendar.tsx | 1 - .../Schedule/CalendarView/components/MobileCalendar.tsx | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx b/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx index 14f22628b9..1c037cab92 100644 --- a/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx +++ b/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx @@ -6,7 +6,6 @@ import { Scrollbars } from 'react-custom-scrollbars'; import { ModalWindow } from './Modal'; import { CourseEvent } from 'services/course'; import { Moment } from 'moment'; -import style from 'styled-jsx'; type Props = { data: CourseEvent[]; diff --git a/client/src/components/Schedule/CalendarView/components/MobileCalendar.tsx b/client/src/components/Schedule/CalendarView/components/MobileCalendar.tsx index b9397b5a62..15bbfad86a 100644 --- a/client/src/components/Schedule/CalendarView/components/MobileCalendar.tsx +++ b/client/src/components/Schedule/CalendarView/components/MobileCalendar.tsx @@ -35,8 +35,7 @@ const MobileCalendar: React.FC = ({ data, timeZone }) => { } } - function onPanelChange(_: any, mode: any) { - console.log(mode) + function onPanelChange(_: any, mode: string) { setCalendarMode(mode); setModalWindowData([]); } From 46d337da8efc188e0dee6cb522558f752f76a3c8 Mon Sep 17 00:00:00 2001 From: keksik77 Date: Tue, 8 Dec 2020 03:17:28 +0300 Subject: [PATCH 06/68] refactor: refactoring the code according to yuliaHope requirements --- client/package-lock.json | 63 ++++++++++++++++++- .../src/components/Schedule/CalendarView.tsx | 19 ------ .../Schedule/CalendarView/Calendar.tsx | 9 ++- .../components/DesktopCalendar.tsx | 7 +-- .../components/MobileCalendar.tsx | 58 +++++++---------- .../CalendarView/components/Modal.tsx | 21 +++---- .../Schedule/CalendarView/utils/DataFuncs.ts | 16 ----- .../Schedule/CalendarView/utils/DateFuncs.ts | 21 ------- .../Schedule/CalendarView/utils/filters.ts | 22 +++++++ .../CalendarView/utils/useWindowDimensions.ts | 24 ------- client/src/configs/timezones.ts | 2 +- server/.env.example | 22 ------- 12 files changed, 124 insertions(+), 160 deletions(-) delete mode 100644 client/src/components/Schedule/CalendarView.tsx delete mode 100644 client/src/components/Schedule/CalendarView/utils/DataFuncs.ts delete mode 100644 client/src/components/Schedule/CalendarView/utils/DateFuncs.ts create mode 100644 client/src/components/Schedule/CalendarView/utils/filters.ts delete mode 100644 client/src/components/Schedule/CalendarView/utils/useWindowDimensions.ts delete mode 100644 server/.env.example diff --git a/client/package-lock.json b/client/package-lock.json index 3f72e50924..5d5e2913e8 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -4509,8 +4509,7 @@ "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", - "dev": true + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "@types/qs": { "version": "6.9.4", @@ -4522,12 +4521,19 @@ "version": "16.9.32", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.32.tgz", "integrity": "sha512-fmejdp0CTH00mOJmxUPPbWCEBWPvRIL4m8r0qD+BSDUqmutPyGQCHifzMpMzdvZwROdEdL78IuZItntFWgPXHQ==", - "dev": true, "requires": { "@types/prop-types": "*", "csstype": "^2.2.0" } }, + "@types/react-custom-scrollbars": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/react-custom-scrollbars/-/react-custom-scrollbars-4.0.7.tgz", + "integrity": "sha512-4QPZdwd+wmzWq9TyNSA/4MZFYvlQn1GlEFFkpFx8VSs13gR/L+hQne0vFnbzwlQmGG7OksthkoVpYxWJjzz95w==", + "requires": { + "@types/react": "*" + } + }, "@types/react-dom": { "version": "16.9.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.6.tgz", @@ -4801,6 +4807,11 @@ "object-assign": "4.x" } }, + "add-px-to-style": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/add-px-to-style/-/add-px-to-style-1.0.0.tgz", + "integrity": "sha1-0ME1RB+oAUqBN5BFMQlvZ/KPJjo=" + }, "adjust-sourcemap-loader": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-2.0.0.tgz", @@ -7048,6 +7059,16 @@ "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.0.tgz", "integrity": "sha512-YkoezQuhp3SLFGdOlr5xkqZ640iXrnHAwVYcDg8ZKRUtO7mSzSC2BA5V0VuyAwPSJA4CLIc6EDDJh4bEsD2+zA==" }, + "dom-css": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dom-css/-/dom-css-2.1.0.tgz", + "integrity": "sha1-/bwtWgFdCj4YcuEUcrvQ57nmogI=", + "requires": { + "add-px-to-style": "1.0.0", + "prefix-style": "2.0.1", + "to-camel-case": "1.0.0" + } + }, "dom-serializer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", @@ -13034,6 +13055,11 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" }, + "prefix-style": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/prefix-style/-/prefix-style-2.0.1.tgz", + "integrity": "sha1-ZrupqHDP2jCKXcIOhekSCTLJWgY=" + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -13946,6 +13972,16 @@ "prop-types": "^15.5.8" } }, + "react-custom-scrollbars": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz", + "integrity": "sha1-gw/ZUCkn6X6KeMIIaBOJmyqLZts=", + "requires": { + "dom-css": "^2.0.0", + "prop-types": "^15.5.10", + "raf": "^3.1.0" + } + }, "react-dom": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", @@ -15551,11 +15587,24 @@ "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" }, + "to-camel-case": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-camel-case/-/to-camel-case-1.0.0.tgz", + "integrity": "sha1-GlYFSy+daWKYzmamCJcyK29CPkY=", + "requires": { + "to-space-case": "^1.0.0" + } + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, + "to-no-case": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz", + "integrity": "sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo=" + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -15593,6 +15642,14 @@ "is-number": "^7.0.0" } }, + "to-space-case": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-space-case/-/to-space-case-1.0.0.tgz", + "integrity": "sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc=", + "requires": { + "to-no-case": "^1.0.0" + } + }, "toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", diff --git a/client/src/components/Schedule/CalendarView.tsx b/client/src/components/Schedule/CalendarView.tsx deleted file mode 100644 index 1956266cf9..0000000000 --- a/client/src/components/Schedule/CalendarView.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { CourseEvent } from 'services/course'; - -type Props = { - data: CourseEvent[]; - timeZone: string; -}; - - -export function CalendarView({ data, timeZone }: Props) { - console.log(data); - return ( -
-

Calendar

- Data length = {data.length}. Timezone = {timeZone} -
- ); -} - -export default CalendarView; diff --git a/client/src/components/Schedule/CalendarView/Calendar.tsx b/client/src/components/Schedule/CalendarView/Calendar.tsx index b79cd85675..cd53bb4585 100644 --- a/client/src/components/Schedule/CalendarView/Calendar.tsx +++ b/client/src/components/Schedule/CalendarView/Calendar.tsx @@ -1,8 +1,8 @@ import React from 'react'; import MobileCalendar from './components/MobileCalendar'; -import useWindowDimensions from './utils/useWindowDimensions'; import DesktopCalendar from './components/DesktopCalendar'; import { CourseEvent } from 'services/course'; +import { isMobile } from 'mobile-device-detect'; type Props = { data: CourseEvent[]; @@ -10,12 +10,11 @@ type Props = { }; export const CalendarView: React.FC = ({data, timeZone}) => { - const { width } = useWindowDimensions(); return <> - {width > 750 - ? - : + {isMobile + ? + : } }; diff --git a/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx b/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx index 1c037cab92..9627a1e867 100644 --- a/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx +++ b/client/src/components/Schedule/CalendarView/components/DesktopCalendar.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import { Calendar, Badge } from 'antd'; -import { getMonthValue } from '../utils/DateFuncs'; -import { getListData } from '../utils/DataFuncs'; +import { getMonthValue, getListData } from '../utils/filters'; import { Scrollbars } from 'react-custom-scrollbars'; import { ModalWindow } from './Modal'; import { CourseEvent } from 'services/course'; @@ -58,12 +57,12 @@ const DesktopCalendar: React.FC = ({ data, timeZone }) => { const monthCellRender = (date: unknown | Moment) => { const num = getMonthValue(date as unknown as Moment, data, timeZone); - return num ? ( + return !!num && (
Number of events
{num}
- ) : null; + ); } diff --git a/client/src/components/Schedule/CalendarView/components/MobileCalendar.tsx b/client/src/components/Schedule/CalendarView/components/MobileCalendar.tsx index 15bbfad86a..bda45af5b4 100644 --- a/client/src/components/Schedule/CalendarView/components/MobileCalendar.tsx +++ b/client/src/components/Schedule/CalendarView/components/MobileCalendar.tsx @@ -1,11 +1,23 @@ import React, { useState} from 'react'; import { Calendar, Badge, List } from 'antd'; -import { getMonthValue } from '../utils/DateFuncs'; +import { getMonthValue, getListData } from '../utils/filters'; import {ModalWindow} from './Modal'; -import { getListData } from '../utils/DataFuncs'; import { CourseEvent } from 'services/course'; import { Moment } from 'moment'; import {dateWithTimeZoneRenderer} from 'components/Table'; +import css from 'styled-jsx/css'; + +const numberEventsStyle = css`section { + position: absolute; + bottom: 12px; + background: #FB6216; + right: -13px; + border-radius: 100%; + width: 20px; + height: 20px; + line-height: 19px; + color: white; +}` type Props = { data: CourseEvent[]; @@ -42,48 +54,26 @@ const MobileCalendar: React.FC = ({ data, timeZone }) => { function dateCellRender(date: unknown | Moment) { const numberEvents = getListData(date as unknown as Moment, data, timeZone).length; - return numberEvents > 0 ? ( + return !!(numberEvents > 0) && ( <>
{numberEvents}
- ) : null; + ); } const monthCellRender = (date: unknown | Moment) => { - const num = getMonthValue(date as unknown as Moment, data, timeZone); - return num ? ( + const numberEvents = getMonthValue(date as unknown as Moment, data, timeZone); + return !!numberEvents && ( <> -
{num}
+
{numberEvents}
+ {numberEventsStyle} + - ) : null; + ); } return ( @@ -106,7 +96,7 @@ const MobileCalendar: React.FC = ({ data, timeZone }) => { } /> -
Time: {dateWithTimeZoneRenderer(timeZone, 'h:mm')(dateTime)}
+
Time: {dateWithTimeZoneRenderer(timeZone, 'h:mm')(dateTime)}
) }}/> diff --git a/client/src/components/Schedule/CalendarView/components/Modal.tsx b/client/src/components/Schedule/CalendarView/components/Modal.tsx index 686447c77e..5e37fd0d12 100644 --- a/client/src/components/Schedule/CalendarView/components/Modal.tsx +++ b/client/src/components/Schedule/CalendarView/components/Modal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Modal} from 'antd'; +import { Modal, Space} from 'antd'; import moment from 'moment'; import {GithubUserLink} from 'components'; import { renderTag, urlRenderer } from 'components/Table/renderers'; @@ -15,9 +15,6 @@ type Props = { export function ModalWindow({ isOpen, dataEvent, handleOnClose, timeZone }: Props) { - //!plug - const newTimeZone = timeZone === 'Europe/Yekaterinburg' ? 'Asia/Yekaterinburg' : timeZone; - return ( <> handleOnClose()} visible={isOpen} > -
Date: {moment(dataEvent.dateTime, 'YYYY-MM-DD HH:mmZ').tz(newTimeZone).format('LLL')}
- {dataEvent.event.descriptionUrl && -
Url: {urlRenderer(dataEvent.event.descriptionUrl)}
- } +
Date: {moment(dataEvent.dateTime, 'YYYY-MM-DD HH:mmZ').tz(timeZone).format('LLL')}
{dataEvent.event.description &&
{dataEvent.event.description}
} -
- {renderTag(EventTypeToName[dataEvent.event.type] || dataEvent.event.type, EventTypeColor[dataEvent.event.type as keyof typeof EventTypeColor])} -
{!!dataEvent.organizer.githubId &&
Organizer:
} + + {dataEvent.event.descriptionUrl && +
Url: {urlRenderer(dataEvent.event.descriptionUrl)}
+ } +
+ {renderTag(EventTypeToName[dataEvent.event.type] || dataEvent.event.type, EventTypeColor[dataEvent.event.type as keyof typeof EventTypeColor])} +
+
+ + ); + }); +}; + +const weekElements = (events: CourseEvent[], currentWeekCount: number, timeZone: number) => { + const currentWeek = events.filter((event: CourseEvent) => isCurrentWeek(event.dateTime, currentWeekCount)); + const weekMap = mapToWeek(currentWeek); + const currentDate = Date.now(); + + return weekMap.map((event: CourseEvent[], index: number) => { + const eventCount = event.length; + const eventCountElem = ( + + {WEEK[index]} + + ); + const style = panelClassName(index + 1, currentWeekCount); + const key = currentDate + index; + + if (event.length) { + return ( + +
+ {dayEvents(event, timeZone)} + +
+ + ); + } + + return ; + }); +}; type Props = { - data: CourseEvent[]; + data: any; timeZone: string; }; -export function ListView({ data, timeZone }: Props) { +const ScheduleList = ({ data }: Props): React.ReactElement => { + const { Text } = Typography; + const [currentWeek, setCurrentWeek] = useState(0); + + const [startWeekDate, setStartWeekDate] = useState(''); + const [endWeekDate, setEndWeekDate] = useState(''); + // const { + // settings: { + // settings: { timeZone }, + // }, + // } = useStores(); + const timeZone = 3; + + useEffect(() => { + const startAndEnd = getStartAndEndWeekTime(currentWeek); + const startWeek = new Date(startAndEnd[0]); + const endWeek = new Date(startAndEnd[1]); + const startWeekText = `${startWeek.getDate()} ${MONTHS[startWeek.getMonth()]}`; + const endWeekText = `${endWeek.getDate()} ${MONTHS[endWeek.getMonth()]}`; + + setStartWeekDate(startWeekText); + setEndWeekDate(endWeekText); + }, [currentWeek]); + + const handleClickBack = () => { + setCurrentWeek(currentWeek - 1); + }; + + const handleClickForward = () => { + setCurrentWeek(currentWeek + 1); + }; + + if (data === null) return ; + return ( -
-

List

- Data length = {data.length}. Timezone = {timeZone} +
+ + + +
); -} +}; + +const listStyles = css` + .List { + padding: 0 100px; + } + + @media (max-width: 600px) { + .List { + padding: 0; + } + } +`; + +const tableStyles = css` + .ListTable { + border-collapse: collapse; + margin: 0 auto; + width: 100%; + + th { + border-bottom: 1px solid #f0f0f0; + padding: 5px 20px; + text-align: center; + cursor: pointer; + } + + tr:hover { + background-color: #fcc; + } + } +`; -export default ListView; +export default ScheduleList; From 5b2f6159f82aee06dfb438d876470e16288f2777 Mon Sep 17 00:00:00 2001 From: Aleksandr Sharikov Date: Wed, 9 Dec 2020 20:51:21 +0300 Subject: [PATCH 16/68] chore: deep refactor code --- client/src/components/Schedule/ListView.tsx | 278 +++++++++----------- 1 file changed, 118 insertions(+), 160 deletions(-) diff --git a/client/src/components/Schedule/ListView.tsx b/client/src/components/Schedule/ListView.tsx index 84faef8c70..6abd1976f9 100644 --- a/client/src/components/Schedule/ListView.tsx +++ b/client/src/components/Schedule/ListView.tsx @@ -1,79 +1,117 @@ -import React, { useEffect, useState } from 'react'; -// import { Link } from 'react-router-dom'; -import { Collapse, Tag, Badge, Spin, Button, Row, Typography, Col, Tooltip } from 'antd'; +import React, { useEffect, useMemo, useState } from 'react'; +import { Collapse, Badge, Button, Row, Typography, Col, Tooltip } from 'antd'; import { LeftOutlined, RightOutlined } from '@ant-design/icons'; - -import { CourseEvent } from 'services/course'; -const WEEK = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; -const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; -// import { getTagColorByEventType, getFormatTime } from '../../helpers/schedule-utils'; -// import { TaskType } from '../../constants/settings'; - import css from 'styled-jsx/css'; +import moment from 'moment-timezone'; +import { CourseEvent } from 'services/course'; +import { dateWithTimeZoneRenderer, renderTag } from 'components/Table'; +import { EventTypeColor, EventTypeToName } from './model'; const { Panel } = Collapse; +const { Text } = Typography; +const currentDayColor = '#ffd591'; +const previuosDaysColor = '#fff1f0'; +const nextDaysColor = '#d9f7be'; +const WEEK = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; const weekLength = WEEK.length; -const millisecondsPerDay = 24 * 60 * 60 * 1000; - -const getStartAndEndWeekTime = (currentWeek: number) => { - const dateNow = 1586250152000; - // поменять на Date.now() - const now = new Date(dateNow + currentWeek * weekLength * millisecondsPerDay); - let day = now.getDay(); - if (day === 0) { - day = 7; - } - now.setHours(0); - now.setMinutes(0); - now.setSeconds(0); - now.setMilliseconds(0); - const startWeek = now.getTime() - millisecondsPerDay * (day - 1); - const endWeek = now.getTime() + millisecondsPerDay * (weekLength - day + 1) - 1; +type Props = { + data: CourseEvent[]; + timeZone: string; +}; + +export const ListView = ({ data, timeZone }: Props): React.ReactElement => { + const [currentWeek, setCurrentWeek] = useState(0); + const [startWeekDate, setStartWeekDate] = useState(''); + const [endWeekDate, setEndWeekDate] = useState(''); + + useEffect(() => { + const startAndEnd = getStartAndEndWeekTime(currentWeek, timeZone); + const startWeekText = startAndEnd[0].format('D MMM'); + const endWeekText = startAndEnd[1].format('D MMM'); + + setStartWeekDate(startWeekText); + setEndWeekDate(endWeekText); + }, [currentWeek]); + + const handleClickBack = () => { + setCurrentWeek(currentWeek - 1); + }; + + const handleClickForward = () => { + setCurrentWeek(currentWeek + 1); + }; + + const getCurrentDayKey = useMemo(() => { + const day = moment().day(); + const currentDay = day ? day - 1 : 6; + return `${WEEK[currentDay]}0`; + }, []); + + return ( +
+ + + +
+ ); +}; + +const getStartAndEndWeekTime = (currentWeek: number, timeZone: string) => { + const dayOfSelectedWeek = moment() + .tz(timeZone) + .add(currentWeek * weekLength, 'days'); + const startWeek = dayOfSelectedWeek.clone().startOf('isoWeek'); + const endWeek = dayOfSelectedWeek.clone().endOf('isoWeek'); return [startWeek, endWeek]; }; -const isCurrentWeek = (date: string, currentWeek: number) => { - const startAndEnd = getStartAndEndWeekTime(currentWeek); - const milliseconds = Date.parse(date); - return milliseconds > startAndEnd[0] && milliseconds < startAndEnd[1]; +const isCurrentWeek = (date: string, timeZone: string, currentWeek: number) => { + const startAndEnd = getStartAndEndWeekTime(currentWeek, timeZone); + const milliseconds = moment(date).tz(timeZone).valueOf(); + + return milliseconds > startAndEnd[0].valueOf() && milliseconds < startAndEnd[1].valueOf(); }; -const panelClassName = (daySelected: number, currentWeek: number) => { - const now = new Date(Date.now()); - let dayNow = now.getDay(); - if (dayNow === 0) { - dayNow = 7; - } +const panelClassName = (dayOfWeek: number, currentWeek: number) => { + const today = moment().day(); - if (currentWeek > 0) { - return { backgroundColor: '#d9f7be' }; - } - if (currentWeek < 0) { - return { backgroundColor: '#fff1f0', opacity: '0.5' }; + if (currentWeek < 0 || (currentWeek === 0 && dayOfWeek < today)) { + return { backgroundColor: previuosDaysColor }; } - if (dayNow > daySelected) { - return { backgroundColor: '#fff1f0', opacity: '0.5' }; - } - if (dayNow < daySelected) { - return { backgroundColor: '#d9f7be' }; + + if (currentWeek > 0 || (currentWeek === 0 && dayOfWeek > today)) { + return { backgroundColor: nextDaysColor }; } - if (dayNow === daySelected) { - return { backgroundColor: '#ffd591' }; + + if (dayOfWeek === today) { + return { backgroundColor: currentDayColor }; } return {}; }; -const mapToWeek = (events: CourseEvent[]) => { +const mapToWeek = (events: CourseEvent[], timeZone: string) => { const weekMap = new Array(weekLength).fill([]); events.forEach((event: CourseEvent) => { - const time = Date.parse(event.dateTime); - const date = new Date(time); - let eventDay = date.getDay(); + let eventDay = moment(event.dateTime).tz(timeZone).day(); if (eventDay === 0) { eventDay = 6; @@ -94,32 +132,20 @@ const mapToWeek = (events: CourseEvent[]) => { return weekMap; }; -const getFormatTime = (dateNumb: number, timeZone: number): string => { - const date = new Date(dateNumb + timeZone * 3600 * 1000); - const hour = `${date.getUTCHours()}`.padStart(2, '0'); - const minutes = `${date.getUTCMinutes()}`.padStart(2, '0'); - - return `${hour}:${minutes}`; -}; - -const dayEvents = (events: CourseEvent[], timeZone: number) => { +const dayEvents = (events: CourseEvent[], timeZone: string) => { return events.map((data: CourseEvent) => { const { id, event, dateTime } = data; const { type, name } = event; - // const color = getTagColorByEventType(type as TaskType); - const color = 'red'; - // const eventCopy = { ...event }; - const eventTime = getFormatTime(Date.parse(dateTime), timeZone); + return ( - {eventTime} - - {type} + {dateWithTimeZoneRenderer(timeZone, 'HH:mm')(dateTime)} + + {renderTag(EventTypeToName[type] || type, EventTypeColor[type as keyof typeof EventTypeColor])} - + {name} - {/*{name}*/} @@ -128,26 +154,25 @@ const dayEvents = (events: CourseEvent[], timeZone: number) => { }); }; -const weekElements = (events: CourseEvent[], currentWeekCount: number, timeZone: number) => { - const currentWeek = events.filter((event: CourseEvent) => isCurrentWeek(event.dateTime, currentWeekCount)); - const weekMap = mapToWeek(currentWeek); - const currentDate = Date.now(); +const weekElements = (events: CourseEvent[], selectedWeek: number, timeZone: string) => { + const currentWeek = events.filter((event: CourseEvent) => isCurrentWeek(event.dateTime, timeZone, selectedWeek)); + const weekMap = mapToWeek(currentWeek, timeZone); - return weekMap.map((event: CourseEvent[], index: number) => { - const eventCount = event.length; + return weekMap.map((eventsPerDay: CourseEvent[], index: number) => { + const eventCount = eventsPerDay.length; const eventCountElem = ( - {WEEK[index]} + {WEEK[index]} ); - const style = panelClassName(index + 1, currentWeekCount); - const key = currentDate + index; + const style = panelClassName(index + 1, selectedWeek); + const key = `${WEEK[index]}${selectedWeek}`; - if (event.length) { + if (eventCount) { return ( - {dayEvents(event, timeZone)} + {dayEvents(eventsPerDay, timeZone)}
@@ -158,77 +183,10 @@ const weekElements = (events: CourseEvent[], currentWeekCount: number, timeZone: }); }; -type Props = { - data: any; - timeZone: string; -}; - -const ScheduleList = ({ data }: Props): React.ReactElement => { - const { Text } = Typography; - const [currentWeek, setCurrentWeek] = useState(0); - - const [startWeekDate, setStartWeekDate] = useState(''); - const [endWeekDate, setEndWeekDate] = useState(''); - // const { - // settings: { - // settings: { timeZone }, - // }, - // } = useStores(); - const timeZone = 3; - - useEffect(() => { - const startAndEnd = getStartAndEndWeekTime(currentWeek); - const startWeek = new Date(startAndEnd[0]); - const endWeek = new Date(startAndEnd[1]); - const startWeekText = `${startWeek.getDate()} ${MONTHS[startWeek.getMonth()]}`; - const endWeekText = `${endWeek.getDate()} ${MONTHS[endWeek.getMonth()]}`; - - setStartWeekDate(startWeekText); - setEndWeekDate(endWeekText); - }, [currentWeek]); - - const handleClickBack = () => { - setCurrentWeek(currentWeek - 1); - }; - - const handleClickForward = () => { - setCurrentWeek(currentWeek + 1); - }; - - if (data === null) return ; - - return ( -
- - - -
- ); -}; - const listStyles = css` .List { - padding: 0 100px; - } - - @media (max-width: 600px) { - .List { - padding: 0; - } + margin: 0 auto; + max-width: 500px; } `; @@ -237,18 +195,18 @@ const tableStyles = css` border-collapse: collapse; margin: 0 auto; width: 100%; + } - th { - border-bottom: 1px solid #f0f0f0; - padding: 5px 20px; - text-align: center; - cursor: pointer; - } + .ListTable th { + border-bottom: 1px solid #f0f0f0; + padding: 5px 20px; + text-align: center; + cursor: pointer; + } - tr:hover { - background-color: #fcc; - } + .ListTable tr:hover { + background-color: #f2f2f2; } `; -export default ScheduleList; +export default ListView; From d1a957ac5a085d26fb637c4f70d5c9b4499cd381 Mon Sep 17 00:00:00 2001 From: Aleksandr Sharikov Date: Wed, 9 Dec 2020 21:47:22 +0300 Subject: [PATCH 17/68] fix: no expand empty today --- client/src/components/Schedule/ListView.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/src/components/Schedule/ListView.tsx b/client/src/components/Schedule/ListView.tsx index 6abd1976f9..e565c8e8dc 100644 --- a/client/src/components/Schedule/ListView.tsx +++ b/client/src/components/Schedule/ListView.tsx @@ -46,8 +46,15 @@ export const ListView = ({ data, timeZone }: Props): React.ReactElement => { const getCurrentDayKey = useMemo(() => { const day = moment().day(); const currentDay = day ? day - 1 : 6; + const date = moment().format('YYYYMMDD'); + const todaysEvents = data.filter(({ dateTime }) => date === moment(dateTime).format('YYYYMMDD')); + + if (todaysEvents.length === 0) { + return []; + } + return `${WEEK[currentDay]}0`; - }, []); + }, [data]); return (
From 40f5c5e97971554654bb102a3e1decaf1b64b07c Mon Sep 17 00:00:00 2001 From: Aleksandr Sharikov Date: Thu, 10 Dec 2020 16:19:50 +0300 Subject: [PATCH 18/68] chore: refactor code --- client/src/components/Schedule/ListView.tsx | 57 ++++++++++----------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/client/src/components/Schedule/ListView.tsx b/client/src/components/Schedule/ListView.tsx index e565c8e8dc..b97990c371 100644 --- a/client/src/components/Schedule/ListView.tsx +++ b/client/src/components/Schedule/ListView.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { Collapse, Badge, Button, Row, Typography, Col, Tooltip } from 'antd'; import { LeftOutlined, RightOutlined } from '@ant-design/icons'; import css from 'styled-jsx/css'; @@ -10,6 +10,7 @@ import { EventTypeColor, EventTypeToName } from './model'; const { Panel } = Collapse; const { Text } = Typography; +const LAST_WEEK_DAY = 6; const currentDayColor = '#ffd591'; const previuosDaysColor = '#fff1f0'; const nextDaysColor = '#d9f7be'; @@ -23,17 +24,24 @@ type Props = { export const ListView = ({ data, timeZone }: Props): React.ReactElement => { const [currentWeek, setCurrentWeek] = useState(0); - const [startWeekDate, setStartWeekDate] = useState(''); - const [endWeekDate, setEndWeekDate] = useState(''); - useEffect(() => { - const startAndEnd = getStartAndEndWeekTime(currentWeek, timeZone); - const startWeekText = startAndEnd[0].format('D MMM'); - const endWeekText = startAndEnd[1].format('D MMM'); + const currentDayKey = useMemo(() => { + const day = moment().day(); + const currentDay = day ? day - 1 : LAST_WEEK_DAY; + const date = moment().format('YYYYMMDD'); + const todaysEvents = data.filter(({ dateTime }) => date === moment(dateTime).tz(timeZone).format('YYYYMMDD')); + + if (todaysEvents.length === 0) { + return []; + } - setStartWeekDate(startWeekText); - setEndWeekDate(endWeekText); - }, [currentWeek]); + return `${WEEK[currentDay]}0`; + }, [data, timeZone]); + + const startEndWeekLabel = useMemo(() => { + const startAndEnd = getStartAndEndWeekTime(currentWeek, timeZone); + return `${startAndEnd[0].format('D MMM')} - ${startAndEnd[1].format('D MMM')}`; + }, [currentWeek, timeZone]); const handleClickBack = () => { setCurrentWeek(currentWeek - 1); @@ -43,21 +51,8 @@ export const ListView = ({ data, timeZone }: Props): React.ReactElement => { setCurrentWeek(currentWeek + 1); }; - const getCurrentDayKey = useMemo(() => { - const day = moment().day(); - const currentDay = day ? day - 1 : 6; - const date = moment().format('YYYYMMDD'); - const todaysEvents = data.filter(({ dateTime }) => date === moment(dateTime).format('YYYYMMDD')); - - if (todaysEvents.length === 0) { - return []; - } - - return `${WEEK[currentDay]}0`; - }, [data]); - return ( -
+
@@ -65,7 +60,7 @@ export const ListView = ({ data, timeZone }: Props): React.ReactElement => { - {`${startWeekDate} - ${endWeekDate}`} + {startEndWeekLabel} @@ -73,7 +68,7 @@ export const ListView = ({ data, timeZone }: Props): React.ReactElement => { - {weekElements(data, currentWeek, timeZone)} + {getWeekElements(data, currentWeek, timeZone)}
); @@ -121,7 +116,7 @@ const mapToWeek = (events: CourseEvent[], timeZone: string) => { let eventDay = moment(event.dateTime).tz(timeZone).day(); if (eventDay === 0) { - eventDay = 6; + eventDay = LAST_WEEK_DAY; } else { eventDay -= 1; } @@ -139,7 +134,7 @@ const mapToWeek = (events: CourseEvent[], timeZone: string) => { return weekMap; }; -const dayEvents = (events: CourseEvent[], timeZone: string) => { +const getDayEvents = (events: CourseEvent[], timeZone: string) => { return events.map((data: CourseEvent) => { const { id, event, dateTime } = data; const { type, name } = event; @@ -161,7 +156,7 @@ const dayEvents = (events: CourseEvent[], timeZone: string) => { }); }; -const weekElements = (events: CourseEvent[], selectedWeek: number, timeZone: string) => { +const getWeekElements = (events: CourseEvent[], selectedWeek: number, timeZone: string) => { const currentWeek = events.filter((event: CourseEvent) => isCurrentWeek(event.dateTime, timeZone, selectedWeek)); const weekMap = mapToWeek(currentWeek, timeZone); @@ -178,8 +173,8 @@ const weekElements = (events: CourseEvent[], selectedWeek: number, timeZone: str if (eventCount) { return ( - - {dayEvents(eventsPerDay, timeZone)} +
+ {getDayEvents(eventsPerDay, timeZone)}
From 4a894c32787b93000bc96d180e226db73ac11995 Mon Sep 17 00:00:00 2001 From: Artem Uspenski Date: Mon, 11 Jan 2021 20:45:18 +0300 Subject: [PATCH 19/68] fix: fix bracket in filterComponent --- client/src/components/Table/FilterComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Table/FilterComponent.tsx b/client/src/components/Table/FilterComponent.tsx index 466e4d7761..b33735b053 100644 --- a/client/src/components/Table/FilterComponent.tsx +++ b/client/src/components/Table/FilterComponent.tsx @@ -29,7 +29,7 @@ const FilterComponent: React.FC = ({setHiddenColumn, hiddenColumn}) => { columnsName.map((el, ind) => { return {el}; }) - } + } ); } From c869c349c09af9549eaf21555c5aafd71e254666 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 16 Dec 2020 19:23:08 +0300 Subject: [PATCH 20/68] feat: implement set of tags, change variable for better reading --- .../src/components/UserSettings/TagColor.tsx | 22 +++++++++++-------- .../components/UserSettings/UserSettings.tsx | 8 +++++-- .../UserSettings/userSettingsHandlers.ts | 22 ------------------- 3 files changed, 19 insertions(+), 33 deletions(-) diff --git a/client/src/components/UserSettings/TagColor.tsx b/client/src/components/UserSettings/TagColor.tsx index 5ec6ddde94..fff8496c45 100644 --- a/client/src/components/UserSettings/TagColor.tsx +++ b/client/src/components/UserSettings/TagColor.tsx @@ -2,40 +2,44 @@ import React, { useCallback } from 'react'; import TagColorIcon from './TagColorIcon'; import { Collapse, Tag } from 'antd'; import { GithubPicker } from 'react-color'; -import { pickerColors, mockedTags as tags, setTagColor, getTagStyle, DEFAULT_COLOR } from './userSettingsHandlers'; +import { pickerColors, setTagColor, getTagStyle, DEFAULT_COLOR } from './userSettingsHandlers'; import { useLocalStorage } from 'react-use'; import { ColorState as IColorState } from 'react-color'; -const TagColor: React.FC = () => { +type Props = { + tags: string[]; +}; + +const TagColor = ({ tags }: Props) => { const { Panel } = Collapse; const [storedTagColors, setStoredTagColors] = useLocalStorage('tagColors', DEFAULT_COLOR); const memoizedSetTagColor = useCallback( - (e: IColorState, itemName, storedTagColors) => { - setTagColor(e, itemName, setStoredTagColors, storedTagColors); + (e: IColorState, tagName, storedTagColors) => { + setTagColor(e, tagName, setStoredTagColors, storedTagColors); }, [storedTagColors], ); const collapseTags = ( - {tags.map((item) => { + {tags.map((tag) => { return ( - {item.name} + {tag} } - key={item.name} + key={tag} > memoizedSetTagColor(e, item.name, storedTagColors)} + onChange={(e) => memoizedSetTagColor(e, tag, storedTagColors)} /> ); diff --git a/client/src/components/UserSettings/UserSettings.tsx b/client/src/components/UserSettings/UserSettings.tsx index ef9154e23e..db84141807 100644 --- a/client/src/components/UserSettings/UserSettings.tsx +++ b/client/src/components/UserSettings/UserSettings.tsx @@ -4,7 +4,11 @@ import { SettingOutlined } from '@ant-design/icons'; import TagColor from './TagColor'; -const UserSettings: React.FC = () => { +type Props = { + tags: string[]; +}; + +const UserSettings = ({ tags }: Props) => { const [isOpen, setIsOpen] = useState(false); const showDrawer = () => { setIsOpen(true); @@ -18,7 +22,7 @@ const UserSettings: React.FC = () => {