@isma91/react-scheduler 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +29 -0
- package/.github/workflows/tests.yml +35 -0
- package/.gitignore +32 -0
- package/.husky/pre-commit +2 -0
- package/.prettierignore +1 -0
- package/.prettierrc.json +7 -0
- package/.yarnrc.yml +1 -0
- package/LICENSE +24 -0
- package/README.md +172 -0
- package/dist/LICENSE +24 -0
- package/dist/README.md +172 -0
- package/dist/SchedulerComponent.d.ts +3 -0
- package/dist/components/common/Cell.d.ts +13 -0
- package/dist/components/common/LocaleArrow.d.ts +8 -0
- package/dist/components/common/ResourceHeader.d.ts +6 -0
- package/dist/components/common/Tabs.d.ts +16 -0
- package/dist/components/common/TodayTypo.d.ts +8 -0
- package/dist/components/common/WithResources.d.ts +6 -0
- package/dist/components/events/Actions.d.ts +8 -0
- package/dist/components/events/AgendaEventsList.d.ts +7 -0
- package/dist/components/events/CurrentTimeBar.d.ts +11 -0
- package/dist/components/events/EmptyAgenda.d.ts +2 -0
- package/dist/components/events/EventItem.d.ts +10 -0
- package/dist/components/events/EventItemPopover.d.ts +9 -0
- package/dist/components/events/MonthEvents.d.ts +13 -0
- package/dist/components/events/TodayEvents.d.ts +16 -0
- package/dist/components/hoc/DateProvider.d.ts +5 -0
- package/dist/components/inputs/DatePicker.d.ts +14 -0
- package/dist/components/inputs/Input.d.ts +19 -0
- package/dist/components/inputs/SelectInput.d.ts +22 -0
- package/dist/components/month/MonthTable.d.ts +8 -0
- package/dist/components/nav/DayDateBtn.d.ts +6 -0
- package/dist/components/nav/MonthDateBtn.d.ts +6 -0
- package/dist/components/nav/Navigation.d.ts +3 -0
- package/dist/components/nav/WeekDateBtn.d.ts +8 -0
- package/dist/components/week/WeekTable.d.ts +11 -0
- package/dist/helpers/constants.d.ts +4 -0
- package/dist/helpers/generals.d.ts +78 -0
- package/dist/hooks/useArrowDisable.d.ts +5 -0
- package/dist/hooks/useCellAttributes.d.ts +18 -0
- package/dist/hooks/useDragAttributes.d.ts +10 -0
- package/dist/hooks/useEventPermissions.d.ts +7 -0
- package/dist/hooks/useStore.d.ts +2 -0
- package/dist/hooks/useSyncScroll.d.ts +8 -0
- package/dist/hooks/useWindowResize.d.ts +4 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2853 -0
- package/dist/package.json +65 -0
- package/dist/positionManger/context.d.ts +14 -0
- package/dist/positionManger/provider.d.ts +5 -0
- package/dist/positionManger/usePosition.d.ts +4 -0
- package/dist/store/context.d.ts +2 -0
- package/dist/store/default.d.ts +245 -0
- package/dist/store/provider.d.ts +7 -0
- package/dist/store/types.d.ts +27 -0
- package/dist/styles/styles.d.ts +30 -0
- package/dist/types.d.ts +372 -0
- package/dist/views/Day.d.ts +2 -0
- package/dist/views/DayAgenda.d.ts +7 -0
- package/dist/views/Editor.d.ts +11 -0
- package/dist/views/Month.d.ts +2 -0
- package/dist/views/MonthAgenda.d.ts +7 -0
- package/dist/views/Week.d.ts +2 -0
- package/dist/views/WeekAgenda.d.ts +8 -0
- package/eslint.config.js +79 -0
- package/index.html +41 -0
- package/jest.config.ts +194 -0
- package/package.json +137 -0
- package/public/favicon.ico +0 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/scripts/post-pack.js +34 -0
- package/src/App.tsx +25 -0
- package/src/Page1.tsx +67 -0
- package/src/events.tsx +227 -0
- package/src/index.tsx +21 -0
- package/src/lib/SchedulerComponent.tsx +78 -0
- package/src/lib/__tests__/index.test.tsx +24 -0
- package/src/lib/components/common/Cell.tsx +52 -0
- package/src/lib/components/common/LocaleArrow.tsx +38 -0
- package/src/lib/components/common/ResourceHeader.tsx +73 -0
- package/src/lib/components/common/Tabs.tsx +119 -0
- package/src/lib/components/common/TodayTypo.tsx +44 -0
- package/src/lib/components/common/WithResources.tsx +98 -0
- package/src/lib/components/events/Actions.tsx +65 -0
- package/src/lib/components/events/AgendaEventsList.tsx +115 -0
- package/src/lib/components/events/CurrentTimeBar.tsx +59 -0
- package/src/lib/components/events/EmptyAgenda.tsx +27 -0
- package/src/lib/components/events/EventItem.tsx +180 -0
- package/src/lib/components/events/EventItemPopover.tsx +179 -0
- package/src/lib/components/events/MonthEvents.tsx +141 -0
- package/src/lib/components/events/TodayEvents.tsx +99 -0
- package/src/lib/components/hoc/DateProvider.tsx +19 -0
- package/src/lib/components/inputs/DatePicker.tsx +95 -0
- package/src/lib/components/inputs/Input.tsx +113 -0
- package/src/lib/components/inputs/SelectInput.tsx +164 -0
- package/src/lib/components/month/MonthTable.tsx +207 -0
- package/src/lib/components/nav/DayDateBtn.tsx +77 -0
- package/src/lib/components/nav/MonthDateBtn.tsx +80 -0
- package/src/lib/components/nav/Navigation.tsx +201 -0
- package/src/lib/components/nav/WeekDateBtn.tsx +89 -0
- package/src/lib/components/week/WeekTable.tsx +229 -0
- package/src/lib/helpers/constants.ts +4 -0
- package/src/lib/helpers/generals.tsx +354 -0
- package/src/lib/hooks/useArrowDisable.ts +26 -0
- package/src/lib/hooks/useCellAttributes.ts +67 -0
- package/src/lib/hooks/useDragAttributes.ts +31 -0
- package/src/lib/hooks/useEventPermissions.ts +42 -0
- package/src/lib/hooks/useStore.ts +8 -0
- package/src/lib/hooks/useSyncScroll.ts +31 -0
- package/src/lib/hooks/useWindowResize.ts +37 -0
- package/src/lib/index.tsx +14 -0
- package/src/lib/positionManger/context.ts +14 -0
- package/src/lib/positionManger/provider.tsx +113 -0
- package/src/lib/positionManger/usePosition.ts +8 -0
- package/src/lib/store/context.ts +5 -0
- package/src/lib/store/default.ts +157 -0
- package/src/lib/store/provider.tsx +211 -0
- package/src/lib/store/types.ts +33 -0
- package/src/lib/styles/styles.ts +256 -0
- package/src/lib/types.ts +423 -0
- package/src/lib/views/Day.tsx +265 -0
- package/src/lib/views/DayAgenda.tsx +57 -0
- package/src/lib/views/Editor.tsx +258 -0
- package/src/lib/views/Month.tsx +82 -0
- package/src/lib/views/MonthAgenda.tsx +84 -0
- package/src/lib/views/Week.tsx +92 -0
- package/src/lib/views/WeekAgenda.tsx +81 -0
- package/src/vite-env.d.ts +3 -0
- package/tsconfig.build.json +5 -0
- package/tsconfig.json +27 -0
- package/vite.config.js +40 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { MouseEvent } from "react";
|
|
2
|
+
import { Box, IconButton, Popover, Typography, useTheme } from "@mui/material";
|
|
3
|
+
import useStore from "../../hooks/useStore";
|
|
4
|
+
import { ProcessedEvent } from "../../types";
|
|
5
|
+
import { PopperInner } from "../../styles/styles";
|
|
6
|
+
import EventActions from "./Actions";
|
|
7
|
+
import { differenceInDaysOmitTime, getHourFormat } from "../../helpers/generals";
|
|
8
|
+
import EventNoteRoundedIcon from "@mui/icons-material/EventNoteRounded";
|
|
9
|
+
import ClearRoundedIcon from "@mui/icons-material/ClearRounded";
|
|
10
|
+
import SupervisorAccountRoundedIcon from "@mui/icons-material/SupervisorAccountRounded";
|
|
11
|
+
import { format } from "date-fns";
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
event: ProcessedEvent;
|
|
15
|
+
anchorEl: Element | null;
|
|
16
|
+
onTriggerViewer: (el?: MouseEvent<Element>) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const EventItemPopover = ({ anchorEl, event, onTriggerViewer }: Props) => {
|
|
20
|
+
const {
|
|
21
|
+
triggerDialog,
|
|
22
|
+
onDelete,
|
|
23
|
+
events,
|
|
24
|
+
handleState,
|
|
25
|
+
triggerLoading,
|
|
26
|
+
customViewer,
|
|
27
|
+
viewerExtraComponent,
|
|
28
|
+
fields,
|
|
29
|
+
resources,
|
|
30
|
+
resourceFields,
|
|
31
|
+
locale,
|
|
32
|
+
viewerTitleComponent,
|
|
33
|
+
viewerSubtitleComponent,
|
|
34
|
+
hourFormat,
|
|
35
|
+
translations,
|
|
36
|
+
onEventEdit,
|
|
37
|
+
} = useStore();
|
|
38
|
+
const theme = useTheme();
|
|
39
|
+
const hFormat = getHourFormat(hourFormat);
|
|
40
|
+
const displayStart = event._originalStart || event.start;
|
|
41
|
+
const displayEnd = event._originalEnd || event.end;
|
|
42
|
+
const hideDates = differenceInDaysOmitTime(displayStart, displayEnd) <= 0 && event.allDay;
|
|
43
|
+
|
|
44
|
+
const idKey = resourceFields.idField;
|
|
45
|
+
const hasResource = resources.filter((res) =>
|
|
46
|
+
Array.isArray(event[idKey]) ? event[idKey].includes(res[idKey]) : res[idKey] === event[idKey]
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const handleDelete = async () => {
|
|
50
|
+
try {
|
|
51
|
+
triggerLoading(true);
|
|
52
|
+
let deletedId = event.event_id;
|
|
53
|
+
// Trigger custom/remote when provided
|
|
54
|
+
if (onDelete) {
|
|
55
|
+
const remoteId = await onDelete(deletedId);
|
|
56
|
+
if (remoteId) {
|
|
57
|
+
deletedId = remoteId;
|
|
58
|
+
} else {
|
|
59
|
+
deletedId = "";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (deletedId) {
|
|
63
|
+
onTriggerViewer();
|
|
64
|
+
const updatedEvents = events.filter((e) => e.event_id !== deletedId);
|
|
65
|
+
handleState(updatedEvents, "events");
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(error);
|
|
69
|
+
} finally {
|
|
70
|
+
triggerLoading(false);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Popover
|
|
76
|
+
open={Boolean(anchorEl)}
|
|
77
|
+
anchorEl={anchorEl}
|
|
78
|
+
onClose={() => {
|
|
79
|
+
onTriggerViewer();
|
|
80
|
+
}}
|
|
81
|
+
anchorOrigin={{
|
|
82
|
+
vertical: "center",
|
|
83
|
+
horizontal: "center",
|
|
84
|
+
}}
|
|
85
|
+
transformOrigin={{
|
|
86
|
+
vertical: "top",
|
|
87
|
+
horizontal: "center",
|
|
88
|
+
}}
|
|
89
|
+
onClick={(e) => {
|
|
90
|
+
e.stopPropagation();
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
{typeof customViewer === "function" ? (
|
|
94
|
+
customViewer(event, () => onTriggerViewer())
|
|
95
|
+
) : (
|
|
96
|
+
<PopperInner>
|
|
97
|
+
<Box
|
|
98
|
+
sx={{
|
|
99
|
+
bgcolor: event.color || theme.palette.primary.main,
|
|
100
|
+
color: theme.palette.primary.contrastText,
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
<div className="rs__popper_actions">
|
|
104
|
+
<div>
|
|
105
|
+
<IconButton
|
|
106
|
+
size="small"
|
|
107
|
+
onClick={() => {
|
|
108
|
+
onTriggerViewer();
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<ClearRoundedIcon color="disabled" />
|
|
112
|
+
</IconButton>
|
|
113
|
+
</div>
|
|
114
|
+
<EventActions
|
|
115
|
+
event={event}
|
|
116
|
+
onDelete={handleDelete}
|
|
117
|
+
onEdit={() => {
|
|
118
|
+
onTriggerViewer();
|
|
119
|
+
triggerDialog(true, event);
|
|
120
|
+
|
|
121
|
+
if (onEventEdit && typeof onEventEdit === "function") {
|
|
122
|
+
onEventEdit(event);
|
|
123
|
+
}
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
126
|
+
</div>
|
|
127
|
+
{viewerTitleComponent instanceof Function ? (
|
|
128
|
+
viewerTitleComponent(event)
|
|
129
|
+
) : (
|
|
130
|
+
<Typography style={{ padding: "5px 0" }} noWrap>
|
|
131
|
+
{event.title}
|
|
132
|
+
</Typography>
|
|
133
|
+
)}
|
|
134
|
+
</Box>
|
|
135
|
+
<div style={{ padding: "5px 10px" }}>
|
|
136
|
+
<Typography
|
|
137
|
+
style={{ display: "flex", alignItems: "center", gap: 8 }}
|
|
138
|
+
color="textSecondary"
|
|
139
|
+
variant="caption"
|
|
140
|
+
noWrap
|
|
141
|
+
>
|
|
142
|
+
<EventNoteRoundedIcon />
|
|
143
|
+
{hideDates
|
|
144
|
+
? translations.event.allDay
|
|
145
|
+
: `${format(displayStart, `dd MMMM yyyy ${hFormat}`, {
|
|
146
|
+
locale: locale,
|
|
147
|
+
})} - ${format(displayEnd, `dd MMMM yyyy ${hFormat}`, {
|
|
148
|
+
locale: locale,
|
|
149
|
+
})}`}
|
|
150
|
+
</Typography>
|
|
151
|
+
{viewerSubtitleComponent instanceof Function ? (
|
|
152
|
+
viewerSubtitleComponent(event)
|
|
153
|
+
) : (
|
|
154
|
+
<Typography variant="body2" style={{ padding: "5px 0" }}>
|
|
155
|
+
{event.subtitle}
|
|
156
|
+
</Typography>
|
|
157
|
+
)}
|
|
158
|
+
{hasResource.length > 0 && (
|
|
159
|
+
<Typography
|
|
160
|
+
style={{ display: "flex", alignItems: "center", gap: 8 }}
|
|
161
|
+
color="textSecondary"
|
|
162
|
+
variant="caption"
|
|
163
|
+
noWrap
|
|
164
|
+
>
|
|
165
|
+
<SupervisorAccountRoundedIcon />
|
|
166
|
+
{hasResource.map((res) => res[resourceFields.textField]).join(", ")}
|
|
167
|
+
</Typography>
|
|
168
|
+
)}
|
|
169
|
+
{viewerExtraComponent instanceof Function
|
|
170
|
+
? viewerExtraComponent(fields, event)
|
|
171
|
+
: viewerExtraComponent}
|
|
172
|
+
</div>
|
|
173
|
+
</PopperInner>
|
|
174
|
+
)}
|
|
175
|
+
</Popover>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export default EventItemPopover;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Fragment, useMemo } from "react";
|
|
2
|
+
import {
|
|
3
|
+
closestTo,
|
|
4
|
+
isBefore,
|
|
5
|
+
startOfWeek,
|
|
6
|
+
differenceInDays,
|
|
7
|
+
differenceInCalendarWeeks,
|
|
8
|
+
format,
|
|
9
|
+
} from "date-fns";
|
|
10
|
+
import { ProcessedEvent } from "../../types";
|
|
11
|
+
import { Typography } from "@mui/material";
|
|
12
|
+
import EventItem from "./EventItem";
|
|
13
|
+
import {
|
|
14
|
+
MONTH_BAR_HEIGHT,
|
|
15
|
+
MONTH_NUMBER_HEIGHT,
|
|
16
|
+
MULTI_DAY_EVENT_HEIGHT,
|
|
17
|
+
} from "../../helpers/constants";
|
|
18
|
+
import { convertEventTimeZone, differenceInDaysOmitTime } from "../../helpers/generals";
|
|
19
|
+
import useStore from "../../hooks/useStore";
|
|
20
|
+
import usePosition from "../../positionManger/usePosition";
|
|
21
|
+
|
|
22
|
+
interface MonthEventProps {
|
|
23
|
+
events: ProcessedEvent[];
|
|
24
|
+
resourceId?: string;
|
|
25
|
+
today: Date;
|
|
26
|
+
eachWeekStart: Date[];
|
|
27
|
+
eachFirstDayInCalcRow: Date | null;
|
|
28
|
+
daysList: Date[];
|
|
29
|
+
onViewMore(day: Date): void;
|
|
30
|
+
cellHeight: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const MonthEvents = ({
|
|
34
|
+
events,
|
|
35
|
+
resourceId,
|
|
36
|
+
today,
|
|
37
|
+
eachWeekStart,
|
|
38
|
+
eachFirstDayInCalcRow,
|
|
39
|
+
daysList,
|
|
40
|
+
onViewMore,
|
|
41
|
+
cellHeight,
|
|
42
|
+
}: MonthEventProps) => {
|
|
43
|
+
const LIMIT = Math.round((cellHeight - MONTH_NUMBER_HEIGHT) / MULTI_DAY_EVENT_HEIGHT - 1);
|
|
44
|
+
const { translations, month, locale, timeZone } = useStore();
|
|
45
|
+
const { renderedSlots } = usePosition();
|
|
46
|
+
|
|
47
|
+
const renderEvents = useMemo(() => {
|
|
48
|
+
const elements: React.ReactNode[] = [];
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < Math.min(events.length, LIMIT + 1); i++) {
|
|
51
|
+
const event = convertEventTimeZone(events[i], timeZone);
|
|
52
|
+
const fromPrevWeek = !!eachFirstDayInCalcRow && isBefore(event.start, eachFirstDayInCalcRow);
|
|
53
|
+
const start = fromPrevWeek && eachFirstDayInCalcRow ? eachFirstDayInCalcRow : event.start;
|
|
54
|
+
let eventLength = differenceInDaysOmitTime(start, event.end) + 1;
|
|
55
|
+
|
|
56
|
+
const toNextWeek =
|
|
57
|
+
differenceInCalendarWeeks(event.end, start, {
|
|
58
|
+
weekStartsOn: month?.weekStartOn,
|
|
59
|
+
locale,
|
|
60
|
+
}) > 0;
|
|
61
|
+
|
|
62
|
+
if (toNextWeek) {
|
|
63
|
+
// Rethink it
|
|
64
|
+
const NotAccurateWeekStart = startOfWeek(event.start, {
|
|
65
|
+
weekStartsOn: month?.weekStartOn,
|
|
66
|
+
locale,
|
|
67
|
+
});
|
|
68
|
+
const closestStart = closestTo(NotAccurateWeekStart, eachWeekStart);
|
|
69
|
+
if (closestStart) {
|
|
70
|
+
eventLength =
|
|
71
|
+
daysList.length -
|
|
72
|
+
(!eachFirstDayInCalcRow ? differenceInDays(event.start, closestStart) : 0);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const day = format(today, "yyyy-MM-dd");
|
|
77
|
+
const rendered = renderedSlots?.[resourceId || "all"]?.[day];
|
|
78
|
+
const position = rendered?.[event.event_id] || 0;
|
|
79
|
+
|
|
80
|
+
const topSpace = Math.min(position, LIMIT) * MULTI_DAY_EVENT_HEIGHT + MONTH_NUMBER_HEIGHT;
|
|
81
|
+
|
|
82
|
+
if (position >= LIMIT) {
|
|
83
|
+
elements.push(
|
|
84
|
+
<Typography
|
|
85
|
+
key={i}
|
|
86
|
+
width="100%"
|
|
87
|
+
className="rs__multi_day rs__hover__op"
|
|
88
|
+
style={{ top: topSpace, fontSize: 11 }}
|
|
89
|
+
onClick={(e) => {
|
|
90
|
+
e.stopPropagation();
|
|
91
|
+
onViewMore(today);
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
{`${Math.abs(events.length - i)} ${translations.moreEvents}`}
|
|
95
|
+
</Typography>
|
|
96
|
+
);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
elements.push(
|
|
101
|
+
<div
|
|
102
|
+
key={`${event.event_id}_${i}`}
|
|
103
|
+
className="rs__multi_day"
|
|
104
|
+
style={{
|
|
105
|
+
top: topSpace,
|
|
106
|
+
width: `${100 * eventLength}%`,
|
|
107
|
+
height: MONTH_BAR_HEIGHT,
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<EventItem
|
|
111
|
+
event={event}
|
|
112
|
+
showdate={false}
|
|
113
|
+
multiday={differenceInDaysOmitTime(event.start, event.end) > 0}
|
|
114
|
+
hasPrev={fromPrevWeek}
|
|
115
|
+
hasNext={toNextWeek}
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return elements;
|
|
122
|
+
}, [
|
|
123
|
+
resourceId,
|
|
124
|
+
renderedSlots,
|
|
125
|
+
events,
|
|
126
|
+
LIMIT,
|
|
127
|
+
eachFirstDayInCalcRow,
|
|
128
|
+
month?.weekStartOn,
|
|
129
|
+
locale,
|
|
130
|
+
today,
|
|
131
|
+
eachWeekStart,
|
|
132
|
+
daysList.length,
|
|
133
|
+
translations.moreEvents,
|
|
134
|
+
onViewMore,
|
|
135
|
+
timeZone,
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
return <Fragment>{renderEvents}</Fragment>;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export default MonthEvents;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { differenceInMinutes } from "date-fns";
|
|
2
|
+
import { Fragment } from "react";
|
|
3
|
+
import { BORDER_HEIGHT } from "../../helpers/constants";
|
|
4
|
+
import { isTimeZonedToday, traversCrossingEvents } from "../../helpers/generals";
|
|
5
|
+
import { ProcessedEvent } from "../../types";
|
|
6
|
+
import CurrentTimeBar from "./CurrentTimeBar";
|
|
7
|
+
import EventItem from "./EventItem";
|
|
8
|
+
|
|
9
|
+
interface TodayEventsProps {
|
|
10
|
+
todayEvents: ProcessedEvent[];
|
|
11
|
+
today: Date;
|
|
12
|
+
startHour: number;
|
|
13
|
+
endHour: number;
|
|
14
|
+
step: number;
|
|
15
|
+
minuteHeight: number;
|
|
16
|
+
direction: "rtl" | "ltr";
|
|
17
|
+
timeZone?: string;
|
|
18
|
+
currentTime?: Date;
|
|
19
|
+
showCurrentTimeBar?: boolean;
|
|
20
|
+
currentTimeBarColor?: string;
|
|
21
|
+
}
|
|
22
|
+
const TodayEvents = ({
|
|
23
|
+
todayEvents,
|
|
24
|
+
today,
|
|
25
|
+
startHour,
|
|
26
|
+
endHour,
|
|
27
|
+
step,
|
|
28
|
+
minuteHeight,
|
|
29
|
+
direction,
|
|
30
|
+
timeZone,
|
|
31
|
+
currentTime,
|
|
32
|
+
showCurrentTimeBar = true,
|
|
33
|
+
currentTimeBarColor,
|
|
34
|
+
}: TodayEventsProps) => {
|
|
35
|
+
const crossingIds: Array<number | string> = [];
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Fragment>
|
|
39
|
+
{showCurrentTimeBar && isTimeZonedToday({ dateLeft: today, timeZone }) && (
|
|
40
|
+
<CurrentTimeBar
|
|
41
|
+
startHour={startHour}
|
|
42
|
+
step={step}
|
|
43
|
+
minuteHeight={minuteHeight}
|
|
44
|
+
timeZone={timeZone}
|
|
45
|
+
zIndex={2 * todayEvents.length + 1}
|
|
46
|
+
currentTime={currentTime}
|
|
47
|
+
color={currentTimeBarColor}
|
|
48
|
+
/>
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
{todayEvents.map((event, i) => {
|
|
52
|
+
const maxHeight = (endHour * 60 - startHour * 60) * minuteHeight;
|
|
53
|
+
const eventHeight = differenceInMinutes(event.end, event.start) * minuteHeight;
|
|
54
|
+
const height = Math.min(eventHeight, maxHeight) - BORDER_HEIGHT;
|
|
55
|
+
|
|
56
|
+
const calendarStartInMins = startHour * 60;
|
|
57
|
+
const eventStartInMins = event.start.getHours() * 60 + event.start.getMinutes();
|
|
58
|
+
const minituesFromTop = Math.max(eventStartInMins - calendarStartInMins, 0);
|
|
59
|
+
|
|
60
|
+
const topSpace = minituesFromTop * minuteHeight;
|
|
61
|
+
/** Add border factor to height of each slot */
|
|
62
|
+
const slots = height / 60;
|
|
63
|
+
const heightBorderFactor = slots * BORDER_HEIGHT;
|
|
64
|
+
|
|
65
|
+
/** Calculate top space */
|
|
66
|
+
const slotsFromTop = minituesFromTop / step;
|
|
67
|
+
const top = topSpace + slotsFromTop;
|
|
68
|
+
|
|
69
|
+
const crossingEvents = traversCrossingEvents(todayEvents, event);
|
|
70
|
+
const alreadyRendered = crossingEvents.filter((e) => crossingIds.includes(e.event_id));
|
|
71
|
+
crossingIds.push(event.event_id);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
key={`${event.event_id}/${event.recurrenceId || ""}`}
|
|
76
|
+
className="rs__event__item"
|
|
77
|
+
style={{
|
|
78
|
+
height: height + heightBorderFactor,
|
|
79
|
+
top,
|
|
80
|
+
width:
|
|
81
|
+
alreadyRendered.length > 0
|
|
82
|
+
? `calc(100% - ${100 - 98 / (alreadyRendered.length + 1)}%)`
|
|
83
|
+
: "98%", // Leave some space to click cell
|
|
84
|
+
zIndex: todayEvents.length + i,
|
|
85
|
+
[direction === "rtl" ? "right" : "left"]:
|
|
86
|
+
alreadyRendered.length > 0
|
|
87
|
+
? `${(100 / (crossingEvents.length + 1)) * alreadyRendered.length}%`
|
|
88
|
+
: "",
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
<EventItem event={event} />
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
})}
|
|
95
|
+
</Fragment>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default TodayEvents;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
|
2
|
+
import useStore from "../../hooks/useStore";
|
|
3
|
+
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
|
4
|
+
|
|
5
|
+
interface AuxProps {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const DateProvider = ({ children }: AuxProps) => {
|
|
10
|
+
const { locale } = useStore();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={locale}>
|
|
14
|
+
{children}
|
|
15
|
+
</LocalizationProvider>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default DateProvider;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import DateProvider from "../hoc/DateProvider";
|
|
3
|
+
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
|
4
|
+
import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker";
|
|
5
|
+
import useStore from "../../hooks/useStore";
|
|
6
|
+
|
|
7
|
+
interface EditorDatePickerProps {
|
|
8
|
+
type?: "date" | "datetime";
|
|
9
|
+
label?: string;
|
|
10
|
+
variant?: "standard" | "filled" | "outlined";
|
|
11
|
+
value: Date | string;
|
|
12
|
+
name: string;
|
|
13
|
+
onChange(name: string, date: Date): void;
|
|
14
|
+
error?: boolean;
|
|
15
|
+
errMsg?: string;
|
|
16
|
+
touched?: boolean;
|
|
17
|
+
required?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const EditorDatePicker = ({
|
|
21
|
+
type = "datetime",
|
|
22
|
+
value,
|
|
23
|
+
label,
|
|
24
|
+
name,
|
|
25
|
+
onChange,
|
|
26
|
+
variant = "outlined",
|
|
27
|
+
error,
|
|
28
|
+
errMsg,
|
|
29
|
+
touched,
|
|
30
|
+
required,
|
|
31
|
+
}: EditorDatePickerProps) => {
|
|
32
|
+
const { translations } = useStore();
|
|
33
|
+
const [state, setState] = useState({
|
|
34
|
+
touched: false,
|
|
35
|
+
valid: !!value,
|
|
36
|
+
errorMsg: errMsg
|
|
37
|
+
? errMsg
|
|
38
|
+
: required
|
|
39
|
+
? translations?.validation?.required || "Required"
|
|
40
|
+
: undefined,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const Picker = type === "date" ? DatePicker : DateTimePicker;
|
|
44
|
+
|
|
45
|
+
const hasError = state.touched && (error || !state.valid);
|
|
46
|
+
|
|
47
|
+
const handleChange = useCallback(
|
|
48
|
+
(value: string | Date) => {
|
|
49
|
+
const isValidDate = !isNaN(Date.parse(value as string));
|
|
50
|
+
const val = typeof value === "string" && isValidDate ? new Date(value) : value;
|
|
51
|
+
let isValid = true;
|
|
52
|
+
let errorMsg = errMsg;
|
|
53
|
+
if (required && !val) {
|
|
54
|
+
isValid = false;
|
|
55
|
+
errorMsg = errMsg || translations?.validation?.required || "Required";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setState((prev) => {
|
|
59
|
+
return { ...prev, touched: true, valid: isValid, errorMsg: errorMsg };
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
onChange(name, val as Date);
|
|
63
|
+
},
|
|
64
|
+
[errMsg, name, onChange, required, translations?.validation?.required]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (touched) {
|
|
69
|
+
handleChange(value);
|
|
70
|
+
}
|
|
71
|
+
}, [handleChange, touched, value]);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<DateProvider>
|
|
75
|
+
<Picker
|
|
76
|
+
value={value instanceof Date ? value : new Date(value)}
|
|
77
|
+
label={label}
|
|
78
|
+
onChange={(e) => {
|
|
79
|
+
handleChange(e as Date);
|
|
80
|
+
}}
|
|
81
|
+
minutesStep={5}
|
|
82
|
+
slotProps={{
|
|
83
|
+
textField: {
|
|
84
|
+
variant,
|
|
85
|
+
helperText: hasError && state.errorMsg,
|
|
86
|
+
error: hasError,
|
|
87
|
+
fullWidth: true,
|
|
88
|
+
},
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
</DateProvider>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export { EditorDatePicker };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { TextField, Typography } from "@mui/material";
|
|
3
|
+
import useStore from "../../hooks/useStore";
|
|
4
|
+
|
|
5
|
+
interface EditorInputProps {
|
|
6
|
+
variant?: "standard" | "filled" | "outlined";
|
|
7
|
+
label?: string;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
min?: number;
|
|
11
|
+
max?: number;
|
|
12
|
+
email?: boolean;
|
|
13
|
+
decimal?: boolean;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
multiline?: boolean;
|
|
16
|
+
rows?: number;
|
|
17
|
+
value: string;
|
|
18
|
+
name: string;
|
|
19
|
+
onChange(name: string, value: string, isValid: boolean): void;
|
|
20
|
+
touched?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const EditorInput = ({
|
|
24
|
+
variant = "outlined",
|
|
25
|
+
label,
|
|
26
|
+
placeholder,
|
|
27
|
+
value,
|
|
28
|
+
name,
|
|
29
|
+
required,
|
|
30
|
+
min,
|
|
31
|
+
max,
|
|
32
|
+
email,
|
|
33
|
+
decimal,
|
|
34
|
+
onChange,
|
|
35
|
+
disabled,
|
|
36
|
+
multiline,
|
|
37
|
+
rows,
|
|
38
|
+
touched,
|
|
39
|
+
}: EditorInputProps) => {
|
|
40
|
+
const [state, setState] = useState({
|
|
41
|
+
touched: false,
|
|
42
|
+
valid: false,
|
|
43
|
+
errorMsg: "",
|
|
44
|
+
});
|
|
45
|
+
const { translations } = useStore();
|
|
46
|
+
|
|
47
|
+
const handleChange = useCallback(
|
|
48
|
+
(value: string) => {
|
|
49
|
+
const val = value;
|
|
50
|
+
let isValid = true;
|
|
51
|
+
let errorMsg = "";
|
|
52
|
+
if (email) {
|
|
53
|
+
const reg =
|
|
54
|
+
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
55
|
+
isValid = reg.test(val) && isValid;
|
|
56
|
+
errorMsg = translations?.validation?.invalidEmail || "Invalid Email";
|
|
57
|
+
}
|
|
58
|
+
if (decimal) {
|
|
59
|
+
const reg = /^[0-9]+(\.[0-9]*)?$/;
|
|
60
|
+
isValid = reg.test(val) && isValid;
|
|
61
|
+
errorMsg = translations?.validation?.onlyNumbers || "Only Numbers Allowed";
|
|
62
|
+
}
|
|
63
|
+
if (min && `${val}`.trim().length < min) {
|
|
64
|
+
isValid = false;
|
|
65
|
+
errorMsg =
|
|
66
|
+
typeof translations?.validation?.min === "function"
|
|
67
|
+
? translations?.validation?.min(min)
|
|
68
|
+
: translations?.validation?.min || `Minimum ${min} letters`;
|
|
69
|
+
}
|
|
70
|
+
if (max && `${val}`.trim().length > max) {
|
|
71
|
+
isValid = false;
|
|
72
|
+
errorMsg =
|
|
73
|
+
typeof translations?.validation?.max === "function"
|
|
74
|
+
? translations?.validation?.max(max)
|
|
75
|
+
: translations?.validation?.max || `Maximum ${max} letters`;
|
|
76
|
+
}
|
|
77
|
+
if (required && `${val}`.trim().length <= 0) {
|
|
78
|
+
isValid = false;
|
|
79
|
+
errorMsg = translations?.validation?.required || "Required";
|
|
80
|
+
}
|
|
81
|
+
setState({ touched: true, valid: isValid, errorMsg: errorMsg });
|
|
82
|
+
onChange(name, val, isValid);
|
|
83
|
+
},
|
|
84
|
+
[decimal, email, max, min, name, onChange, required, translations?.validation]
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (touched) {
|
|
89
|
+
handleChange(value);
|
|
90
|
+
}
|
|
91
|
+
}, [handleChange, touched, value]);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<TextField
|
|
95
|
+
variant={variant}
|
|
96
|
+
label={label && <Typography variant="body2">{`${label} ${required ? "*" : ""}`}</Typography>}
|
|
97
|
+
value={value}
|
|
98
|
+
name={name}
|
|
99
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
100
|
+
disabled={disabled}
|
|
101
|
+
error={state.touched && !state.valid}
|
|
102
|
+
helperText={state.touched && !state.valid && state.errorMsg}
|
|
103
|
+
multiline={multiline}
|
|
104
|
+
rows={rows}
|
|
105
|
+
style={{ width: "100%" }}
|
|
106
|
+
InputProps={{
|
|
107
|
+
placeholder: placeholder || "",
|
|
108
|
+
}}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export { EditorInput };
|