From 690ec9bf34e7e6b28b2a2e48b91f8e9280352095 Mon Sep 17 00:00:00 2001 From: Delirium Date: Tue, 23 Apr 2024 16:15:00 +0200 Subject: [PATCH] Sess header fixes (#2122) * fix ui: fixes for session list header * same for notes * console --- .../components/shared/DatePicker/index.tsx | 157 ++++++++++++++++++ .../DateRangeDropdown/DateRangePopup.js | 40 ++--- .../SelectDateRange/SelectDateRange.tsx | 74 ++++++--- .../components/Notes/NoteTags.tsx | 112 ++++++++----- .../components/SessionSort/SessionSort.tsx | 33 ++-- .../components/SessionTags/SessionTags.tsx | 10 +- frontend/app/mstore/notesStore.ts | 32 ++-- 7 files changed, 356 insertions(+), 102 deletions(-) create mode 100644 frontend/app/components/shared/DatePicker/index.tsx diff --git a/frontend/app/components/shared/DatePicker/index.tsx b/frontend/app/components/shared/DatePicker/index.tsx new file mode 100644 index 000000000..93bf874e2 --- /dev/null +++ b/frontend/app/components/shared/DatePicker/index.tsx @@ -0,0 +1,157 @@ +// @ts-nocheck +import { DatePicker } from 'antd'; +import { PickerTimeProps } from 'antd/es/time-picker'; +import moment from 'moment'; +import type { Moment } from 'moment'; +import React from 'react'; + +const generateConfig = { + // get + getNow: () => moment(), + getFixedDate: (string: string) => moment(string, 'YYYY-MM-DD'), + getEndDate: (date) => { + const clone = date.clone(); + return clone.endOf('month'); + }, + getWeekDay: (date) => { + const clone = date.clone().locale('en_US'); + return clone.weekday() + clone.localeData().firstDayOfWeek(); + }, + getYear: (date) => date.year(), + getMonth: (date) => date.month(), + getDate: (date) => date.date(), + getHour: (date) => date.hour(), + getMinute: (date) => date.minute(), + getSecond: (date) => date.second(), + getMillisecond: (date) => date.millisecond(), + + // set + addYear: (date, diff) => { + const clone = date.clone(); + return clone.add(diff, 'year'); + }, + addMonth: (date, diff) => { + const clone = date.clone(); + return clone.add(diff, 'month'); + }, + addDate: (date, diff) => { + const clone = date.clone(); + return clone.add(diff, 'day'); + }, + setYear: (date, year) => { + const clone = date.clone(); + return clone.year(year); + }, + setMonth: (date, month) => { + const clone = date.clone(); + return clone.month(month); + }, + setDate: (date, num) => { + const clone = date.clone(); + return clone.date(num); + }, + setHour: (date, hour) => { + const clone = date.clone(); + return clone.hour(hour); + }, + setMinute: (date, minute) => { + const clone = date.clone(); + return clone.minute(minute); + }, + setSecond: (date, second) => { + const clone = date.clone(); + return clone.second(second); + }, + setMillisecond: (date, millisecond) => { + const clone = date.clone(); + return clone.millisecond(millisecond); + }, + + // Compare + isAfter: (date1, date2) => date1.isAfter(date2), + isValidate: (date) => date.isValid(), + + locale: { + getWeekFirstDay: (locale) => { + const date = moment().locale(locale); + return date.localeData().firstDayOfWeek(); + }, + getWeekFirstDate: (locale, date) => { + const clone = date.clone(); + const result = clone.locale(locale); + return result.weekday(0); + }, + getWeek: (locale, date) => { + const clone = date.clone(); + const result = clone.locale(locale); + return result.week(); + }, + getShortWeekDays: (locale) => { + const date = moment().locale(locale); + return date.localeData().weekdaysMin(); + }, + getShortMonths: (locale) => { + const date = moment().locale(locale); + return date.localeData().monthsShort(); + }, + format: (locale, date, format) => { + const clone = date.clone(); + const result = clone.locale(locale); + return result.format(format); + }, + parse: (locale, text, formats) => { + const fallbackFormatList: string[] = []; + + for (let i = 0; i < formats.length; i += 1) { + let format = formats[i]; + let formatText = text; + + if (format.includes('wo') || format.includes('Wo')) { + format = format.replace(/wo/g, 'w').replace(/Wo/g, 'W'); + const matchFormat = format.match(/[-YyMmDdHhSsWwGg]+/g); + const matchText = formatText.match(/[-\d]+/g); + + if (matchFormat && matchText) { + format = matchFormat.join(''); + formatText = matchText.join(''); + } else { + fallbackFormatList.push(format.replace(/o/g, '')); + } + } + + const date = moment(formatText, format, locale, true); + if (date.isValid()) { + return date; + } + } + + // Fallback to fuzzy matching, this should always not reach match or need fire a issue + for (let i = 0; i < fallbackFormatList.length; i += 1) { + const date = moment(text, fallbackFormatList[i], locale, false); + + /* istanbul ignore next */ + if (date.isValid()) { + console.error( + 'Not match any format strictly and fallback to fuzzy match. Please help to fire a issue about this.' + ); + return date; + } + } + + return null; + }, + }, +}; + +const CustomPicker = DatePicker.generatePicker(generateConfig); + +export interface TimePickerProps + extends Omit, 'picker'> {} + +const TimePicker = React.forwardRef((props, ref) => ( + +)); + +TimePicker.displayName = 'TimePicker'; + +export { CustomPicker as DatePicker, TimePicker }; diff --git a/frontend/app/components/shared/DateRangeDropdown/DateRangePopup.js b/frontend/app/components/shared/DateRangeDropdown/DateRangePopup.js index 88f4a8292..ec84feb61 100644 --- a/frontend/app/components/shared/DateRangeDropdown/DateRangePopup.js +++ b/frontend/app/components/shared/DateRangeDropdown/DateRangePopup.js @@ -1,8 +1,8 @@ import React from 'react'; import DateRangePicker from 'react-daterange-picker' -import TimePicker from 'rc-time-picker'; -import { Button } from 'UI'; import { getDateRangeFromValue, getDateRangeLabel, dateRangeValues, CUSTOM_RANGE, moment, DATE_RANGE_VALUES } from 'App/dateRange'; +import { Button } from 'antd' +import { TimePicker } from 'App/components/shared/DatePicker' import styles from './dateRangePopup.module.css'; @@ -24,8 +24,8 @@ export default class DateRangePopup extends React.PureComponent { if (value.isAfter(this.state.range.end)) { return; } - this.setState({ - range: moment.range( + this.setState({ + range: moment.range( value, this.state.range.end, ), @@ -36,8 +36,8 @@ export default class DateRangePopup extends React.PureComponent { if (value && value.isBefore(this.state.range.start)) { return; } - this.setState({ - range: moment.range( + this.setState({ + range: moment.range( this.state.range.start, value, ), @@ -72,7 +72,7 @@ export default class DateRangePopup extends React.PureComponent {
{ dateRangeValues.filter(value => value !== CUSTOM_RANGE && value !== DATE_RANGE_VALUES.LAST_30_MINUTES).map(value => (
this.selectValue(value) } > { getDateRangeLabel(value) } @@ -92,29 +92,31 @@ export default class DateRangePopup extends React.PureComponent { />
-
+
{range.start.format("DD/MM")} {range.end.format("DD/MM")} -
- +
diff --git a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx index ff6b9b594..afde5005d 100644 --- a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx +++ b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx @@ -1,13 +1,16 @@ -import React from 'react'; -import { Select as AntSelect } from 'antd'; -import { DATE_RANGE_OPTIONS, CUSTOM_RANGE } from 'App/dateRange'; -import Select from 'Shared/Select'; +import { DownOutlined } from '@ant-design/icons'; import Period from 'Types/app/period'; -import { components } from 'react-select'; -import DateRangePopup from 'Shared/DateRangeDropdown/DateRangePopup'; -import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; +import { Dropdown, DatePicker } from 'antd'; import cn from 'classnames'; import { observer } from 'mobx-react-lite'; +import React from 'react'; +import { components } from 'react-select'; + +import { CUSTOM_RANGE, DATE_RANGE_OPTIONS } from 'App/dateRange'; + +import DateRangePopup from 'Shared/DateRangeDropdown/DateRangePopup'; +import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; +import Select from 'Shared/Select'; interface Props { period: any; @@ -23,8 +26,11 @@ interface Props { function SelectDateRange(props: Props) { const [isCustom, setIsCustom] = React.useState(false); + const [isCustomOpen, setIsCustomOpen] = React.useState(false); const { right = false, period, disableCustom = false, timezone } = props; - let selectedValue = DATE_RANGE_OPTIONS.find((obj: any) => obj.value === period.rangeName); + let selectedValue = DATE_RANGE_OPTIONS.find( + (obj: any) => obj.value === period.rangeName + ); const options = DATE_RANGE_OPTIONS.filter((obj: any) => disableCustom ? obj.value !== CUSTOM_RANGE : true ); @@ -33,7 +39,7 @@ function SelectDateRange(props: Props) { if (value === CUSTOM_RANGE) { setTimeout(() => { setIsCustom(true); - }, 1) + }, 1); } else { // @ts-ignore props.onChange(new Period({ rangeName: value })); @@ -42,27 +48,48 @@ function SelectDateRange(props: Props) { const onApplyDateRange = (value: any) => { // @ts-ignore - const range = new Period({ rangeName: CUSTOM_RANGE, start: value.start, end: value.end }); + const range = new Period({ + rangeName: CUSTOM_RANGE, + start: value.start, + end: value.end, + }); props.onChange(range); setIsCustom(false); }; const isCustomRange = period.rangeName === CUSTOM_RANGE; const customRange = isCustomRange ? period.rangeFormatted() : ''; - + if (props.isAnt) { const onAntUpdate = (val: any) => { onChange(val); }; return (
- ({ + label: opt.label, + key: opt.value, + })), + defaultSelectedKeys: selectedValue?.value + ? [selectedValue.value] + : undefined, + onClick: (e: any) => { + onChange(e.key); + }, + }} onChange={onAntUpdate} style={{ width: 170 }} defaultValue={selectedValue?.value ?? undefined} - /> + > +
+
{isCustomRange ? customRange : selectedValue?.label}
+ +
+ {isCustom && ( { @@ -70,7 +97,12 @@ function SelectDateRange(props: Props) { e.target.parentElement.parentElement.classList.contains( 'rc-time-picker-panel-select' ) || - e.target.parentElement.parentElement.classList[0]?.includes('-menu') + e.target.parentElement.parentElement.classList[0]?.includes( + '-menu' + ) + || e.target.className.includes( + 'ant-picker' + ) ) { return false; } @@ -78,7 +110,10 @@ function SelectDateRange(props: Props) { }} >
diff --git a/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteTags.tsx b/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteTags.tsx index b7a4f6101..3463b5356 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteTags.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteTags.tsx @@ -1,9 +1,17 @@ -import React from 'react'; -import Select from 'Shared/Select'; -import { TAGS, iTag } from 'App/services/NotesService'; -import { TagItem } from '../SessionTags'; -import { useStore } from 'App/mstore'; +import { DownOutlined } from "@ant-design/icons"; +import { Dropdown, Segmented } from 'antd'; import { observer } from 'mobx-react-lite'; +import React from 'react'; + + + +import { useStore } from 'App/mstore'; +import { TAGS, iTag } from 'App/services/NotesService'; + + + +import Select from 'Shared/Select'; + const sortOptionsMap = { 'createdAt-DESC': 'Newest', @@ -14,45 +22,73 @@ const notesOwner = [ { value: '0', label: 'All Notes' }, { value: '1', label: 'My Notes' }, ]; +function toTitleCase(str) { + return str.replace( + /\w\S*/g, + function(txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + } + ); +} function NoteTags() { const { notesStore } = useStore(); return (
-
- notesStore.toggleTag()} - label="ALL" - isActive={notesStore.activeTags.length === 0} - /> -
- {TAGS.map((tag: iTag) => ( -
- notesStore.toggleTag(tag)} - label={tag} - isActive={notesStore.activeTags.includes(tag)} - /> -
- ))} + ({ + value: tag, + label: toTitleCase(tag), + })), + ]} + onChange={(value: iTag) => notesStore.toggleTag(value)} + />
- notesStore.toggleSort(value.value)} - defaultValue={sortOptions[0].value} - /> + ({ key: value, label })), + onClick: ({ key }) => { + notesStore.toggleShared(key === '1'); + }, + }} + > +
+
+ {notesStore.ownOnly ? notesOwner[1].label : notesOwner[0].label} +
+ +
+
+
+ ({ key: value, label })), + onClick: ({ key }) => { + notesStore.toggleSort(key); + }, + }} + > +
+
+ {notesStore.order === "DESC" ? "Newest" : "Oldest"} +
+ +
+
); } diff --git a/frontend/app/components/shared/SessionsTabOverview/components/SessionSort/SessionSort.tsx b/frontend/app/components/shared/SessionsTabOverview/components/SessionSort/SessionSort.tsx index 9944f2cfd..5d25232ba 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/SessionSort/SessionSort.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/SessionSort/SessionSort.tsx @@ -1,4 +1,5 @@ -import { Select } from 'antd'; +import { DownOutlined } from '@ant-design/icons'; +import { Dropdown } from 'antd'; import React from 'react'; import { connect } from 'react-redux'; @@ -13,8 +14,9 @@ const sortOptionsMap = { }; const sortOptions = Object.entries(sortOptionsMap).map(([value, label]) => ({ - value, + // value, label, + key: value, })); interface Props { @@ -26,21 +28,32 @@ interface Props { function SessionSort(props: Props) { const { sort, order } = props.filter; - const onSort = (value: string) => { - const [sort, order] = value.split('-'); + const onSort = ({ key }: { key: string }) => { + const [sort, order] = key.split('-'); const sign = order === 'desc' ? -1 : 1; props.applyFilter({ order, sort }); props.sort(sort, sign); }; const defaultOption = `${sort}-${order}`; + return ( -