From d7b37470efd99282d97dd4121e3c7aab62f76afe Mon Sep 17 00:00:00 2001 From: Pavel Gnedov Date: Fri, 2 Feb 2024 07:51:50 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BB=D1=8F=20=D0=B4=D0=B0=D1=88=D0=B1?= =?UTF-8?q?=D0=BE=D1=80=D0=B4=D0=B0=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=20=D0=B2=D0=B8=D0=B4=D0=B6=D0=B5=D1=82=20=D0=BA=D0=B0=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B4=D0=B0=D1=80=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/calendar-next-events.tsx | 202 ++++++++++++++++++ .../src/dashboard/widgets/widget-factory.tsx | 5 + 2 files changed, 207 insertions(+) create mode 100644 frontend/src/dashboard/widgets/calendar-next-events.tsx diff --git a/frontend/src/dashboard/widgets/calendar-next-events.tsx b/frontend/src/dashboard/widgets/calendar-next-events.tsx new file mode 100644 index 0000000..61322f1 --- /dev/null +++ b/frontend/src/dashboard/widgets/calendar-next-events.tsx @@ -0,0 +1,202 @@ +import { observer } from 'mobx-react-lite'; +import { Instance, onSnapshot, types } from 'mobx-state-tree'; +import React from 'react'; +import * as Luxon from 'luxon'; +import * as DashboardStoreNs from '../dashboard-store'; +import { DateTime } from 'luxon'; +import { IssueHref } from '../../misc-components/issue-href'; + +export const CalendarEvent = types.model({ + from: types.string, + fromTimestamp: types.number, + to: types.string, + toTimestamp: types.number, + fullDay: types.boolean, + description: types.string, +}); + +export type ICalendarEvent = Instance; + +export const FormattedDateTime = types.model({ + fromDate: types.string, + fromTime: types.string, + toDate: types.string, + toTime: types.string, + interval: types.string, +}); + +export type IFormattedDateTime = Instance; + +export const IssueAndEvent = types.model({ + issue: types.frozen(), + event: CalendarEvent, +}); + +export type IIssueAndEvent = Instance; + +type InDay = { + order: number; + relativeDate: string | null; + formattedDate: string; + events: string[]; +}; + +export const DATE_FORMAT = 'dd.MM.yyyy'; +export const TIME_FORMAT = 'HH:mm'; + +function getEventKey(e: IIssueAndEvent): string { + const description: string = e.event.description.replaceAll(/\s+/g, '_'); + const fromKey = String(e.event.fromTimestamp); + const toKey = String(e.event.toTimestamp); + return `${e.issue.id}-${fromKey}-${toKey}-${description}`; +} + +export const EventsStore = types + .model({ + events: types.array(IssueAndEvent), + }) + .views((self) => { + const getRelativeDate = (issueAndEvent: IIssueAndEvent): string | null => { + const from = Luxon.DateTime.fromMillis(issueAndEvent.event.fromTimestamp); + return from.toRelativeCalendar(); + }; + + const getStartOfDayTimestamp = (issueAndEvent: IIssueAndEvent): number => { + const from = Luxon.DateTime.fromMillis(issueAndEvent.event.fromTimestamp); + return from.startOf('day').toMillis(); + }; + + return { + eventsMap: (): Record => { + return self.events.reduce((acc, issueAndEvent) => { + const key = getEventKey(issueAndEvent); + acc[key] = issueAndEvent; + return acc; + }, {} as Record); + }, + + orderedByDates: (): InDay[] => { + const res: Record = self.events.reduce( + (acc, issueAndEvent) => { + const order = getStartOfDayTimestamp(issueAndEvent); + const formattedDate = DateTime.fromMillis( + issueAndEvent.event.fromTimestamp, + ).toFormat(DATE_FORMAT); + if (!acc[order]) { + acc[order] = { + order: order, + relativeDate: getRelativeDate(issueAndEvent), + formattedDate: formattedDate, + events: [], + }; + } + const key = getEventKey(issueAndEvent); + acc[order].events.push(key); + return acc; + }, + {} as Record, + ); + return Object.values(res).sort((a, b) => a.order - b.order); + }, + + formattedDateTimes: (): Record => { + const res: Record = self.events.reduce( + (acc, event) => { + const key = getEventKey(event); + acc[key] = getFormattedDateTime(event); + return acc; + }, + {} as Record, + ); + return res; + }, + }; + }) + .actions((self) => { + return { + setEvents: (events: any): void => { + self.events = events; + }, + }; + }); + +export type IEventsStore = Instance; + +export type Props = { + store: DashboardStoreNs.IWidget; +}; + +function getFormattedDateTime( + issueAndEvent: IIssueAndEvent, +): IFormattedDateTime { + const from = DateTime.fromMillis(issueAndEvent.event.fromTimestamp); + const to = DateTime.fromMillis(issueAndEvent.event.toTimestamp); + const fromDate: string = from.isValid ? from.toFormat(DATE_FORMAT) : '-'; + const fromTime: string = from.isValid ? from.toFormat(TIME_FORMAT) : '-'; + const toDate: string = to.isValid ? to.toFormat(DATE_FORMAT) : '-'; + const toTime: string = to.isValid ? to.toFormat(TIME_FORMAT) : '-'; + let interval: string; + if (issueAndEvent.event.fullDay) { + interval = 'весь день'; + } else if (toTime != '-') { + interval = `${fromTime} - ${toTime}`; + } else { + interval = `${fromTime}`; + } + return FormattedDateTime.create({ + fromDate: fromDate, + fromTime: fromTime, + toDate: toDate, + toTime: toTime, + interval: interval, + }); +} + +export const CalendarList = observer( + (props: { store: IEventsStore }): JSX.Element => { + const list = props.store.orderedByDates().map((events) => { + const keyOfGroup = `${events.order}-${events.relativeDate}`; + const item = ( +
+

{events.relativeDate}:

+
    + {events.events.map((e) => { + const keyOfEvent = e; + const events = props.store.eventsMap(); + const formatted = props.store.formattedDateTimes(); + if (!events[keyOfEvent] && !formatted[keyOfEvent]) return <>; + const issue = events[keyOfEvent].issue; + return ( +
  • + {formatted[keyOfEvent].interval}:{' '} + +
  • + ); + })} +
+
+ ); + return item; + }); + return <>{list}; + }, +); + +export const CalendarNextEvents = observer((props: Props): JSX.Element => { + const calendarListStore = EventsStore.create(); + onSnapshot(props.store, (storeState) => { + if (storeState.data) { + calendarListStore.setEvents(storeState.data); + } + }); + return ( + <> + + + ); +}); diff --git a/frontend/src/dashboard/widgets/widget-factory.tsx b/frontend/src/dashboard/widgets/widget-factory.tsx index df2ece7..7f3e02b 100644 --- a/frontend/src/dashboard/widgets/widget-factory.tsx +++ b/frontend/src/dashboard/widgets/widget-factory.tsx @@ -5,6 +5,7 @@ import { observer } from 'mobx-react-lite'; import * as KanbanWidgetNs from './kanban'; import { DebugInfo } from '../../misc-components/debug-info'; import * as IssuesListNs from './issues-list'; +import * as CalendarNextEventsNs from './calendar-next-events'; export type Props = { store: Instance; @@ -21,6 +22,10 @@ export const WidgetFactory = observer((props: Props): JSX.Element => { return ; } + if (type === 'calendar_next_events') { + return ; + } + return (
Unknown widget