* #708 apply strictier linting rules and fix simple eslint bugs * #708 fix eslint errors relate to promise * #708 fix eslint import/no-extraneous-dependencies * #708 fix eslint errors of react-hook * #708 enable eslint check for typescript --------- Co-authored-by: lethemanh <lethemanh@lethemanhs-MacBook-Pro.local>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
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 { 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,
|
||||
@@ -11,61 +11,61 @@ import {
|
||||
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 { getAccessiblePair } from "@/utils/getAccessiblePair";
|
||||
import { defaultColors } from "@/utils/defaultColors";
|
||||
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 { getAccessiblePair } from '@/utils/getAccessiblePair'
|
||||
import { defaultColors } from '@/utils/defaultColors'
|
||||
|
||||
interface CalendarWithOwner {
|
||||
cal: CalendarData;
|
||||
owner: User;
|
||||
cal: CalendarData
|
||||
owner: User
|
||||
}
|
||||
|
||||
function CalendarItem({
|
||||
cal,
|
||||
onRemove,
|
||||
onColorChange,
|
||||
onColorChange
|
||||
}: {
|
||||
cal: CalendarWithOwner;
|
||||
onRemove: () => void;
|
||||
onColorChange: (color: Record<string, string>) => void;
|
||||
cal: CalendarWithOwner
|
||||
onRemove: () => void
|
||||
onColorChange: (color: Record<string, string>) => void
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const { t } = useI18n();
|
||||
const theme = useTheme()
|
||||
const { t } = useI18n()
|
||||
return (
|
||||
<Box
|
||||
key={cal.owner.email + cal.cal["dav:name"]}
|
||||
key={cal.owner.email + cal.cal['dav:name']}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
alignItems="flex-start"
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
border: "1px solid #e5e7eb",
|
||||
border: '1px solid #e5e7eb',
|
||||
padding: 8,
|
||||
marginBottom: 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}`,
|
||||
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.cal['dav:name'],
|
||||
cal.owner.displayName,
|
||||
t,
|
||||
false
|
||||
@@ -80,10 +80,10 @@ function CalendarItem({
|
||||
<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,
|
||||
light: cal.cal['apple:color'] ?? defaultColors[0].light,
|
||||
dark: cal.cal['apple:color']
|
||||
? getAccessiblePair(cal.cal['apple:color'], theme)
|
||||
: defaultColors[0].dark
|
||||
}}
|
||||
onChange={onColorChange}
|
||||
/>
|
||||
@@ -92,33 +92,30 @@ function CalendarItem({
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function SelectedCalendarsList({
|
||||
calendars,
|
||||
selectedCal,
|
||||
onRemove,
|
||||
onColorChange,
|
||||
onColorChange
|
||||
}: {
|
||||
calendars: Record<string, Calendar>;
|
||||
selectedCal: CalendarWithOwner[];
|
||||
onRemove: (cal: CalendarWithOwner) => void;
|
||||
onColorChange: (
|
||||
cal: CalendarWithOwner,
|
||||
color: Record<string, string>
|
||||
) => void;
|
||||
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 { t } = useI18n()
|
||||
if (selectedCal.length === 0) return null
|
||||
|
||||
const groupedByOwner = selectedCal.reduce<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
owner: User;
|
||||
visibleCals: CalendarWithOwner[];
|
||||
alreadyExisting: boolean;
|
||||
owner: User
|
||||
visibleCals: CalendarWithOwner[]
|
||||
alreadyExisting: boolean
|
||||
}
|
||||
>
|
||||
>((acc, cal) => {
|
||||
@@ -126,107 +123,107 @@ function SelectedCalendarsList({
|
||||
(existing: Calendar) =>
|
||||
existing.id ===
|
||||
cal.cal?._links?.self?.href
|
||||
?.replace("/calendars/", "")
|
||||
.replace(".json", "")
|
||||
);
|
||||
?.replace('/calendars/', '')
|
||||
.replace('.json', '')
|
||||
)
|
||||
|
||||
if (!acc[cal.owner.email]) {
|
||||
acc[cal.owner.email] = {
|
||||
owner: cal.owner,
|
||||
visibleCals: [],
|
||||
alreadyExisting: false,
|
||||
};
|
||||
alreadyExisting: false
|
||||
}
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
acc[cal.owner.email].alreadyExisting = true;
|
||||
acc[cal.owner.email].alreadyExisting = true
|
||||
} else {
|
||||
acc[cal.owner.email].visibleCals.push(cal);
|
||||
acc[cal.owner.email].visibleCals.push(cal)
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return (
|
||||
<Box mt={2}>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
{t("common.name")}
|
||||
{t('common.name')}
|
||||
</Typography>
|
||||
|
||||
{Object.values(groupedByOwner).map(
|
||||
({ owner, visibleCals, alreadyExisting }) => (
|
||||
<Box key={owner.email} mb={2}>
|
||||
{visibleCals.length > 0 ? (
|
||||
visibleCals.map((cal) =>
|
||||
visibleCals.map(cal =>
|
||||
cal.cal ? (
|
||||
<CalendarItem
|
||||
key={cal.owner.email + cal.cal["dav:name"]}
|
||||
key={cal.owner.email + cal.cal['dav:name']}
|
||||
cal={cal}
|
||||
onRemove={() => onRemove(cal)}
|
||||
onColorChange={(color) => onColorChange(cal, color)}
|
||||
onColorChange={color => onColorChange(cal, color)}
|
||||
/>
|
||||
) : (
|
||||
<Typography
|
||||
key={t("calendar.noPublicCalendarsFor", {
|
||||
name: owner.displayName,
|
||||
key={t('calendar.noPublicCalendarsFor', {
|
||||
name: owner.displayName
|
||||
})}
|
||||
color="textSecondary"
|
||||
>
|
||||
{t("calendar.noPublicCalendarsFor", {
|
||||
name: owner.displayName,
|
||||
{t('calendar.noPublicCalendarsFor', {
|
||||
name: owner.displayName
|
||||
})}
|
||||
</Typography>
|
||||
)
|
||||
)
|
||||
) : alreadyExisting ? (
|
||||
<Typography
|
||||
key={t("calendar.noMoreCalendarsFor", {
|
||||
name: owner.displayName,
|
||||
key={t('calendar.noMoreCalendarsFor', {
|
||||
name: owner.displayName
|
||||
})}
|
||||
color="textSecondary"
|
||||
>
|
||||
{t("calendar.noMoreCalendarsFor", { name: owner.displayName })}
|
||||
{t('calendar.noMoreCalendarsFor', { name: owner.displayName })}
|
||||
</Typography>
|
||||
) : null}
|
||||
</Box>
|
||||
)
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default function CalendarSearch({
|
||||
open,
|
||||
onClose,
|
||||
onClose
|
||||
}: {
|
||||
open: boolean;
|
||||
open: boolean
|
||||
onClose: (
|
||||
result?: string[] | Record<string, never>,
|
||||
reason?: "backdropClick" | "escapeKeyDown"
|
||||
) => void;
|
||||
reason?: 'backdropClick' | 'escapeKeyDown'
|
||||
) => void
|
||||
}) {
|
||||
const dispatch = useAppDispatch();
|
||||
const theme = useTheme();
|
||||
const dispatch = useAppDispatch()
|
||||
const theme = useTheme()
|
||||
|
||||
const openpaasId =
|
||||
useAppSelector((state) => state.user.userData?.openpaasId) ?? "";
|
||||
const calendars = useAppSelector((state) => state.calendars.list);
|
||||
useAppSelector(state => state.user.userData?.openpaasId) ?? ''
|
||||
const calendars = useAppSelector(state => state.calendars.list)
|
||||
|
||||
const [selectedCal, setSelectedCalendars] = useState<CalendarWithOwner[]>([]);
|
||||
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
|
||||
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();
|
||||
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", "")
|
||||
);
|
||||
?.replace('/calendars/', '')
|
||||
.replace('.json', '')
|
||||
)
|
||||
if (!exists && cal.cal) {
|
||||
await dispatch(
|
||||
addSharedCalendarAsync({
|
||||
@@ -234,117 +231,117 @@ export default function CalendarSearch({
|
||||
calId,
|
||||
cal: {
|
||||
...cal,
|
||||
color: cal.cal["apple:color"]
|
||||
color: cal.cal['apple:color']
|
||||
? {
|
||||
light: cal.cal["apple:color"],
|
||||
dark: getAccessiblePair(cal.cal["apple:color"], theme),
|
||||
light: cal.cal['apple:color'],
|
||||
dark: getAccessiblePair(cal.cal['apple:color'], theme)
|
||||
}
|
||||
: defaultColors[0],
|
||||
},
|
||||
: defaultColors[0]
|
||||
}
|
||||
})
|
||||
);
|
||||
)
|
||||
return cal.cal._links.self?.href
|
||||
?.replace("/calendars/", "")
|
||||
.replace(".json", "");
|
||||
?.replace('/calendars/', '')
|
||||
.replace('.json', '')
|
||||
}
|
||||
return null;
|
||||
return null
|
||||
})
|
||||
);
|
||||
)
|
||||
|
||||
onClose(idList.filter(Boolean) as string[]);
|
||||
setSelectedCalendars([]);
|
||||
setSelectedUsers([]);
|
||||
onClose(idList.filter(Boolean) as string[])
|
||||
setSelectedCalendars([])
|
||||
setSelectedUsers([])
|
||||
}
|
||||
};
|
||||
const { t } = useI18n();
|
||||
}
|
||||
const { t } = useI18n()
|
||||
|
||||
return (
|
||||
<ResponsiveDialog
|
||||
open={open}
|
||||
contentSx={{ paddingTop: "8px !important" }}
|
||||
contentSx={{ paddingTop: '8px !important' }}
|
||||
onClose={() => {
|
||||
onClose({}, "backdropClick");
|
||||
setSelectedCalendars([]);
|
||||
setSelectedUsers([]);
|
||||
onClose({}, 'backdropClick')
|
||||
setSelectedCalendars([])
|
||||
setSelectedUsers([])
|
||||
}}
|
||||
title={t("calendar.browseOtherCalendars")}
|
||||
title={t('calendar.browseOtherCalendars')}
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => onClose({}, "backdropClick")}
|
||||
onClick={() => onClose({}, 'backdropClick')}
|
||||
>
|
||||
{t("common.cancel")}
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleSave}>
|
||||
{t("actions.add")}
|
||||
{t('actions.add')}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<PeopleSearch
|
||||
objectTypes={["user"]}
|
||||
objectTypes={['user']}
|
||||
selectedUsers={selectedUsers}
|
||||
inputSlot={(params) => <TextField {...params} size="small" />}
|
||||
inputSlot={params => <TextField {...params} size="small" />}
|
||||
onChange={async (_event: React.SyntheticEvent, value: User[]) => {
|
||||
setSelectedUsers(value);
|
||||
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) => ({
|
||||
'sharedPublic=true&'
|
||||
)
|
||||
return cals._embedded?.['dav:calendar']
|
||||
? cals._embedded['dav:calendar'].map(cal => ({
|
||||
cal,
|
||||
owner: user,
|
||||
owner: user
|
||||
}))
|
||||
: { cal: undefined, owner: user };
|
||||
: { cal: undefined, owner: user }
|
||||
}
|
||||
return null;
|
||||
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) =>
|
||||
onRemove={cal => {
|
||||
if (!cal.cal?._links?.self?.href) return
|
||||
setSelectedCalendars(prev =>
|
||||
prev.filter(
|
||||
(c) => c.cal?._links?.self?.href !== cal.cal._links.self?.href
|
||||
c => c.cal?._links?.self?.href !== cal.cal._links.self?.href
|
||||
)
|
||||
);
|
||||
)
|
||||
if (
|
||||
!selectedCal.find(
|
||||
(c) =>
|
||||
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)
|
||||
);
|
||||
setSelectedUsers(prev =>
|
||||
prev.filter(u => u.email !== cal.owner.email)
|
||||
)
|
||||
}
|
||||
}}
|
||||
onColorChange={(cal, color) =>
|
||||
setSelectedCalendars((prev) =>
|
||||
prev.map((prevcal) =>
|
||||
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,
|
||||
},
|
||||
'apple:color': color.light
|
||||
}
|
||||
}
|
||||
: prevcal
|
||||
)
|
||||
@@ -352,5 +349,5 @@ export default function CalendarSearch({
|
||||
}
|
||||
/>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user