356 lines
10 KiB
TypeScript
356 lines
10 KiB
TypeScript
import { useAppDispatch, useAppSelector } from "@/app/hooks";
|
|
import { getCalendars } from "@/features/Calendars/CalendarApi";
|
|
import { Calendar } from "@/features/Calendars/CalendarTypes";
|
|
import { addSharedCalendarAsync } from "@/features/Calendars/services";
|
|
import { CalendarData } from "@/features/Calendars/types/CalendarData";
|
|
import { renameDefault } from "@/utils/renameDefault";
|
|
import {
|
|
Avatar,
|
|
Box,
|
|
Button,
|
|
IconButton,
|
|
TextField,
|
|
Typography,
|
|
useTheme,
|
|
} from "@linagora/twake-mui";
|
|
import CloseIcon from "@mui/icons-material/Close";
|
|
import { useState } from "react";
|
|
import { useI18n } from "twake-i18n";
|
|
import { PeopleSearch, User } from "../Attendees/PeopleSearch";
|
|
import { ResponsiveDialog } from "../Dialog";
|
|
import { stringAvatar } from "../Event/utils/eventUtils";
|
|
import { ColorPicker } from "./CalendarColorPicker";
|
|
import { defaultColors, getAccessiblePair } from "./utils/calendarColorsUtils";
|
|
|
|
interface CalendarWithOwner {
|
|
cal: CalendarData;
|
|
owner: User;
|
|
}
|
|
|
|
function CalendarItem({
|
|
cal,
|
|
onRemove,
|
|
onColorChange,
|
|
}: {
|
|
cal: CalendarWithOwner;
|
|
onRemove: () => void;
|
|
onColorChange: (color: Record<string, string>) => void;
|
|
}) {
|
|
const theme = useTheme();
|
|
const { t } = useI18n();
|
|
return (
|
|
<Box
|
|
key={cal.owner.email + cal.cal["dav:name"]}
|
|
display="flex"
|
|
flexDirection="column"
|
|
alignItems="flex-start"
|
|
style={{
|
|
borderRadius: 8,
|
|
border: "1px solid #e5e7eb",
|
|
padding: 8,
|
|
marginBottom: 8,
|
|
}}
|
|
>
|
|
<Box display="flex" alignItems="center" gap={2} mb={1}>
|
|
<Avatar
|
|
{...stringAvatar(cal.owner.displayName || cal.owner.email)}
|
|
style={{
|
|
border: `2px solid ${cal.cal["apple:color"] || defaultColors[0].light}`,
|
|
boxShadow: cal.cal["apple:color"]
|
|
? `0 0 0 2px ${cal.cal["apple:color"]}`
|
|
: `0 0 0 2px ${defaultColors[0].light}`,
|
|
}}
|
|
/>
|
|
<Box>
|
|
<Typography variant="body1">
|
|
{renameDefault(
|
|
cal.cal["dav:name"],
|
|
cal.owner.displayName,
|
|
t,
|
|
false
|
|
)}
|
|
</Typography>
|
|
<Typography variant="body2" color="textSecondary">
|
|
{cal.owner.email}
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
|
|
<Box display="flex" alignItems="center" gap={1}>
|
|
<ColorPicker
|
|
selectedColor={{
|
|
light: cal.cal["apple:color"] ?? defaultColors[0].light,
|
|
dark: cal.cal["apple:color"]
|
|
? getAccessiblePair(cal.cal["apple:color"], theme)
|
|
: defaultColors[0].dark,
|
|
}}
|
|
onChange={onColorChange}
|
|
/>
|
|
<IconButton size="small" onClick={onRemove}>
|
|
<CloseIcon fontSize="small" />
|
|
</IconButton>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
function SelectedCalendarsList({
|
|
calendars,
|
|
selectedCal,
|
|
onRemove,
|
|
onColorChange,
|
|
}: {
|
|
calendars: Record<string, Calendar>;
|
|
selectedCal: CalendarWithOwner[];
|
|
onRemove: (cal: CalendarWithOwner) => void;
|
|
onColorChange: (
|
|
cal: CalendarWithOwner,
|
|
color: Record<string, string>
|
|
) => void;
|
|
}) {
|
|
const { t } = useI18n();
|
|
if (selectedCal.length === 0) return null;
|
|
|
|
const groupedByOwner = selectedCal.reduce<
|
|
Record<
|
|
string,
|
|
{
|
|
owner: User;
|
|
visibleCals: CalendarWithOwner[];
|
|
alreadyExisting: boolean;
|
|
}
|
|
>
|
|
>((acc, cal) => {
|
|
const exists = Object.values(calendars).some(
|
|
(existing: Calendar) =>
|
|
existing.id ===
|
|
cal.cal?._links?.self?.href
|
|
?.replace("/calendars/", "")
|
|
.replace(".json", "")
|
|
);
|
|
|
|
if (!acc[cal.owner.email]) {
|
|
acc[cal.owner.email] = {
|
|
owner: cal.owner,
|
|
visibleCals: [],
|
|
alreadyExisting: false,
|
|
};
|
|
}
|
|
|
|
if (exists) {
|
|
acc[cal.owner.email].alreadyExisting = true;
|
|
} else {
|
|
acc[cal.owner.email].visibleCals.push(cal);
|
|
}
|
|
|
|
return acc;
|
|
}, {});
|
|
|
|
return (
|
|
<Box mt={2}>
|
|
<Typography variant="subtitle1" gutterBottom>
|
|
{t("common.name")}
|
|
</Typography>
|
|
|
|
{Object.values(groupedByOwner).map(
|
|
({ owner, visibleCals, alreadyExisting }) => (
|
|
<Box key={owner.email} mb={2}>
|
|
{visibleCals.length > 0 ? (
|
|
visibleCals.map((cal) =>
|
|
cal.cal ? (
|
|
<CalendarItem
|
|
key={cal.owner.email + cal.cal["dav:name"]}
|
|
cal={cal}
|
|
onRemove={() => onRemove(cal)}
|
|
onColorChange={(color) => onColorChange(cal, color)}
|
|
/>
|
|
) : (
|
|
<Typography
|
|
key={t("calendar.noPublicCalendarsFor", {
|
|
name: owner.displayName,
|
|
})}
|
|
color="textSecondary"
|
|
>
|
|
{t("calendar.noPublicCalendarsFor", {
|
|
name: owner.displayName,
|
|
})}
|
|
</Typography>
|
|
)
|
|
)
|
|
) : alreadyExisting ? (
|
|
<Typography
|
|
key={t("calendar.noMoreCalendarsFor", {
|
|
name: owner.displayName,
|
|
})}
|
|
color="textSecondary"
|
|
>
|
|
{t("calendar.noMoreCalendarsFor", { name: owner.displayName })}
|
|
</Typography>
|
|
) : null}
|
|
</Box>
|
|
)
|
|
)}
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
export default function CalendarSearch({
|
|
open,
|
|
onClose,
|
|
}: {
|
|
open: boolean;
|
|
onClose: (
|
|
result?: string[] | Record<string, never>,
|
|
reason?: "backdropClick" | "escapeKeyDown"
|
|
) => void;
|
|
}) {
|
|
const dispatch = useAppDispatch();
|
|
const theme = useTheme();
|
|
|
|
const openpaasId =
|
|
useAppSelector((state) => state.user.userData?.openpaasId) ?? "";
|
|
const calendars = useAppSelector((state) => state.calendars.list);
|
|
|
|
const [selectedCal, setSelectedCalendars] = useState<CalendarWithOwner[]>([]);
|
|
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
|
|
|
|
const handleSave = async () => {
|
|
if (selectedCal.length > 0) {
|
|
const idList = await Promise.all(
|
|
selectedCal.map(async (cal) => {
|
|
const calId = crypto.randomUUID();
|
|
const exists = Object.values(calendars).some(
|
|
(existing: Calendar) =>
|
|
existing.id ===
|
|
cal.cal?._links?.self?.href
|
|
?.replace("/calendars/", "")
|
|
.replace(".json", "")
|
|
);
|
|
if (!exists && cal.cal) {
|
|
await dispatch(
|
|
addSharedCalendarAsync({
|
|
userId: openpaasId,
|
|
calId,
|
|
cal: {
|
|
...cal,
|
|
color: cal.cal["apple:color"]
|
|
? {
|
|
light: cal.cal["apple:color"],
|
|
dark: getAccessiblePair(cal.cal["apple:color"], theme),
|
|
}
|
|
: defaultColors[0],
|
|
},
|
|
})
|
|
);
|
|
return cal.cal._links.self?.href
|
|
?.replace("/calendars/", "")
|
|
.replace(".json", "");
|
|
}
|
|
return null;
|
|
})
|
|
);
|
|
|
|
onClose(idList.filter(Boolean) as string[]);
|
|
setSelectedCalendars([]);
|
|
setSelectedUsers([]);
|
|
}
|
|
};
|
|
const { t } = useI18n();
|
|
|
|
return (
|
|
<ResponsiveDialog
|
|
open={open}
|
|
contentSx={{ paddingTop: "8px !important" }}
|
|
onClose={() => {
|
|
onClose({}, "backdropClick");
|
|
setSelectedCalendars([]);
|
|
setSelectedUsers([]);
|
|
}}
|
|
title={t("calendar.browseOtherCalendars")}
|
|
actions={
|
|
<>
|
|
<Button
|
|
variant="outlined"
|
|
onClick={() => onClose({}, "backdropClick")}
|
|
>
|
|
{t("common.cancel")}
|
|
</Button>
|
|
<Button variant="contained" onClick={handleSave}>
|
|
{t("actions.add")}
|
|
</Button>
|
|
</>
|
|
}
|
|
>
|
|
<PeopleSearch
|
|
objectTypes={["user"]}
|
|
selectedUsers={selectedUsers}
|
|
inputSlot={(params) => <TextField {...params} size="small" />}
|
|
onChange={async (_event: React.SyntheticEvent, value: User[]) => {
|
|
setSelectedUsers(value);
|
|
|
|
const cals = await Promise.all(
|
|
value.map(async (user: User) => {
|
|
if (user?.openpaasId) {
|
|
const cals = await getCalendars(
|
|
user.openpaasId,
|
|
"sharedPublic=true&"
|
|
);
|
|
return cals._embedded?.["dav:calendar"]
|
|
? cals._embedded["dav:calendar"].map((cal) => ({
|
|
cal,
|
|
owner: user,
|
|
}))
|
|
: { cal: undefined, owner: user };
|
|
}
|
|
return null;
|
|
})
|
|
);
|
|
setSelectedCalendars(
|
|
cals.flat().filter(Boolean) as CalendarWithOwner[]
|
|
);
|
|
}}
|
|
/>
|
|
|
|
<SelectedCalendarsList
|
|
calendars={calendars}
|
|
selectedCal={selectedCal}
|
|
onRemove={(cal) => {
|
|
if (!cal.cal?._links?.self?.href) return;
|
|
setSelectedCalendars((prev) =>
|
|
prev.filter(
|
|
(c) => c.cal?._links?.self?.href !== cal.cal._links.self?.href
|
|
)
|
|
);
|
|
if (
|
|
!selectedCal.find(
|
|
(c) =>
|
|
cal.owner.email === c.owner.email &&
|
|
c.cal?._links?.self?.href !== cal.cal._links.self?.href
|
|
)
|
|
) {
|
|
setSelectedUsers((prev) =>
|
|
prev.filter((u) => u.email !== cal.owner.email)
|
|
);
|
|
}
|
|
}}
|
|
onColorChange={(cal, color) =>
|
|
setSelectedCalendars((prev) =>
|
|
prev.map((prevcal) =>
|
|
prevcal.owner.email === cal.owner.email &&
|
|
prevcal.cal._links.self?.href === cal.cal._links.self?.href
|
|
? {
|
|
...prevcal,
|
|
cal: {
|
|
...prevcal.cal,
|
|
"apple:color": color.light,
|
|
},
|
|
}
|
|
: prevcal
|
|
)
|
|
)
|
|
}
|
|
/>
|
|
</ResponsiveDialog>
|
|
);
|
|
}
|