@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,98 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { DefaultResource } from "../../types";
|
|
3
|
+
import { ResourceHeader } from "./ResourceHeader";
|
|
4
|
+
import { ButtonTabProps, ButtonTabs } from "./Tabs";
|
|
5
|
+
import useStore from "../../hooks/useStore";
|
|
6
|
+
import { Box, useTheme } from "@mui/material";
|
|
7
|
+
|
|
8
|
+
interface WithResourcesProps {
|
|
9
|
+
renderChildren(resource: DefaultResource): React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
const WithResources = ({ renderChildren }: WithResourcesProps) => {
|
|
12
|
+
const { resources, resourceFields, resourceViewMode } = useStore();
|
|
13
|
+
const theme = useTheme();
|
|
14
|
+
|
|
15
|
+
if (resourceViewMode === "tabs") {
|
|
16
|
+
return <ResourcesTabTables renderChildren={renderChildren} />;
|
|
17
|
+
} else if (resourceViewMode === "vertical") {
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
{resources.map((res: DefaultResource, i: number) => (
|
|
21
|
+
<Box key={`${res[resourceFields.idField]}_${i}`} sx={{ display: "flex" }}>
|
|
22
|
+
<Box
|
|
23
|
+
sx={{
|
|
24
|
+
borderColor: theme.palette.grey[300],
|
|
25
|
+
borderStyle: "solid",
|
|
26
|
+
borderWidth: "1px 1px 0 1px",
|
|
27
|
+
paddingTop: 1,
|
|
28
|
+
flexBasis: 140,
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
<ResourceHeader resource={res} />
|
|
32
|
+
</Box>
|
|
33
|
+
<Box
|
|
34
|
+
//
|
|
35
|
+
sx={{ width: "100%", overflowX: "auto" }}
|
|
36
|
+
>
|
|
37
|
+
{renderChildren(res)}
|
|
38
|
+
</Box>
|
|
39
|
+
</Box>
|
|
40
|
+
))}
|
|
41
|
+
</>
|
|
42
|
+
);
|
|
43
|
+
} else {
|
|
44
|
+
return (
|
|
45
|
+
<>
|
|
46
|
+
{resources.map((res: DefaultResource, i: number) => (
|
|
47
|
+
<div key={`${res[resourceFields.idField]}_${i}`}>
|
|
48
|
+
<ResourceHeader resource={res} />
|
|
49
|
+
{renderChildren(res)}
|
|
50
|
+
</div>
|
|
51
|
+
))}
|
|
52
|
+
</>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const ResourcesTabTables = ({ renderChildren }: WithResourcesProps) => {
|
|
58
|
+
const { resources, resourceFields, selectedTab, handleState, onResourceChange } = useStore();
|
|
59
|
+
|
|
60
|
+
const tabs: ButtonTabProps[] = resources.map((res) => {
|
|
61
|
+
return {
|
|
62
|
+
id: res[resourceFields.idField],
|
|
63
|
+
label: <ResourceHeader resource={res} />,
|
|
64
|
+
component: <>{renderChildren(res)}</>,
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const setTab = (tab: DefaultResource["assignee"]) => {
|
|
69
|
+
handleState(tab, "selectedTab");
|
|
70
|
+
if (typeof onResourceChange === "function") {
|
|
71
|
+
const selected = resources.find((re) => re[resourceFields.idField] === tab);
|
|
72
|
+
if (selected) {
|
|
73
|
+
onResourceChange(selected);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const currentTabSafeId = useMemo(() => {
|
|
79
|
+
const firstId = resources[0][resourceFields.idField];
|
|
80
|
+
if (!selectedTab) {
|
|
81
|
+
return firstId;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Make sure current selected id is within the resources array
|
|
85
|
+
const idx = resources.findIndex((re) => re[resourceFields.idField] === selectedTab);
|
|
86
|
+
if (idx < 0) {
|
|
87
|
+
return firstId;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return selectedTab;
|
|
91
|
+
}, [resources, resourceFields.idField, selectedTab]);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<ButtonTabs tabs={tabs} tab={currentTabSafeId} setTab={setTab} style={{ display: "grid" }} />
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export { WithResources };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import DeleteRounded from "@mui/icons-material/DeleteRounded";
|
|
2
|
+
import EditRounded from "@mui/icons-material/EditRounded";
|
|
3
|
+
import { Button, Grow, IconButton, Slide } from "@mui/material";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { EventActions as Actions } from "../../styles/styles";
|
|
6
|
+
import { ProcessedEvent } from "../../types";
|
|
7
|
+
import useStore from "../../hooks/useStore";
|
|
8
|
+
import useEventPermissions from "../../hooks/useEventPermissions";
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
event: ProcessedEvent;
|
|
12
|
+
onDelete(): void;
|
|
13
|
+
onEdit(): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const EventActions = ({ event, onDelete, onEdit }: Props) => {
|
|
17
|
+
const { translations, direction } = useStore();
|
|
18
|
+
const [deleteConfirm, setDeleteConfirm] = useState(false);
|
|
19
|
+
|
|
20
|
+
const handleDelete = () => {
|
|
21
|
+
if (!deleteConfirm) {
|
|
22
|
+
return setDeleteConfirm(true);
|
|
23
|
+
}
|
|
24
|
+
onDelete();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const { canEdit, canDelete } = useEventPermissions(event);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Actions>
|
|
31
|
+
<Grow in={!deleteConfirm} exit={false} timeout={400} unmountOnExit>
|
|
32
|
+
<div>
|
|
33
|
+
{canEdit && (
|
|
34
|
+
<IconButton size="small" onClick={onEdit}>
|
|
35
|
+
<EditRounded />
|
|
36
|
+
</IconButton>
|
|
37
|
+
)}
|
|
38
|
+
{canDelete && (
|
|
39
|
+
<IconButton size="small" onClick={handleDelete}>
|
|
40
|
+
<DeleteRounded />
|
|
41
|
+
</IconButton>
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
</Grow>
|
|
45
|
+
<Slide
|
|
46
|
+
in={deleteConfirm}
|
|
47
|
+
direction={direction === "rtl" ? "right" : "left"}
|
|
48
|
+
unmountOnExit
|
|
49
|
+
timeout={400}
|
|
50
|
+
exit={false}
|
|
51
|
+
>
|
|
52
|
+
<div>
|
|
53
|
+
<Button className="delete" size="small" onClick={handleDelete}>
|
|
54
|
+
{translations.form.delete.toUpperCase()}
|
|
55
|
+
</Button>
|
|
56
|
+
<Button className="cancel" size="small" onClick={() => setDeleteConfirm(false)}>
|
|
57
|
+
{translations.form.cancel.toUpperCase()}
|
|
58
|
+
</Button>
|
|
59
|
+
</div>
|
|
60
|
+
</Slide>
|
|
61
|
+
</Actions>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default EventActions;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Fragment, MouseEvent, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
useTheme,
|
|
4
|
+
List,
|
|
5
|
+
ListItemButton,
|
|
6
|
+
ListItemAvatar,
|
|
7
|
+
Avatar,
|
|
8
|
+
ListItemText,
|
|
9
|
+
} from "@mui/material";
|
|
10
|
+
import { format } from "date-fns";
|
|
11
|
+
import { ProcessedEvent } from "../../types";
|
|
12
|
+
import { getHourFormat, isTimeZonedToday } from "../../helpers/generals";
|
|
13
|
+
import useStore from "../../hooks/useStore";
|
|
14
|
+
import EventItemPopover from "./EventItemPopover";
|
|
15
|
+
|
|
16
|
+
interface AgendaEventsListProps {
|
|
17
|
+
day: Date;
|
|
18
|
+
events: ProcessedEvent[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const AgendaEventsList = ({ day, events }: AgendaEventsListProps) => {
|
|
22
|
+
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
|
|
23
|
+
const [selectedEvent, setSelectedEvent] = useState<ProcessedEvent>();
|
|
24
|
+
const [deleteConfirm, setDeleteConfirm] = useState(false);
|
|
25
|
+
const { locale, hourFormat, eventRenderer, onEventClick, timeZone, disableViewer } = useStore();
|
|
26
|
+
const theme = useTheme();
|
|
27
|
+
const hFormat = getHourFormat(hourFormat);
|
|
28
|
+
|
|
29
|
+
const triggerViewer = (el?: MouseEvent<Element>) => {
|
|
30
|
+
if (!el?.currentTarget && deleteConfirm) {
|
|
31
|
+
setDeleteConfirm(false);
|
|
32
|
+
}
|
|
33
|
+
setAnchorEl(el?.currentTarget || null);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Fragment>
|
|
38
|
+
<List>
|
|
39
|
+
{events.map((event) => {
|
|
40
|
+
const startIsToday = isTimeZonedToday({
|
|
41
|
+
dateLeft: event.start,
|
|
42
|
+
dateRight: day,
|
|
43
|
+
timeZone,
|
|
44
|
+
});
|
|
45
|
+
const startFormat = startIsToday ? hFormat : `MMM d, ${hFormat}`;
|
|
46
|
+
const startDate = format(event.start, startFormat, {
|
|
47
|
+
locale,
|
|
48
|
+
});
|
|
49
|
+
const endIsToday = isTimeZonedToday({ dateLeft: event.end, dateRight: day, timeZone });
|
|
50
|
+
|
|
51
|
+
const endFormat = endIsToday ? hFormat : `MMM d, ${hFormat}`;
|
|
52
|
+
const endDate = format(event.end, endFormat, {
|
|
53
|
+
locale,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (typeof eventRenderer === "function") {
|
|
57
|
+
return eventRenderer({
|
|
58
|
+
event,
|
|
59
|
+
onClick: (e) => {
|
|
60
|
+
setSelectedEvent(event);
|
|
61
|
+
triggerViewer(e);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<ListItemButton
|
|
68
|
+
key={`${event.start.getTime()}_${event.end.getTime()}_${event.event_id}`}
|
|
69
|
+
focusRipple
|
|
70
|
+
disableRipple={disableViewer}
|
|
71
|
+
tabIndex={disableViewer ? -1 : 0}
|
|
72
|
+
disabled={event.disabled}
|
|
73
|
+
onClick={(e) => {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
e.stopPropagation();
|
|
76
|
+
if (!disableViewer) {
|
|
77
|
+
triggerViewer(e);
|
|
78
|
+
}
|
|
79
|
+
setSelectedEvent(event);
|
|
80
|
+
if (typeof onEventClick === "function") {
|
|
81
|
+
onEventClick(event);
|
|
82
|
+
}
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<ListItemAvatar>
|
|
86
|
+
<Avatar
|
|
87
|
+
sx={{
|
|
88
|
+
bgcolor: event.disabled ? "#d0d0d0" : event.color || theme.palette.primary.main,
|
|
89
|
+
color: event.disabled
|
|
90
|
+
? "#808080"
|
|
91
|
+
: event.textColor || theme.palette.primary.contrastText,
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
{event.agendaAvatar || " "}
|
|
95
|
+
</Avatar>
|
|
96
|
+
</ListItemAvatar>
|
|
97
|
+
<ListItemText primary={event.title} secondary={`${startDate} - ${endDate}`} />
|
|
98
|
+
</ListItemButton>
|
|
99
|
+
);
|
|
100
|
+
})}
|
|
101
|
+
</List>
|
|
102
|
+
|
|
103
|
+
{/* Viewer */}
|
|
104
|
+
{selectedEvent && (
|
|
105
|
+
<EventItemPopover
|
|
106
|
+
anchorEl={anchorEl}
|
|
107
|
+
event={selectedEvent}
|
|
108
|
+
onTriggerViewer={triggerViewer}
|
|
109
|
+
/>
|
|
110
|
+
)}
|
|
111
|
+
</Fragment>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export default AgendaEventsList;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { differenceInMinutes, set } from "date-fns";
|
|
3
|
+
import { BORDER_HEIGHT } from "../../helpers/constants";
|
|
4
|
+
import { getTimeZonedDate } from "../../helpers/generals";
|
|
5
|
+
import { TimeIndicatorBar } from "../../styles/styles";
|
|
6
|
+
|
|
7
|
+
interface CurrentTimeBarProps {
|
|
8
|
+
startHour: number;
|
|
9
|
+
step: number;
|
|
10
|
+
minuteHeight: number;
|
|
11
|
+
timeZone?: string;
|
|
12
|
+
zIndex?: number;
|
|
13
|
+
currentTime?: Date;
|
|
14
|
+
color?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function calculateTop({
|
|
18
|
+
startHour,
|
|
19
|
+
step,
|
|
20
|
+
minuteHeight,
|
|
21
|
+
timeZone,
|
|
22
|
+
currentTime,
|
|
23
|
+
}: CurrentTimeBarProps): number {
|
|
24
|
+
const now = currentTime
|
|
25
|
+
? getTimeZonedDate(currentTime, timeZone)
|
|
26
|
+
: getTimeZonedDate(new Date(), timeZone);
|
|
27
|
+
|
|
28
|
+
const minutesFromTop = differenceInMinutes(now, set(now, { hours: startHour, minutes: 0 }));
|
|
29
|
+
const topSpace = minutesFromTop * minuteHeight;
|
|
30
|
+
const slotsFromTop = minutesFromTop / step;
|
|
31
|
+
const borderFactor = slotsFromTop + BORDER_HEIGHT;
|
|
32
|
+
const top = topSpace + borderFactor;
|
|
33
|
+
|
|
34
|
+
return top;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const CurrentTimeBar = (props: CurrentTimeBarProps) => {
|
|
38
|
+
const [top, setTop] = useState(calculateTop(props));
|
|
39
|
+
const { startHour, step, minuteHeight, timeZone, currentTime, color } = props;
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const calcProps = { startHour, step, minuteHeight, timeZone, currentTime };
|
|
43
|
+
setTop(calculateTop(calcProps));
|
|
44
|
+
const interval = setInterval(() => setTop(calculateTop(calcProps)), 60 * 1000);
|
|
45
|
+
return () => clearInterval(interval);
|
|
46
|
+
}, [startHour, step, minuteHeight, timeZone, currentTime]);
|
|
47
|
+
|
|
48
|
+
// Prevent showing bar on top of days/header
|
|
49
|
+
if (top < 0) return null;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<TimeIndicatorBar style={{ top, zIndex: props.zIndex }} color={color}>
|
|
53
|
+
<div />
|
|
54
|
+
<div />
|
|
55
|
+
</TimeIndicatorBar>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default CurrentTimeBar;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AgendaDiv } from "../../styles/styles";
|
|
2
|
+
import { Typography } from "@mui/material";
|
|
3
|
+
import useStore from "../../hooks/useStore";
|
|
4
|
+
|
|
5
|
+
const EmptyAgenda = () => {
|
|
6
|
+
const { height, translations, stickyNavigationOffset, stickyNavigationHeight } = useStore();
|
|
7
|
+
return (
|
|
8
|
+
<AgendaDiv
|
|
9
|
+
stickyOffset={stickyNavigationOffset}
|
|
10
|
+
stickyHeight={stickyNavigationHeight}
|
|
11
|
+
sx={{
|
|
12
|
+
borderWidth: 1,
|
|
13
|
+
padding: 1,
|
|
14
|
+
height: height / 2,
|
|
15
|
+
display: "flex",
|
|
16
|
+
alignItems: "center",
|
|
17
|
+
justifyContent: "center",
|
|
18
|
+
}}
|
|
19
|
+
>
|
|
20
|
+
<div className="rs__cell rs__agenda_items">
|
|
21
|
+
<Typography>{translations.noDataToDisplay}</Typography>
|
|
22
|
+
</div>
|
|
23
|
+
</AgendaDiv>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default EmptyAgenda;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { Fragment, MouseEvent, useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { Typography, ButtonBase, useTheme } from "@mui/material";
|
|
3
|
+
import { format } from "date-fns";
|
|
4
|
+
import { ProcessedEvent } from "../../types";
|
|
5
|
+
import ArrowRightRoundedIcon from "@mui/icons-material/ArrowRightRounded";
|
|
6
|
+
import ArrowLeftRoundedIcon from "@mui/icons-material/ArrowLeftRounded";
|
|
7
|
+
import { EventItemPaper } from "../../styles/styles";
|
|
8
|
+
import { differenceInDaysOmitTime, getHourFormat } from "../../helpers/generals";
|
|
9
|
+
import useStore from "../../hooks/useStore";
|
|
10
|
+
import useDragAttributes from "../../hooks/useDragAttributes";
|
|
11
|
+
import EventItemPopover from "./EventItemPopover";
|
|
12
|
+
import useEventPermissions from "../../hooks/useEventPermissions";
|
|
13
|
+
|
|
14
|
+
interface EventItemProps {
|
|
15
|
+
event: ProcessedEvent;
|
|
16
|
+
multiday?: boolean;
|
|
17
|
+
hasPrev?: boolean;
|
|
18
|
+
hasNext?: boolean;
|
|
19
|
+
showdate?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: EventItemProps) => {
|
|
23
|
+
const { direction, locale, hourFormat, eventRenderer, onEventClick, view, disableViewer } =
|
|
24
|
+
useStore();
|
|
25
|
+
const dragProps = useDragAttributes(event);
|
|
26
|
+
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
|
|
27
|
+
const [deleteConfirm, setDeleteConfirm] = useState(false);
|
|
28
|
+
const theme = useTheme();
|
|
29
|
+
const hFormat = getHourFormat(hourFormat);
|
|
30
|
+
|
|
31
|
+
const NextArrow = direction === "rtl" ? ArrowLeftRoundedIcon : ArrowRightRoundedIcon;
|
|
32
|
+
const PrevArrow = direction === "rtl" ? ArrowRightRoundedIcon : ArrowLeftRoundedIcon;
|
|
33
|
+
const hideDates = differenceInDaysOmitTime(event.start, event.end) <= 0 && event.allDay;
|
|
34
|
+
|
|
35
|
+
const { canDrag } = useEventPermissions(event);
|
|
36
|
+
|
|
37
|
+
const triggerViewer = useCallback(
|
|
38
|
+
(el?: MouseEvent<Element>) => {
|
|
39
|
+
if (!el?.currentTarget && deleteConfirm) {
|
|
40
|
+
setDeleteConfirm(false);
|
|
41
|
+
}
|
|
42
|
+
setAnchorEl(el?.currentTarget || null);
|
|
43
|
+
},
|
|
44
|
+
[deleteConfirm]
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const renderEvent = useMemo(() => {
|
|
48
|
+
// Check if has custom render event method
|
|
49
|
+
// only applicable to non-multiday events and not in month-view
|
|
50
|
+
if (typeof eventRenderer === "function" && !multiday && view !== "month") {
|
|
51
|
+
const custom = eventRenderer({ event, onClick: triggerViewer, ...dragProps });
|
|
52
|
+
if (custom) {
|
|
53
|
+
return (
|
|
54
|
+
<EventItemPaper key={`${event.start.getTime()}_${event.end.getTime()}_${event.event_id}`}>
|
|
55
|
+
{custom}
|
|
56
|
+
</EventItemPaper>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let item = (
|
|
62
|
+
<div style={{ padding: "2px 6px" }}>
|
|
63
|
+
<Typography variant="subtitle2" style={{ fontSize: 12 }} noWrap>
|
|
64
|
+
{event.title}
|
|
65
|
+
</Typography>
|
|
66
|
+
{event.subtitle && (
|
|
67
|
+
<Typography variant="body2" style={{ fontSize: 11 }} noWrap>
|
|
68
|
+
{event.subtitle}
|
|
69
|
+
</Typography>
|
|
70
|
+
)}
|
|
71
|
+
{showdate && (
|
|
72
|
+
<Typography style={{ fontSize: 11 }} noWrap>
|
|
73
|
+
{`${format(event.start, hFormat, {
|
|
74
|
+
locale,
|
|
75
|
+
})} - ${format(event.end, hFormat, { locale })}`}
|
|
76
|
+
</Typography>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
if (multiday) {
|
|
81
|
+
item = (
|
|
82
|
+
<div
|
|
83
|
+
style={{
|
|
84
|
+
padding: 2,
|
|
85
|
+
display: "flex",
|
|
86
|
+
alignItems: "center",
|
|
87
|
+
justifyContent: "space-between",
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
<Typography sx={{ fontSize: 11 }} noWrap>
|
|
91
|
+
{hasPrev ? (
|
|
92
|
+
<PrevArrow fontSize="small" sx={{ display: "flex" }} />
|
|
93
|
+
) : (
|
|
94
|
+
showdate && !hideDates && format(event.start, hFormat, { locale })
|
|
95
|
+
)}
|
|
96
|
+
</Typography>
|
|
97
|
+
<Typography variant="subtitle2" align="center" sx={{ fontSize: 12 }} noWrap>
|
|
98
|
+
{event.title}
|
|
99
|
+
</Typography>
|
|
100
|
+
<Typography sx={{ fontSize: 11 }} noWrap>
|
|
101
|
+
{hasNext ? (
|
|
102
|
+
<NextArrow fontSize="small" sx={{ display: "flex" }} />
|
|
103
|
+
) : (
|
|
104
|
+
showdate && !hideDates && format(event.end, hFormat, { locale })
|
|
105
|
+
)}
|
|
106
|
+
</Typography>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return (
|
|
111
|
+
<EventItemPaper
|
|
112
|
+
key={`${event.start.getTime()}_${event.end.getTime()}_${event.event_id}`}
|
|
113
|
+
disabled={event.disabled}
|
|
114
|
+
sx={{
|
|
115
|
+
bgcolor: event.disabled ? "#d0d0d0" : event.color || theme.palette.primary.main,
|
|
116
|
+
color: event.disabled ? "#808080" : event.textColor || theme.palette.primary.contrastText,
|
|
117
|
+
borderTop: event._hasPrev
|
|
118
|
+
? `3px dashed ${event.textColor || theme.palette.primary.contrastText}`
|
|
119
|
+
: undefined,
|
|
120
|
+
borderBottom: event._hasNext
|
|
121
|
+
? `3px dashed ${event.textColor || theme.palette.primary.contrastText}`
|
|
122
|
+
: undefined,
|
|
123
|
+
...(event.sx || {}),
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
<ButtonBase
|
|
127
|
+
onClick={(e) => {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
e.stopPropagation();
|
|
130
|
+
if (!disableViewer) {
|
|
131
|
+
triggerViewer(e);
|
|
132
|
+
}
|
|
133
|
+
if (typeof onEventClick === "function") {
|
|
134
|
+
onEventClick(event);
|
|
135
|
+
}
|
|
136
|
+
}}
|
|
137
|
+
focusRipple
|
|
138
|
+
tabIndex={disableViewer ? -1 : 0}
|
|
139
|
+
disableRipple={disableViewer}
|
|
140
|
+
disabled={event.disabled}
|
|
141
|
+
>
|
|
142
|
+
<div {...dragProps} draggable={canDrag}>
|
|
143
|
+
{item}
|
|
144
|
+
</div>
|
|
145
|
+
</ButtonBase>
|
|
146
|
+
</EventItemPaper>
|
|
147
|
+
);
|
|
148
|
+
}, [
|
|
149
|
+
eventRenderer,
|
|
150
|
+
multiday,
|
|
151
|
+
view,
|
|
152
|
+
event,
|
|
153
|
+
showdate,
|
|
154
|
+
hFormat,
|
|
155
|
+
locale,
|
|
156
|
+
theme.palette.primary.main,
|
|
157
|
+
theme.palette.primary.contrastText,
|
|
158
|
+
disableViewer,
|
|
159
|
+
dragProps,
|
|
160
|
+
canDrag,
|
|
161
|
+
triggerViewer,
|
|
162
|
+
hasPrev,
|
|
163
|
+
PrevArrow,
|
|
164
|
+
hideDates,
|
|
165
|
+
hasNext,
|
|
166
|
+
NextArrow,
|
|
167
|
+
onEventClick,
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<Fragment>
|
|
172
|
+
{renderEvent}
|
|
173
|
+
|
|
174
|
+
{/* Viewer */}
|
|
175
|
+
<EventItemPopover anchorEl={anchorEl} event={event} onTriggerViewer={triggerViewer} />
|
|
176
|
+
</Fragment>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export default EventItem;
|