sidebar availability search (#128)
* [123] added people search in left side bar * [#123] added toggle temp calendars * [#123] added tests] * fixup! [123] added people search in left side bar * fixup! [#123] added toggle temp calendars * [#123] fixed event creation on Enter * fixup! [#123] fixed event creation on Enter * fixup! [#123] fixed event creation on Enter * fixup! [#123] fixed event creation on Enter * fixup! [#123] fixed event creation on Enter * fixup! [#123] fixed event creation on Enter * fixup! [#123] fixed event creation on Enter * fixup! [#123] fixed event creation on Enter * [#123] added color diff for temp calendars * fixup! [#123] fixed event creation on Enter * fixup! [#123] added tests] * fixup! [#123] added toggle temp calendars --------- Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import { getOpenPaasUser, getUserDetails } from "../User/userAPI";
|
||||
import { parseCalendarEvent } from "../Events/eventUtils";
|
||||
import { deleteEvent, getEvent, moveEvent, putEvent } from "../Events/EventApi";
|
||||
import { formatDateToYYYYMMDDTHHMMSS } from "../../utils/dateUtils";
|
||||
import { User } from "../../components/Attendees/PeopleSearch";
|
||||
|
||||
export const getCalendarsListAsync = createAsyncThunk<
|
||||
Record<string, Calendars> // Return type
|
||||
@@ -55,10 +56,50 @@ export const getCalendarsListAsync = createAsyncThunk<
|
||||
return importedCalendars;
|
||||
});
|
||||
|
||||
export const getTempCalendarsListAsync = createAsyncThunk<
|
||||
Record<string, Calendars>,
|
||||
User
|
||||
>("calendars/getTempCalendars", async (tempUser) => {
|
||||
const importedCalendars: Record<string, Calendars> = {};
|
||||
|
||||
const calendars = (await getCalendars(
|
||||
tempUser.openpaasId ?? "",
|
||||
"sharedPublic=true&WithRights=true"
|
||||
)) as Record<string, any>;
|
||||
const rawCalendars = calendars._embedded["dav:calendar"];
|
||||
|
||||
for (const cal of rawCalendars) {
|
||||
const name = cal["dav:name"];
|
||||
const description = cal["caldav:description"];
|
||||
const delegated = cal["calendarserver:delegatedsource"] ? true : false;
|
||||
const source = cal["calendarserver:source"]
|
||||
? cal["calendarserver:source"]._links.self.href
|
||||
: cal._links.self.href;
|
||||
const link = cal._links.self.href;
|
||||
|
||||
const id = source.replace("/calendars/", "").replace(".json", "");
|
||||
const ownerData: any = await getUserDetails(id.split("/")[0]);
|
||||
|
||||
importedCalendars[id] = {
|
||||
id,
|
||||
name,
|
||||
link,
|
||||
owner: `${ownerData.firstname ? `${ownerData.firstname} ` : ""}${ownerData.lastname}`,
|
||||
ownerEmails: ownerData.emails,
|
||||
description,
|
||||
delegated,
|
||||
color: tempUser.color ?? "#a8a8a8ff",
|
||||
events: {},
|
||||
};
|
||||
}
|
||||
|
||||
return importedCalendars;
|
||||
});
|
||||
|
||||
export const getCalendarDetailAsync = createAsyncThunk<
|
||||
{ calId: string; events: CalendarEvent[] }, // Return type
|
||||
{ calId: string; match: { start: string; end: string } } // Arg type
|
||||
>("calendars/getCalendarDetails", async ({ calId, match }) => {
|
||||
{ calId: string; events: CalendarEvent[]; calType?: string }, // Return type
|
||||
{ calId: string; match: { start: string; end: string }; calType?: string } // Arg type
|
||||
>("calendars/getCalendarDetails", async ({ calId, match, calType }) => {
|
||||
const calendar = (await getCalendar(calId, match)) as Record<string, any>;
|
||||
const color = calendar["apple:color"];
|
||||
const events: CalendarEvent[] = calendar._embedded["dav:item"].flatMap(
|
||||
@@ -72,13 +113,13 @@ export const getCalendarDetailAsync = createAsyncThunk<
|
||||
}
|
||||
);
|
||||
|
||||
return { calId, events };
|
||||
return { calId, events, calType };
|
||||
});
|
||||
|
||||
export const putEventAsync = createAsyncThunk<
|
||||
{ calId: string; events: CalendarEvent[] }, // Return type
|
||||
{ cal: Calendars; newEvent: CalendarEvent } // Arg type
|
||||
>("calendars/putEvent", async ({ cal, newEvent }) => {
|
||||
{ calId: string; events: CalendarEvent[]; calType?: "temp" }, // Return type
|
||||
{ cal: Calendars; newEvent: CalendarEvent; calType?: "temp" } // Arg type
|
||||
>("calendars/putEvent", async ({ cal, newEvent, calType }) => {
|
||||
const response = await putEvent(
|
||||
newEvent,
|
||||
cal.ownerEmails ? cal.ownerEmails[0] : undefined
|
||||
@@ -116,6 +157,7 @@ export const putEventAsync = createAsyncThunk<
|
||||
return {
|
||||
calId: cal.id,
|
||||
events,
|
||||
calType,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -247,7 +289,11 @@ export const addSharedCalendarAsync = createAsyncThunk<
|
||||
|
||||
const CalendarSlice = createSlice({
|
||||
name: "calendars",
|
||||
initialState: { list: {} as Record<string, Calendars>, pending: false },
|
||||
initialState: {
|
||||
list: {} as Record<string, Calendars>,
|
||||
templist: {} as Record<string, Calendars>,
|
||||
pending: false,
|
||||
},
|
||||
reducers: {
|
||||
createCalendar: (state, action: PayloadAction<Record<string, string>>) => {
|
||||
const id = Date.now().toString(36);
|
||||
@@ -280,6 +326,9 @@ const CalendarSlice = createSlice({
|
||||
action.payload.eventUid
|
||||
];
|
||||
},
|
||||
removeTempCal: (state, action: PayloadAction<string>) => {
|
||||
delete state.templist[action.payload];
|
||||
},
|
||||
updateEventLocal: (
|
||||
state,
|
||||
action: PayloadAction<{ calId: string; event: CalendarEvent }>
|
||||
@@ -297,56 +346,81 @@ const CalendarSlice = createSlice({
|
||||
state.list = action.payload;
|
||||
}
|
||||
)
|
||||
.addCase(
|
||||
getTempCalendarsListAsync.fulfilled,
|
||||
(state, action: PayloadAction<Record<string, Calendars>>) => {
|
||||
state.pending = false;
|
||||
Object.keys(action.payload).forEach(
|
||||
(id) => (state.templist[id] = action.payload[id])
|
||||
);
|
||||
}
|
||||
)
|
||||
.addCase(
|
||||
getCalendarDetailAsync.fulfilled,
|
||||
(
|
||||
state,
|
||||
action: PayloadAction<{ calId: string; events: CalendarEvent[] }>
|
||||
action: PayloadAction<{
|
||||
calId: string;
|
||||
events: CalendarEvent[];
|
||||
calType?: string;
|
||||
}>
|
||||
) => {
|
||||
state.pending = false;
|
||||
if (!state.list[action.payload.calId]) {
|
||||
state.list[action.payload.calId] = {
|
||||
const type = action.payload.calType === "temp" ? "templist" : "list";
|
||||
|
||||
if (!state[type][action.payload.calId]) {
|
||||
state[type][action.payload.calId] = {
|
||||
id: action.payload.calId,
|
||||
events: {},
|
||||
} as Calendars;
|
||||
}
|
||||
action.payload.events.forEach((event) => {
|
||||
state.list[action.payload.calId].events[event.uid] = event;
|
||||
});
|
||||
Object.keys(state.list[action.payload.calId].events).forEach((id) => {
|
||||
state.list[action.payload.calId].events[id].color =
|
||||
state.list[action.payload.calId].color;
|
||||
state.list[action.payload.calId].events[id].calId =
|
||||
action.payload.calId;
|
||||
state.list[action.payload.calId].events[id].timezone =
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
state[type][action.payload.calId].events[event.uid] = event;
|
||||
});
|
||||
Object.keys(state[type][action.payload.calId].events).forEach(
|
||||
(id) => {
|
||||
state[type][action.payload.calId].events[id].color =
|
||||
state[type][action.payload.calId].color;
|
||||
state[type][action.payload.calId].events[id].calId =
|
||||
action.payload.calId;
|
||||
state[type][action.payload.calId].events[id].timezone =
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
}
|
||||
);
|
||||
}
|
||||
)
|
||||
.addCase(
|
||||
putEventAsync.fulfilled,
|
||||
(
|
||||
state,
|
||||
action: PayloadAction<{ calId: string; events: CalendarEvent[] }>
|
||||
action: PayloadAction<{
|
||||
calId: string;
|
||||
events: CalendarEvent[];
|
||||
calType?: "temp";
|
||||
}>
|
||||
) => {
|
||||
state.pending = false;
|
||||
if (!state.list[action.payload.calId]) {
|
||||
state.list[action.payload.calId] = {
|
||||
const type = action.payload.calType === "temp" ? "templist" : "list";
|
||||
|
||||
if (!state[type][action.payload.calId]) {
|
||||
state[type][action.payload.calId] = {
|
||||
id: action.payload.calId,
|
||||
events: {},
|
||||
} as Calendars;
|
||||
}
|
||||
action.payload.events.forEach((event) => {
|
||||
state.list[action.payload.calId].events[event.uid] = event;
|
||||
});
|
||||
Object.keys(state.list[action.payload.calId].events).forEach((id) => {
|
||||
state.list[action.payload.calId].events[id].color =
|
||||
state.list[action.payload.calId].color;
|
||||
state.list[action.payload.calId].events[id].calId =
|
||||
action.payload.calId;
|
||||
state.list[action.payload.calId].events[id].timezone =
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
state[type][action.payload.calId].events[event.uid] = event;
|
||||
});
|
||||
Object.keys(state[type][action.payload.calId].events).forEach(
|
||||
(id) => {
|
||||
state[type][action.payload.calId].events[id].color =
|
||||
state[type][action.payload.calId].color;
|
||||
state[type][action.payload.calId].events[id].calId =
|
||||
action.payload.calId;
|
||||
state[type][action.payload.calId].events[id].timezone =
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
}
|
||||
);
|
||||
}
|
||||
)
|
||||
.addCase(
|
||||
@@ -484,12 +558,20 @@ const CalendarSlice = createSlice({
|
||||
.addCase(createCalendarAsync.pending, (state) => {
|
||||
state.pending = true;
|
||||
})
|
||||
.addCase(getTempCalendarsListAsync.pending, (state) => {
|
||||
state.pending = true;
|
||||
})
|
||||
.addCase(addSharedCalendarAsync.pending, (state) => {
|
||||
state.pending = true;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { addEvent, removeEvent, createCalendar, updateEventLocal } =
|
||||
CalendarSlice.actions;
|
||||
export const {
|
||||
addEvent,
|
||||
removeEvent,
|
||||
createCalendar,
|
||||
updateEventLocal,
|
||||
removeTempCal,
|
||||
} = CalendarSlice.actions;
|
||||
export default CalendarSlice.reducer;
|
||||
|
||||
@@ -45,22 +45,24 @@ import { getCalendar } from "../Calendars/CalendarApi";
|
||||
export default function EventPreviewModal({
|
||||
eventId,
|
||||
calId,
|
||||
tempEvent,
|
||||
anchorPosition,
|
||||
open,
|
||||
onClose,
|
||||
}: {
|
||||
eventId: string;
|
||||
calId: string;
|
||||
tempEvent?: boolean;
|
||||
anchorPosition: PopoverPosition | null;
|
||||
open: boolean;
|
||||
onClose: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void;
|
||||
}) {
|
||||
const dispatch = useAppDispatch();
|
||||
const calendars = useAppSelector((state) => state.calendars);
|
||||
const calendar = calendars.list[calId];
|
||||
const event = useAppSelector(
|
||||
(state) => state.calendars.list[calId]?.events[eventId]
|
||||
);
|
||||
const calendar = tempEvent
|
||||
? calendars.templist[calId]
|
||||
: calendars.list[calId];
|
||||
const event = calendar.events[eventId];
|
||||
const user = useAppSelector((state) => state.user);
|
||||
const [showAllAttendees, setShowAllAttendees] = useState(false);
|
||||
const [openFullDisplay, setOpenFullDisplay] = useState(false);
|
||||
|
||||
@@ -67,13 +67,16 @@ function EventPopover({
|
||||
const [showMore, setShowMore] = useState(false);
|
||||
|
||||
const [title, setTitle] = useState(event?.title ?? "");
|
||||
|
||||
const [description, setDescription] = useState(event?.description ?? "");
|
||||
const [location, setLocation] = useState(event?.location ?? "");
|
||||
const [start, setStart] = useState(
|
||||
event?.start ? new Date(event.start).toISOString() : ""
|
||||
event?.start
|
||||
? new Date(event.start).toISOString()
|
||||
: new Date().toISOString()
|
||||
);
|
||||
const [end, setEnd] = useState(
|
||||
event?.end ? new Date(event.end)?.toISOString() : ""
|
||||
event?.end ? new Date(event.end)?.toISOString() : new Date().toISOString()
|
||||
);
|
||||
const [calendarid, setCalendarid] = useState(
|
||||
event?.calId
|
||||
@@ -104,6 +107,24 @@ function EventPopover({
|
||||
}
|
||||
}, [selectedRange]);
|
||||
|
||||
useEffect(() => {
|
||||
setTitle(event?.title ?? "");
|
||||
setAttendees(
|
||||
event?.attendee
|
||||
? event.attendee.filter((a) => a.cal_address !== organizer?.cal_address)
|
||||
: []
|
||||
);
|
||||
}, [event, organizer?.cal_address]);
|
||||
|
||||
const handleClose = () => {
|
||||
onClose({}, "backdropClick"); // Reset
|
||||
setTitle("");
|
||||
setDescription("");
|
||||
setAttendees([]);
|
||||
setLocation("");
|
||||
setCalendarid(0);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
const newEventUID = crypto.randomUUID();
|
||||
|
||||
@@ -142,7 +163,7 @@ function EventPopover({
|
||||
newEvent.attendee = newEvent.attendee.concat(attendees);
|
||||
}
|
||||
|
||||
dispatch(
|
||||
await dispatch(
|
||||
putEventAsync({
|
||||
cal: userPersonnalCalendars[calendarid],
|
||||
newEvent,
|
||||
@@ -153,6 +174,7 @@ function EventPopover({
|
||||
// Reset
|
||||
setTitle("");
|
||||
setDescription("");
|
||||
setAttendees([]);
|
||||
setLocation("");
|
||||
setCalendarid(0);
|
||||
};
|
||||
@@ -161,7 +183,7 @@ function EventPopover({
|
||||
<Popover
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={onClose}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: "center",
|
||||
horizontal: "center",
|
||||
@@ -172,7 +194,7 @@ function EventPopover({
|
||||
}}
|
||||
>
|
||||
<Card>
|
||||
<CardHeader title={event ? "Duplicate Event" : "Create Event"} />
|
||||
<CardHeader title={event?.uid ? "Duplicate Event" : "Create Event"} />
|
||||
<CardContent
|
||||
sx={{ maxHeight: "85vh", maxWidth: "40vw", overflow: "auto" }}
|
||||
>
|
||||
@@ -359,10 +381,7 @@ function EventPopover({
|
||||
|
||||
<CardActions>
|
||||
<Box mt={2} display="flex" justifyContent="flex-end" gap={1}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => onClose({}, "backdropClick")}
|
||||
>
|
||||
<Button variant="outlined" onClick={handleClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="small" onClick={() => setShowMore(!showMore)}>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { User } from "../../components/Attendees/PeopleSearch";
|
||||
import { api } from "../../utils/apiUtils";
|
||||
|
||||
export async function getOpenPaasUser() {
|
||||
@@ -8,14 +9,7 @@ export async function getOpenPaasUser() {
|
||||
export async function searchUsers(
|
||||
query: string,
|
||||
objectTypes: string[] = ["user", "contact"]
|
||||
): Promise<
|
||||
{
|
||||
email: string;
|
||||
displayName: string;
|
||||
avatarUrl: string;
|
||||
openpaasId: string;
|
||||
}[]
|
||||
> {
|
||||
): Promise<User[]> {
|
||||
const response: any[] = await api
|
||||
.post(`api/people/search`, {
|
||||
body: JSON.stringify({
|
||||
|
||||
Reference in New Issue
Block a user