Для дашборда создан виджет календаря

This commit is contained in:
Pavel Gnedov 2024-02-02 07:51:50 +07:00
parent 5ec0c1d99e
commit d7b37470ef
2 changed files with 207 additions and 0 deletions

View file

@ -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<typeof CalendarEvent>;
export const FormattedDateTime = types.model({
fromDate: types.string,
fromTime: types.string,
toDate: types.string,
toTime: types.string,
interval: types.string,
});
export type IFormattedDateTime = Instance<typeof FormattedDateTime>;
export const IssueAndEvent = types.model({
issue: types.frozen<any>(),
event: CalendarEvent,
});
export type IIssueAndEvent = Instance<typeof IssueAndEvent>;
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<string, IIssueAndEvent> => {
return self.events.reduce((acc, issueAndEvent) => {
const key = getEventKey(issueAndEvent);
acc[key] = issueAndEvent;
return acc;
}, {} as Record<string, IIssueAndEvent>);
},
orderedByDates: (): InDay[] => {
const res: Record<number, InDay> = 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<number, InDay>,
);
return Object.values(res).sort((a, b) => a.order - b.order);
},
formattedDateTimes: (): Record<string, IFormattedDateTime> => {
const res: Record<string, IFormattedDateTime> = self.events.reduce(
(acc, event) => {
const key = getEventKey(event);
acc[key] = getFormattedDateTime(event);
return acc;
},
{} as Record<string, IFormattedDateTime>,
);
return res;
},
};
})
.actions((self) => {
return {
setEvents: (events: any): void => {
self.events = events;
},
};
});
export type IEventsStore = Instance<typeof EventsStore>;
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 = (
<div key={keyOfGroup}>
<p title={events.formattedDate}>{events.relativeDate}:</p>
<ul>
{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 (
<li key={keyOfEvent}>
{formatted[keyOfEvent].interval}:{' '}
<IssueHref
id={issue.id}
subject={events[keyOfEvent].event.description}
tracker={issue.tracker.name}
url={issue.url.url}
/>
</li>
);
})}
</ul>
</div>
);
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 (
<>
<CalendarList store={calendarListStore} />
</>
);
});

View file

@ -5,6 +5,7 @@ import { observer } from 'mobx-react-lite';
import * as KanbanWidgetNs from './kanban'; import * as KanbanWidgetNs from './kanban';
import { DebugInfo } from '../../misc-components/debug-info'; import { DebugInfo } from '../../misc-components/debug-info';
import * as IssuesListNs from './issues-list'; import * as IssuesListNs from './issues-list';
import * as CalendarNextEventsNs from './calendar-next-events';
export type Props = { export type Props = {
store: Instance<typeof DashboardStoreNs.Widget>; store: Instance<typeof DashboardStoreNs.Widget>;
@ -21,6 +22,10 @@ export const WidgetFactory = observer((props: Props): JSX.Element => {
return <IssuesListNs.IssuesList store={props.store} />; return <IssuesListNs.IssuesList store={props.store} />;
} }
if (type === 'calendar_next_events') {
return <CalendarNextEventsNs.CalendarNextEvents store={props.store} />;
}
return ( return (
<div> <div>
<div>Unknown widget</div> <div>Unknown widget</div>