From 4acd6c1ccde448ae17424af82ca5eb3d2a4448f1 Mon Sep 17 00:00:00 2001 From: danilfomchik Date: Sun, 26 Apr 2026 21:46:00 +0300 Subject: [PATCH 1/2] changed cs to classNames --- src/components/common/Modal/index.tsx | 4 ++-- src/components/ui/icons/EditIcon.tsx | 4 ++-- src/components/ui/icons/VerticalDots.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/common/Modal/index.tsx b/src/components/common/Modal/index.tsx index 6d86984..3ba4817 100644 --- a/src/components/common/Modal/index.tsx +++ b/src/components/common/Modal/index.tsx @@ -1,4 +1,4 @@ -import cn from 'classnames'; +import classNames from 'classnames'; import {createPortal} from 'react-dom'; import {TModalProps} from './types'; @@ -19,7 +19,7 @@ const Modal = ({refItem, className, children, onClose}: TModalProps) => { {createPortal(
diff --git a/src/components/ui/icons/EditIcon.tsx b/src/components/ui/icons/EditIcon.tsx index e2d9fff..6d93122 100644 --- a/src/components/ui/icons/EditIcon.tsx +++ b/src/components/ui/icons/EditIcon.tsx @@ -1,4 +1,4 @@ -import cn from 'classnames'; +import classNames from 'classnames'; import {IconProps} from './types'; @@ -10,7 +10,7 @@ const EditIcon = ({size, ...restProps}: IconProps) => { viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" - className={cn('cursor-pointer', size)} + className={classNames('cursor-pointer', size)} {...restProps}> { viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" - className={cn('cursor-pointer', size)} + className={classNames('cursor-pointer', size)} {...restProps}> Date: Sun, 26 Apr 2026 21:55:40 +0300 Subject: [PATCH 2/2] added calendar for each event and ability to select events by calendar name Co-authored-by: Copilot --- src/components/common/EventForm/form.ts | 7 +- src/components/common/EventForm/index.tsx | 82 +++++++++++------ src/components/common/EventForm/types.ts | 4 +- .../EventsList/EventsListItem/index.tsx | 51 ++++++---- .../common/EventsList/EventsListItem/types.ts | 7 ++ src/components/common/EventsList/index.tsx | 10 +- .../common/formInputs/DropdownControl.tsx | 3 +- .../common/inputs/Dropdown/index.tsx | 25 +++-- .../common/inputs/Dropdown/types.ts | 2 + .../components/MyCalendarsList/index.tsx | 17 +++- .../layout/DataInitWrapper/index.tsx | 47 ++++++++++ src/components/layout/MainLayout/index.tsx | 3 + src/components/ui/Tooltip/index.tsx | 43 +++------ src/components/ui/Tooltip/types.ts | 3 +- src/components/ui/icons/types.ts | 2 +- src/hooks/useEventTooltip.ts | 30 ++++++ src/pages/Day/components/AIInfo/index.tsx | 19 +++- src/pages/Day/index.tsx | 9 +- .../Day/DayEventsList/Event/index.tsx | 22 ++++- .../Calendar/Day/DayEventsList/Event/types.ts | 1 + .../Day/DayEventsList/RemainedItems/index.tsx | 12 ++- .../Calendar/Day/DayEventsList/index.tsx | 1 + src/pages/Main/components/Header/index.tsx | 2 +- src/redux/events/eventsSlice.ts | 40 ++++---- src/redux/events/types.ts | 21 +++-- src/redux/listenerMiddleware.ts | 10 ++ .../listenersMiddleware/eventsListener.ts | 31 +++++++ src/redux/listenersMiddleware/index.ts | 2 + .../selectedCalendarListener.ts | 34 +++++++ src/redux/myCalendars/helpers.ts | 46 ++++++++++ src/redux/myCalendars/myCalendarsSlice.ts | 92 +++++++++++++------ src/redux/myCalendars/selectors.ts | 1 + src/redux/myCalendars/types.ts | 6 ++ src/redux/store.ts | 9 +- src/services/calendars.ts | 7 ++ src/services/constants.ts | 22 +++++ src/services/eventUtils.ts | 19 ++-- src/services/types.ts | 1 - src/services/utils.ts | 6 -- 39 files changed, 563 insertions(+), 186 deletions(-) create mode 100644 src/components/common/EventsList/EventsListItem/types.ts create mode 100644 src/components/layout/DataInitWrapper/index.tsx create mode 100644 src/hooks/useEventTooltip.ts create mode 100644 src/redux/listenerMiddleware.ts create mode 100644 src/redux/listenersMiddleware/eventsListener.ts create mode 100644 src/redux/listenersMiddleware/index.ts create mode 100644 src/redux/listenersMiddleware/selectedCalendarListener.ts create mode 100644 src/redux/myCalendars/helpers.ts create mode 100644 src/services/calendars.ts diff --git a/src/components/common/EventForm/form.ts b/src/components/common/EventForm/form.ts index e6919ad..cef2972 100644 --- a/src/components/common/EventForm/form.ts +++ b/src/components/common/EventForm/form.ts @@ -1,11 +1,14 @@ -import {object, string} from 'yup'; +import {mixed, object, string} from 'yup'; + +import {CalendarsNames} from '@/services/types'; export const validation = object().shape({ - eventName: string() + eventTitle: string() .required('Name is required') .test('empty-check', 'Event name can not be empty string.', name => !!name.trim().length), eventYear: string().required(), eventMonth: string().required(), eventDay: string().required(), eventDescription: string(), + eventCalendar: mixed().oneOf(Object.values(CalendarsNames)).required('Calendar is required'), }); diff --git a/src/components/common/EventForm/index.tsx b/src/components/common/EventForm/index.tsx index c008d19..6ccbf2f 100644 --- a/src/components/common/EventForm/index.tsx +++ b/src/components/common/EventForm/index.tsx @@ -1,7 +1,7 @@ import {yupResolver} from '@hookform/resolvers/yup'; -import cn from 'classnames'; +import classNames from 'classnames'; import moment from 'moment'; -import {useMemo} from 'react'; +import {useCallback, useMemo} from 'react'; import {FormProvider, SubmitHandler, useForm} from 'react-hook-form'; import {useSelector} from 'react-redux'; @@ -14,9 +14,12 @@ import CloseIcon from '@/components/ui/icons/CloseIcon'; import EditIcon from '@/components/ui/icons/EditIcon'; import {selectDay, selectMonth, selectYear} from '@/redux/date/selectors'; import {addEvent, editEvent} from '@/redux/events/eventsSlice'; +import {selectSelectedCalendar} from '@/redux/myCalendars/selectors'; import {useAppDispatch} from '@/redux/store'; +import {defaultCalendars} from '@/services/constants'; import {createDate, formatDate, getDays, getMonthsOptions, getYearsOptions} from '@/services/dateUtils'; import {createEventObj, editEventObj} from '@/services/eventUtils'; +import {CalendarsNames} from '@/services/types'; import {validation} from './form'; import {FormActionType, TEventFormProps, TFormValues} from './types'; @@ -26,22 +29,16 @@ const EventForm = ({actionType = FormActionType.create, formTitle, event, date, const year = useSelector(selectYear); const month = useSelector(selectMonth); const day = useSelector(selectDay); - - const defaultValues = event - ? { - eventName: event.title, - eventDescription: event.description, - eventYear: formatDate(moment(event.date), 'YYYY'), - eventMonth: formatDate(moment(event.date), 'MMMM'), - eventDay: formatDate(moment(event.date), 'DD'), - } - : { - eventName: '', - eventYear: formatDate(moment(date), 'YYYY') || year || '', - eventMonth: formatDate(moment(date), 'MMMM') || month || '', - eventDay: formatDate(moment(date), 'DD') || day || '', - eventDescription: '', - }; + const selectedCalendar = useSelector(selectSelectedCalendar); + + const defaultValues = { + eventTitle: event?.title ?? '', + eventDescription: event?.description ?? '', + eventYear: formatDate(moment(event?.date || date), 'YYYY') || year || '', + eventMonth: formatDate(moment(event?.date || date), 'MMMM') || month || '', + eventDay: formatDate(moment(event?.date || date), 'DD') || day || '', + eventCalendar: event?.eventCalendar ?? CalendarsNames.personal, + }; const methods = useForm({ resolver: yupResolver(validation), @@ -66,32 +63,37 @@ const EventForm = ({actionType = FormActionType.create, formTitle, event, date, () => getDays(formYearValue || '', formMonthValue || ''), [formYearValue, formMonthValue], ); + const calendarOptions = Object.values(CalendarsNames); const handleCreateEvent = (eventData: TFormValues) => { - const {eventName, eventYear, eventMonth, eventDay, eventDescription} = eventData; + const {eventTitle, eventYear, eventMonth, eventDay, eventDescription, eventCalendar} = eventData; const newEvent = createEventObj({ - eventName, + title: eventTitle, date: createDate(+eventYear, eventMonth, +eventDay), description: eventDescription, + eventCalendar, + isDisabled: !selectedCalendar ? false : selectedCalendar !== eventCalendar, }); dispatch(addEvent(newEvent)); }; const handleEditEvent = (eventData: TFormValues) => { - const {eventName, eventYear, eventMonth, eventDay, eventDescription} = eventData; + const {eventTitle, eventYear, eventMonth, eventDay, eventDescription, eventCalendar} = eventData; const editedEvent = editEventObj({ event, updatedEvent: { - eventName, + title: eventTitle, date: createDate(+eventYear, eventMonth, +eventDay), description: eventDescription, + eventCalendar, + isDisabled: !selectedCalendar ? event?.isDisabled || false : selectedCalendar !== eventCalendar, }, }); - dispatch(editEvent(editedEvent)); + dispatch(editEvent({oldEvent: event!, newEvent: editedEvent})); }; const onSubmit: SubmitHandler = eventData => { @@ -104,6 +106,17 @@ const EventForm = ({actionType = FormActionType.create, formTitle, event, date, handleModalClose(); }; + const customEventCalendarOption = useCallback((option: string) => { + const currentCalendar = defaultCalendars.find(c => c.name === option); + + return ( +
+
+ {option} +
+ ); + }, []); + return (
)} - {/* TODO: add d&d */}
diff --git a/src/components/common/EventsList/EventsListItem/types.ts b/src/components/common/EventsList/EventsListItem/types.ts new file mode 100644 index 0000000..7410cf9 --- /dev/null +++ b/src/components/common/EventsList/EventsListItem/types.ts @@ -0,0 +1,7 @@ +import {TEvent} from '@/redux/events/types'; + +export interface IEventsListItemProps { + event: TEvent; + showItemControls: boolean; + isDisabled?: boolean; +} diff --git a/src/components/common/EventsList/index.tsx b/src/components/common/EventsList/index.tsx index 24a15f0..14e5b44 100644 --- a/src/components/common/EventsList/index.tsx +++ b/src/components/common/EventsList/index.tsx @@ -45,12 +45,18 @@ const EventsList = ({
- {/*
*/} {!events?.length ? (

no events

) : (
- {events?.map(event => )} + {events?.map(event => ( + + ))}
)}
diff --git a/src/components/common/formInputs/DropdownControl.tsx b/src/components/common/formInputs/DropdownControl.tsx index b583e68..fd97967 100644 --- a/src/components/common/formInputs/DropdownControl.tsx +++ b/src/components/common/formInputs/DropdownControl.tsx @@ -1,4 +1,4 @@ -import {memo} from 'react'; +import {ReactNode, memo} from 'react'; import {Control, useController} from 'react-hook-form'; import Dropdown from '../inputs/Dropdown'; @@ -9,6 +9,7 @@ type DropdownControlProps = { control: Control; options: string[]; selectedOption?: string; + customOption?: (option: string) => ReactNode; }; const DropdownControl = ({name, control, selectedOption = '', ...restProps}: DropdownControlProps) => { diff --git a/src/components/common/inputs/Dropdown/index.tsx b/src/components/common/inputs/Dropdown/index.tsx index cb148be..c2e5865 100644 --- a/src/components/common/inputs/Dropdown/index.tsx +++ b/src/components/common/inputs/Dropdown/index.tsx @@ -15,6 +15,7 @@ const Dropdown = ({ options, placeholder = 'Choose an option', className = '', + customOption, }: TDropdownProps) => { const [currentValue, setCurrentValue] = useState(selectedOption); @@ -53,10 +54,16 @@ const Dropdown = ({ return (
{isOpen && ( <> @@ -92,8 +100,9 @@ const Dropdown = ({ 'transition-all flex items-center justify-between gap-1 cursor-pointer text-white select-none relative py-2 px-3 hover:bg-secondary-background-color', )} onClick={() => handleChange(option)}> - {option} - {currentValue === option && } + {customOption ? customOption(option) : {option}} + + {currentValue === option && } ))} diff --git a/src/components/common/inputs/Dropdown/types.ts b/src/components/common/inputs/Dropdown/types.ts index 6e39baf..93aeb17 100644 --- a/src/components/common/inputs/Dropdown/types.ts +++ b/src/components/common/inputs/Dropdown/types.ts @@ -1,3 +1,4 @@ +import {ReactNode} from 'react'; import {ControllerRenderProps, FieldValues} from 'react-hook-form'; export type Option = { @@ -13,4 +14,5 @@ export type TDropdownProps = { placeholder?: string; className?: string; setDropDownRef?: (node: HTMLDivElement | null) => void; + customOption?: (option: string) => ReactNode; }; diff --git a/src/components/layout/Aside/components/MyCalendarsList/index.tsx b/src/components/layout/Aside/components/MyCalendarsList/index.tsx index 0926b5d..6f0066f 100644 --- a/src/components/layout/Aside/components/MyCalendarsList/index.tsx +++ b/src/components/layout/Aside/components/MyCalendarsList/index.tsx @@ -1,16 +1,23 @@ +import moment from 'moment'; import {useSelector} from 'react-redux'; +import {selectFullDate} from '@/redux/date/selectors'; import {setSelectedCalendar} from '@/redux/myCalendars/myCalendarsSlice'; -import {selectCalendarsList, selectSelectedCalendar} from '@/redux/myCalendars/selectors'; +import {selectCalendarsList, selectCalendarsMap, selectSelectedCalendar} from '@/redux/myCalendars/selectors'; import {useAppDispatch} from '@/redux/store'; +import {formatDate} from '@/services/dateUtils'; import {CalendarsNames} from '@/services/types'; import {cx} from '@/services/utils'; const MyCalendarsList = () => { const dispatch = useAppDispatch(); - // TODO: for day page - filter by date const calendarsList = useSelector(selectCalendarsList); const selectedCalendar = useSelector(selectSelectedCalendar); + const calendarsMap = useSelector(selectCalendarsMap); + const fullDate = useSelector(selectFullDate); + + const yearMonthKey = formatDate(moment(fullDate), 'YYYY-MM'); + const currentCalendarsData = calendarsMap[yearMonthKey]; const selectCalendar = (name: CalendarsNames) => { if (selectedCalendar === name) { @@ -25,8 +32,9 @@ const MyCalendarsList = () => {

My calendars

- {calendarsList?.map(({name, count, itemColor}) => { + {calendarsList?.map(({name, itemColor}) => { const isSelected = selectedCalendar === name; + const count = currentCalendarsData?.[name]; return (
{ boxShadow: isSelected ? `0 0 0 2px #0a0a0a, 0 0 0 3px ${itemColor}` : '', }}>

{name}

- {count} + + {!!count && {count}}
); })} diff --git a/src/components/layout/DataInitWrapper/index.tsx b/src/components/layout/DataInitWrapper/index.tsx new file mode 100644 index 0000000..a859173 --- /dev/null +++ b/src/components/layout/DataInitWrapper/index.tsx @@ -0,0 +1,47 @@ +import moment from 'moment'; +import {useEffect, useMemo} from 'react'; + +import {TEvent} from '@/redux/events/types'; +import {handleEventsCountChangeInMap} from '@/redux/myCalendars/helpers'; +import {setCalendarsMap} from '@/redux/myCalendars/myCalendarsSlice'; +import {EventsCountChangeKind} from '@/redux/myCalendars/types'; +import {useAppDispatch} from '@/redux/store'; +import {formatDate} from '@/services/dateUtils'; +import {CalendarsNames, StorageKeys} from '@/services/types'; +import {getLocalStoredValues} from '@/services/utils'; + +const DataInitWrapper = () => { + const dispatch = useAppDispatch(); + + const calendarsMap = useMemo(() => { + let eventsCalendarsMap = {} as Record>; + + // list of stored events [{id: event}, ...] + const initialEventsById = getLocalStoredValues(StorageKeys.eventsById, {}); + // get only events values from stored object + const eventsValues: TEvent[] = Object.values(initialEventsById); + + // generate map of events count for each calendar for current month {work: 3, personal: 5, ...} + eventsValues.forEach(event => { + const calendarName = event.eventCalendar; + const yearMonthKey = formatDate(moment(event.date), 'YYYY-MM'); + + eventsCalendarsMap = handleEventsCountChangeInMap( + eventsCalendarsMap, + yearMonthKey, + calendarName, + EventsCountChangeKind.ADD, + ); + }); + + return eventsCalendarsMap; + }, []); + + useEffect(() => { + dispatch(setCalendarsMap(calendarsMap)); + }, [calendarsMap, dispatch]); + + return null; +}; + +export default DataInitWrapper; diff --git a/src/components/layout/MainLayout/index.tsx b/src/components/layout/MainLayout/index.tsx index 6cf19d9..66c1929 100644 --- a/src/components/layout/MainLayout/index.tsx +++ b/src/components/layout/MainLayout/index.tsx @@ -6,6 +6,7 @@ import Loading from '@/components/ui/Loading'; import {selectOpenedItems} from '@/redux/overflow/selectors'; import Aside from '../Aside'; +import DataInitWrapper from '../DataInitWrapper'; const MainLayout = () => { const openedItems = useSelector(selectOpenedItems); @@ -24,6 +25,8 @@ const MainLayout = () => { return ( <> + +