Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import PeopleOutlineOutlinedIcon from "@mui/icons-material/PeopleOutlineOutlined
|
||||
import Chip from "@mui/material/Chip";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { getAccessiblePair } from "../Calendar/utils/calendarColorsUtils";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
export interface User {
|
||||
email: string;
|
||||
@@ -35,6 +36,7 @@ export function PeopleSearch({
|
||||
freeSolo?: boolean;
|
||||
onToggleEventPreview?: Function;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const [query, setQuery] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [options, setOptions] = useState<User[]>([]);
|
||||
@@ -79,7 +81,7 @@ export function PeopleSearch({
|
||||
onChange={(event, value) => {
|
||||
const last = value[value.length - 1];
|
||||
if (typeof last === "string" && !isValidEmail(last)) {
|
||||
setError(`"${last}" is not a valid email address`);
|
||||
setError(t("peopleSearch.invalidEmail", { email: last }));
|
||||
return;
|
||||
}
|
||||
setError(null);
|
||||
@@ -93,8 +95,8 @@ export function PeopleSearch({
|
||||
{...params}
|
||||
error={!!error}
|
||||
helperText={error}
|
||||
placeholder="Start typing a name or email"
|
||||
label="Start typing a name or email"
|
||||
placeholder={t("peopleSearch.placeholder")}
|
||||
label={t("peopleSearch.label")}
|
||||
autoComplete="off"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && onToggleEventPreview) {
|
||||
@@ -134,7 +136,7 @@ export function PeopleSearch({
|
||||
if (selectedUsers.find((u) => u.email === option.email)) return null;
|
||||
const { key, ...otherProps } = props as any;
|
||||
return (
|
||||
<ListItem key={key} {...otherProps} disableGutters>
|
||||
<ListItem key={key + option?.email} {...otherProps} disableGutters>
|
||||
<ListItemAvatar>
|
||||
<Avatar src={option.avatarUrl} alt={option.displayName} />
|
||||
</ListItemAvatar>
|
||||
|
||||
@@ -3,10 +3,13 @@ import { Box, IconButton, TextField } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import { Calendars } from "../../features/Calendars/CalendarTypes";
|
||||
import { SnackbarAlert } from "../Loading/SnackBarAlert";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
export function AccessTab({ calendar }: { calendar: Calendars }) {
|
||||
const { t } = useI18n();
|
||||
const calDAVLink = `${(window as any).CALENDAR_BASE_URL}${calendar.link.replace(".json", "")}`;
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(calDAVLink);
|
||||
setOpen(true);
|
||||
@@ -18,7 +21,7 @@ export function AccessTab({ calendar }: { calendar: Calendars }) {
|
||||
<TextField
|
||||
disabled
|
||||
fullWidth
|
||||
label="CalDAV access"
|
||||
label={t("calendar.caldav_access")}
|
||||
value={calDAVLink}
|
||||
slotProps={{
|
||||
input: {
|
||||
@@ -31,7 +34,11 @@ export function AccessTab({ calendar }: { calendar: Calendars }) {
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<SnackbarAlert setOpen={setOpen} open={open} message="Link copied!" />
|
||||
<SnackbarAlert
|
||||
setOpen={setOpen}
|
||||
open={open}
|
||||
message={t("common.link_copied")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import { MiniCalendar } from "./MiniCalendar";
|
||||
import { User } from "../Attendees/PeopleSearch";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { updateDarkColor } from "./utils/calendarColorsUtils";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
interface CalendarAppProps {
|
||||
calendarRef: React.RefObject<CalendarApi | null>;
|
||||
@@ -286,6 +287,8 @@ export default function CalendarApp({
|
||||
(window as any).__calendarRef = calendarRef;
|
||||
}
|
||||
|
||||
const { t, lang } = useI18n();
|
||||
|
||||
return (
|
||||
<main className="main-layout">
|
||||
<Box
|
||||
@@ -312,7 +315,7 @@ export default function CalendarApp({
|
||||
eventHandlers.handleDateSelect(null as unknown as DateSelectArg)
|
||||
}
|
||||
>
|
||||
<AddIcon /> <p>Create Event</p>
|
||||
<AddIcon /> <p>{t("event.createEvent")}</p>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
@@ -356,6 +359,7 @@ export default function CalendarApp({
|
||||
initialView="timeGridWeek"
|
||||
firstDay={1}
|
||||
editable={true}
|
||||
locale={lang}
|
||||
selectable={true}
|
||||
timeZone={timezone}
|
||||
height={"100%"}
|
||||
@@ -382,7 +386,9 @@ export default function CalendarApp({
|
||||
weekNumberContent={(arg) => {
|
||||
return (
|
||||
<div className="weekSelector">
|
||||
<div>{arg.text}</div>
|
||||
<div>
|
||||
{t("menubar.views.week")} {arg.num}
|
||||
</div>
|
||||
<TimezoneSelector
|
||||
value={timezone}
|
||||
onChange={(newTimezone: string) =>
|
||||
@@ -393,7 +399,7 @@ export default function CalendarApp({
|
||||
);
|
||||
}}
|
||||
dayCellContent={(arg) => {
|
||||
const month = arg.date.toLocaleDateString("en-US", {
|
||||
const month = arg.date.toLocaleDateString(t("locale"), {
|
||||
month: "short",
|
||||
});
|
||||
if (arg.view.type === "dayGridMonth") {
|
||||
@@ -453,7 +459,7 @@ export default function CalendarApp({
|
||||
dayHeaderContent={(arg) => {
|
||||
const date = arg.date.getDate();
|
||||
const weekDay = arg.date
|
||||
.toLocaleDateString("en-US", { weekday: "short" })
|
||||
.toLocaleDateString(t("locale"), { weekday: "short" })
|
||||
.toUpperCase();
|
||||
return (
|
||||
<div className="fc-daygrid-day-top">
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import { HexColorPicker } from "react-colorful";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
import { defaultColors, getAccessiblePair } from "./utils/calendarColorsUtils";
|
||||
|
||||
export function ColorPicker({
|
||||
@@ -54,6 +55,7 @@ export function ColorPicker({
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ColorBox({
|
||||
color,
|
||||
onChange,
|
||||
@@ -107,11 +109,11 @@ function ColorPickerBox({
|
||||
onChange: (color: Record<string, string>) => void;
|
||||
selectedColor: Record<string, string>;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const [oldColor] = useState(
|
||||
selectedColor ?? { light: "#ffffff", dark: "#808080" }
|
||||
);
|
||||
const [color, setColor] = useState(oldColor);
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const theme = useTheme();
|
||||
@@ -139,12 +141,13 @@ function ColorPickerBox({
|
||||
setColor(newColor);
|
||||
onChange(newColor);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
key={"colorPicker"}
|
||||
role="button"
|
||||
aria-label={`select custom color`}
|
||||
aria-label={t("colorPicker.selectCustom")}
|
||||
onClick={handleClick}
|
||||
style={{
|
||||
width: "46px",
|
||||
@@ -198,10 +201,10 @@ function ColorPickerBox({
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle1" fontWeight="600">
|
||||
Choose custom colour
|
||||
{t("colorPicker.title")}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ mb: 2, color: "text.secondary" }}>
|
||||
Choose a background colour for this calendar
|
||||
{t("colorPicker.subtitle")}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mb: 2 }}>
|
||||
@@ -214,7 +217,7 @@ function ColorPickerBox({
|
||||
|
||||
<Box sx={{ display: "flex", alignItems: "center", mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ mr: 1 }}>
|
||||
Hex
|
||||
{t("colorPicker.hex")}
|
||||
</Typography>
|
||||
<TextField
|
||||
value={color.light?.toUpperCase()}
|
||||
@@ -226,13 +229,13 @@ function ColorPickerBox({
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex", justifyContent: "flex-end", gap: 1 }}>
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
<Button onClick={handleClose}>{t("common.cancel")}</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSave}
|
||||
sx={{ textTransform: "none" }}
|
||||
>
|
||||
Save
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Popover>
|
||||
|
||||
@@ -4,9 +4,9 @@ import { Calendars } from "../../features/Calendars/CalendarTypes";
|
||||
import { CalendarName } from "./CalendarName";
|
||||
|
||||
export function CalendarItemList(
|
||||
userPersonnalCalendars: Calendars[]
|
||||
userPersonalCalendars: Calendars[]
|
||||
): React.ReactNode {
|
||||
return Object.values(userPersonnalCalendars).map((calendar) => (
|
||||
return Object.values(userPersonalCalendars).map((calendar) => (
|
||||
<MenuItem key={calendar.id} value={calendar.id}>
|
||||
<CalendarName calendar={calendar} />
|
||||
</MenuItem>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { AccessTab } from "./AccessTab";
|
||||
import { ImportTab } from "./ImportTab";
|
||||
import { SettingsTab } from "./SettingsTab";
|
||||
import { defaultColors } from "./utils/calendarColorsUtils";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
function CalendarPopover({
|
||||
open,
|
||||
@@ -26,6 +27,7 @@ function CalendarPopover({
|
||||
) => void;
|
||||
calendar?: Calendars;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const dispatch = useAppDispatch();
|
||||
const userId =
|
||||
useAppSelector((state) => state.user.userData?.openpaasId) ?? "";
|
||||
@@ -181,10 +183,18 @@ function CalendarPopover({
|
||||
<Tabs value={tab} onChange={(e, v) => setTab(v)}>
|
||||
<Tab
|
||||
value="settings"
|
||||
label={calendar ? "Settings" : "Add new calendar"}
|
||||
label={
|
||||
calendar
|
||||
? t("calendarPopover.tabs.settings")
|
||||
: t("calendarPopover.tabs.addNew")
|
||||
}
|
||||
/>
|
||||
{calendar && <Tab value="access" label="Access" />}
|
||||
{isOwn && <Tab value="import" label="Import" />}
|
||||
{calendar && (
|
||||
<Tab value="access" label={t("calendarPopover.tabs.access")} />
|
||||
)}
|
||||
{isOwn && (
|
||||
<Tab value="import" label={t("calendarPopover.tabs.import")} />
|
||||
)}
|
||||
</Tabs>
|
||||
}
|
||||
actions={
|
||||
@@ -193,14 +203,18 @@ function CalendarPopover({
|
||||
variant="outlined"
|
||||
onClick={(e) => handleClose({}, "backdropClick")}
|
||||
>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={tab === "import" ? !importedContent : !name.trim()}
|
||||
variant="contained"
|
||||
onClick={tab === "import" ? handleImport : handleSave}
|
||||
>
|
||||
{tab === "import" ? "Import" : calendar ? "Save" : "Create"}
|
||||
{tab === "import"
|
||||
? t("actions.import")
|
||||
: calendar
|
||||
? t("actions.save")
|
||||
: t("actions.create")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
}
|
||||
|
||||
@@ -2,23 +2,19 @@ import CloseIcon from "@mui/icons-material/Close";
|
||||
import Avatar from "@mui/material/Avatar";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Card from "@mui/material/Card";
|
||||
import CardActions from "@mui/material/CardActions";
|
||||
import CardContent from "@mui/material/CardContent";
|
||||
import CardHeader from "@mui/material/CardHeader";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Modal from "@mui/material/Modal";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
import { useState } from "react";
|
||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||
import { getCalendars } from "../../features/Calendars/CalendarApi";
|
||||
import { addSharedCalendarAsync } from "../../features/Calendars/CalendarSlice";
|
||||
import { ColorPicker } from "./CalendarColorPicker";
|
||||
import { Calendars } from "../../features/Calendars/CalendarTypes";
|
||||
import { User, PeopleSearch } from "../Attendees/PeopleSearch";
|
||||
import { defaultColors, getAccessiblePair } from "./utils/calendarColorsUtils";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { PeopleSearch, User } from "../Attendees/PeopleSearch";
|
||||
import { ResponsiveDialog } from "../Dialog";
|
||||
import { ColorPicker } from "./CalendarColorPicker";
|
||||
import { defaultColors, getAccessiblePair } from "./utils/calendarColorsUtils";
|
||||
|
||||
interface CalendarWithOwner {
|
||||
cal: Record<string, any>;
|
||||
@@ -104,6 +100,7 @@ function SelectedCalendarsList({
|
||||
color: Record<string, string>
|
||||
) => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
if (selectedCal.length === 0) return null;
|
||||
|
||||
const groupedByOwner = selectedCal.reduce<
|
||||
@@ -144,7 +141,7 @@ function SelectedCalendarsList({
|
||||
return (
|
||||
<Box mt={2}>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Name
|
||||
{t("common.name")}
|
||||
</Typography>
|
||||
|
||||
{Object.values(groupedByOwner).map(
|
||||
@@ -160,14 +157,26 @@ function SelectedCalendarsList({
|
||||
onColorChange={(color) => onColorChange(cal, color)}
|
||||
/>
|
||||
) : (
|
||||
<Typography color="textSecondary">
|
||||
No publicly available calendars for {owner.displayName}
|
||||
<Typography
|
||||
key={t("calendar.noPublicCalendarsFor", {
|
||||
name: owner.displayName,
|
||||
})}
|
||||
color="textSecondary"
|
||||
>
|
||||
{t("calendar.noPublicCalendarsFor", {
|
||||
name: owner.displayName,
|
||||
})}
|
||||
</Typography>
|
||||
)
|
||||
)
|
||||
) : alreadyExisting ? (
|
||||
<Typography color="textSecondary">
|
||||
No more Calendar for {owner.displayName}
|
||||
<Typography
|
||||
key={t("calendar.noMoreCalendarsFor", {
|
||||
name: owner.displayName,
|
||||
})}
|
||||
color="textSecondary"
|
||||
>
|
||||
{t("calendar.noMoreCalendarsFor", { name: owner.displayName })}
|
||||
</Typography>
|
||||
) : null}
|
||||
</Box>
|
||||
@@ -230,6 +239,7 @@ export default function CalendarSearch({
|
||||
setSelectedUsers([]);
|
||||
}
|
||||
};
|
||||
const { t } = useI18n();
|
||||
|
||||
return (
|
||||
<ResponsiveDialog
|
||||
@@ -240,17 +250,17 @@ export default function CalendarSearch({
|
||||
setSelectedCalendars([]);
|
||||
setSelectedUsers([]);
|
||||
}}
|
||||
title="Browse other calendars"
|
||||
title={t("calendar.browseOtherCalendars")}
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => onClose({}, "backdropClick")}
|
||||
>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleSave}>
|
||||
Add
|
||||
{t("actions.add")}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Divider, ListItem, Menu, MenuItem } from "@mui/material";
|
||||
import { removeCalendarAsync } from "../../features/Calendars/CalendarSlice";
|
||||
import { DeleteCalendarDialog } from "./DeleteCalendarDialog";
|
||||
import { trimLongTextWithoutSpace } from "../../utils/textUtils";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
function CalendarAccordion({
|
||||
title,
|
||||
@@ -80,7 +81,7 @@ function CalendarAccordion({
|
||||
key={id}
|
||||
calendars={allCalendars}
|
||||
id={id}
|
||||
isPersonnal={defaultExpanded}
|
||||
isPersonal={defaultExpanded}
|
||||
selectedCalendars={selectedCalendars}
|
||||
handleCalendarToggle={handleToggle}
|
||||
setOpen={() => setOpen(id)}
|
||||
@@ -98,11 +99,12 @@ export default function CalendarSelection({
|
||||
selectedCalendars: string[];
|
||||
setSelectedCalendars: Function;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const userId =
|
||||
useAppSelector((state) => state.user.userData?.openpaasId) ?? "";
|
||||
const calendars = useAppSelector((state) => state.calendars.list);
|
||||
|
||||
const personnalCalendars = Object.keys(calendars).filter(
|
||||
const personalCalendars = Object.keys(calendars).filter(
|
||||
(id) => id.split("/")[0] === userId
|
||||
);
|
||||
const delegatedCalendars = Object.keys(calendars).filter(
|
||||
@@ -127,8 +129,8 @@ export default function CalendarSelection({
|
||||
<>
|
||||
<div>
|
||||
<CalendarAccordion
|
||||
title="Personnal Calendars"
|
||||
calendars={personnalCalendars}
|
||||
title={t("calendar.personal")}
|
||||
calendars={personalCalendars}
|
||||
selectedCalendars={selectedCalendars}
|
||||
handleToggle={handleCalendarToggle}
|
||||
showAddButton
|
||||
@@ -141,7 +143,7 @@ export default function CalendarSelection({
|
||||
/>
|
||||
|
||||
<CalendarAccordion
|
||||
title="Delegated Calendars"
|
||||
title={t("calendar.delegated")}
|
||||
calendars={delegatedCalendars}
|
||||
selectedCalendars={selectedCalendars}
|
||||
handleToggle={handleCalendarToggle}
|
||||
@@ -155,7 +157,7 @@ export default function CalendarSelection({
|
||||
/>
|
||||
|
||||
<CalendarAccordion
|
||||
title="Other Calendars"
|
||||
title={t("calendar.other")}
|
||||
calendars={sharedCalendars}
|
||||
selectedCalendars={selectedCalendars}
|
||||
showAddButton
|
||||
@@ -197,18 +199,19 @@ export default function CalendarSelection({
|
||||
function CalendarSelector({
|
||||
calendars,
|
||||
id,
|
||||
isPersonnal,
|
||||
isPersonal,
|
||||
selectedCalendars,
|
||||
handleCalendarToggle,
|
||||
setOpen,
|
||||
}: {
|
||||
calendars: Record<string, Calendars>;
|
||||
id: string;
|
||||
isPersonnal: boolean;
|
||||
isPersonal: boolean;
|
||||
selectedCalendars: string[];
|
||||
handleCalendarToggle: (name: string) => void;
|
||||
setOpen: Function;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const dispatch = useAppDispatch();
|
||||
const calLink =
|
||||
useAppSelector((state) => state.calendars.list[id].link) ?? "";
|
||||
@@ -222,7 +225,7 @@ function CalendarSelector({
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const [userId, calId] = id.split("/");
|
||||
const isDefault = isPersonnal && userId === calId;
|
||||
const isDefault = isPersonal && userId === calId;
|
||||
|
||||
const [deletePopupOpen, setDeletePopupOpen] = useState(false);
|
||||
const handleDeleteConfirm = () => {
|
||||
@@ -244,14 +247,10 @@ function CalendarSelector({
|
||||
justifyContent: "space-between",
|
||||
transition: "background-color 0.2s ease",
|
||||
padding: "8px 24px 8px 16px",
|
||||
"& .MoreBtn": {
|
||||
opacity: 0,
|
||||
},
|
||||
"& .MoreBtn": { opacity: 0 },
|
||||
"&:hover": {
|
||||
backgroundColor: "#F3F3F6",
|
||||
"& .MoreBtn": {
|
||||
opacity: 1,
|
||||
},
|
||||
"& .MoreBtn": { opacity: 1 },
|
||||
},
|
||||
}}
|
||||
>
|
||||
@@ -293,12 +292,12 @@ function CalendarSelector({
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
Modify
|
||||
{t("actions.modify")}
|
||||
</MenuItem>
|
||||
{!isDefault && <Divider />}
|
||||
{!isDefault && (
|
||||
<MenuItem onClick={() => setDeletePopupOpen(!deletePopupOpen)}>
|
||||
{isPersonnal ? "Delete" : "Remove"}
|
||||
{isPersonal ? t("actions.delete") : t("actions.remove")}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Menu>
|
||||
@@ -308,7 +307,7 @@ function CalendarSelector({
|
||||
setDeletePopupOpen={setDeletePopupOpen}
|
||||
calendars={calendars}
|
||||
id={id}
|
||||
isPersonnal={isPersonnal}
|
||||
isPersonal={isPersonal}
|
||||
handleDeleteConfirm={handleDeleteConfirm}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -5,37 +5,45 @@ import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import Button from "@mui/material/Button";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
export function DeleteCalendarDialog({
|
||||
deletePopupOpen,
|
||||
setDeletePopupOpen,
|
||||
calendars,
|
||||
id,
|
||||
isPersonnal,
|
||||
isPersonal,
|
||||
handleDeleteConfirm,
|
||||
}: {
|
||||
deletePopupOpen: boolean;
|
||||
setDeletePopupOpen: (e: boolean) => void;
|
||||
calendars: Record<string, Calendars>;
|
||||
id: string;
|
||||
isPersonnal: boolean;
|
||||
isPersonal: boolean;
|
||||
handleDeleteConfirm: () => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
|
||||
return (
|
||||
<Dialog open={deletePopupOpen} onClose={() => setDeletePopupOpen(false)}>
|
||||
<DialogTitle>Remove {calendars[id].name}?</DialogTitle>
|
||||
<DialogTitle>
|
||||
{t("calendar.delete.title", { name: calendars[id].name })}
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Are you sure you want to remove this calendar?{" "}
|
||||
{isPersonnal
|
||||
? "You will loose all events in this calendar."
|
||||
: "You will loose access to its events. You will still be able to add it back later."}
|
||||
{isPersonal
|
||||
? t("calendar.delete.personalWarning")
|
||||
: t("calendar.delete.sharedWarning")}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDeletePopupOpen(false)}>Cancel</Button>
|
||||
<Button onClick={() => setDeletePopupOpen(false)}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleDeleteConfirm} variant="contained">
|
||||
{isPersonnal ? "Delete" : "Remove"}
|
||||
{isPersonal ? t("actions.delete") : t("actions.remove")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useEffect, useState } from "react";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import { CalendarItemList } from "./CalendarItemList";
|
||||
import { SettingsTab } from "./SettingsTab";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
export function ImportTab({
|
||||
userId,
|
||||
@@ -37,11 +38,12 @@ export function ImportTab({
|
||||
setVisibility: Function;
|
||||
};
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const [importMode, setImportMode] = useState<"file" | "url">("file");
|
||||
const [importFile, setImportFile] = useState<File | null>(null);
|
||||
const [importUrl, setImportUrl] = useState("");
|
||||
const calendars = useAppSelector((state) => state.calendars.list);
|
||||
const personnalCalendars = Object.values(calendars).filter(
|
||||
const personalCalendars = Object.values(calendars).filter(
|
||||
(cal) => cal.id.split("/")[0] === userId
|
||||
);
|
||||
|
||||
@@ -59,14 +61,14 @@ export function ImportTab({
|
||||
size="small"
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
<ToggleButton value="file">File</ToggleButton>
|
||||
{/* <ToggleButton value="url">URL</ToggleButton> */}
|
||||
<ToggleButton value="file">{t("common.import_file")}</ToggleButton>
|
||||
{/* <ToggleButton value="url">{t("common.import_url")}</ToggleButton> */}
|
||||
</ToggleButtonGroup>
|
||||
|
||||
{importMode === "file" && (
|
||||
<>
|
||||
<Button variant="outlined" component="label" sx={{ mb: 1 }}>
|
||||
Select file
|
||||
{t("common.select_file")}
|
||||
<input
|
||||
type="file"
|
||||
hidden
|
||||
@@ -85,7 +87,7 @@ export function ImportTab({
|
||||
display="block"
|
||||
mb={2}
|
||||
>
|
||||
Import events from an ICS file to one of your calendars.
|
||||
{t("calendar.import_file_description")}
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
@@ -93,7 +95,7 @@ export function ImportTab({
|
||||
{importMode === "url" && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="ICS feed URL"
|
||||
label={t("calendar.ics_feed_url")}
|
||||
value={importUrl}
|
||||
onChange={(e) => setImportUrl(e.target.value)}
|
||||
size="small"
|
||||
@@ -102,15 +104,15 @@ export function ImportTab({
|
||||
)}
|
||||
|
||||
<FormControl fullWidth size="small" sx={{ mt: 2 }}>
|
||||
<InputLabel id="import-to-label">Import to</InputLabel>
|
||||
<InputLabel id="import-to-label">{t("calendar.import_to")}</InputLabel>
|
||||
<Select
|
||||
labelId="import-to-label"
|
||||
label="Import to"
|
||||
label={t("calendar.import_to")}
|
||||
value={importTarget}
|
||||
onChange={(e) => setImportTarget(e.target.value)}
|
||||
>
|
||||
<MenuItem value="new">New calendar</MenuItem>
|
||||
{CalendarItemList(personnalCalendars)}
|
||||
<MenuItem value="new">{t("calendar.new_calendar")}</MenuItem>
|
||||
{CalendarItemList(personalCalendars)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||
import { refreshCalendars } from "../Event/utils/eventUtils";
|
||||
import { getCalendarDetailAsync } from "../../features/Calendars/CalendarSlice";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
export function MiniCalendar({
|
||||
calendarRef,
|
||||
@@ -29,10 +30,14 @@ export function MiniCalendar({
|
||||
const dispatch = useAppDispatch();
|
||||
const calendars = useAppSelector((state) => state.calendars);
|
||||
const [visibleDate, setVisibleDate] = useState(selectedDate);
|
||||
const { t } = useI18n();
|
||||
|
||||
useEffect(() => setVisibleDate(selectedDate), [selectedDate]);
|
||||
return (
|
||||
<LocalizationProvider dateAdapter={AdapterMoment} adapterLocale="en-gb">
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterMoment}
|
||||
adapterLocale={t("locale") ?? "en-gb"}
|
||||
>
|
||||
<DateCalendar
|
||||
value={moment(visibleDate)}
|
||||
onChange={(dateMoment) => {
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
ToggleButtonGroup,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import { Calendars } from "../../features/Calendars/CalendarTypes";
|
||||
import { ColorPicker } from "./CalendarColorPicker";
|
||||
@@ -35,16 +36,21 @@ export function SettingsTab({
|
||||
setVisibility: Function;
|
||||
calendar?: Calendars;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const [toggleDesc, setToggleDesc] = useState(Boolean(description));
|
||||
const userId =
|
||||
useAppSelector((state) => state.user.userData?.openpaasId) ?? "";
|
||||
const isOwn = calendar ? calendar.id.split("/")[0] === userId : true;
|
||||
|
||||
useEffect(() => {
|
||||
if (description) setToggleDesc(true);
|
||||
}, [description]);
|
||||
|
||||
return (
|
||||
<Box mt={2}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Name"
|
||||
label={t("common.name")}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
size="small"
|
||||
@@ -58,14 +64,14 @@ export function SettingsTab({
|
||||
onClick={() => setToggleDesc(!toggleDesc)}
|
||||
startIcon={<FormatListBulletedIcon />}
|
||||
>
|
||||
Add description
|
||||
{t("calendar.addDescription")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{toggleDesc && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Description"
|
||||
label={t("common.description")}
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
size="small"
|
||||
@@ -77,7 +83,7 @@ export function SettingsTab({
|
||||
|
||||
<Box mt={2}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
Color
|
||||
{t("calendar.color")}
|
||||
</Typography>
|
||||
<ColorPicker
|
||||
onChange={(color) => setColor(color)}
|
||||
@@ -88,7 +94,7 @@ export function SettingsTab({
|
||||
{isOwn && (
|
||||
<Box mt={2}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
New events created will be visible to:
|
||||
{t("calendar.newEventsVisibility")}
|
||||
</Typography>
|
||||
<ToggleButtonGroup
|
||||
value={visibility}
|
||||
@@ -98,12 +104,12 @@ export function SettingsTab({
|
||||
>
|
||||
<ToggleButton value="public">
|
||||
<PublicIcon fontSize="small" />
|
||||
All
|
||||
{t("common.all")}
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton value="private">
|
||||
<LockIcon fontSize="small" />
|
||||
You
|
||||
{t("common.you")}
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Box>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Button, Popover } from "@mui/material";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
import { MouseEvent, useMemo, useState } from "react";
|
||||
import { TIMEZONES } from "../../utils/timezone-data";
|
||||
import { TimezoneAutocomplete } from "../Timezone/TimezoneAutocomplete";
|
||||
@@ -34,7 +35,7 @@ export function TimezoneSelector({ value, onChange }: TimezoneSelectProps) {
|
||||
};
|
||||
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
@@ -49,7 +50,7 @@ export function TimezoneSelector({ value, onChange }: TimezoneSelectProps) {
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{selectedOffset || "Select Timezone"}
|
||||
{selectedOffset || t("common.select_timezone")}
|
||||
</Button>
|
||||
|
||||
<Popover
|
||||
|
||||
@@ -4,8 +4,10 @@ import { Box, Button, Fade, Paper, Stack, Typography } from "@mui/material";
|
||||
import { useEffect } from "react";
|
||||
import { push } from "redux-first-history";
|
||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
export function Error() {
|
||||
const { t } = useI18n();
|
||||
const dispatch = useAppDispatch();
|
||||
const userError = useAppSelector((state) => state.user.error);
|
||||
const calendarError = useAppSelector((state) => state.calendars.error);
|
||||
@@ -16,7 +18,7 @@ export function Error() {
|
||||
}
|
||||
}, [calendarError, dispatch]);
|
||||
|
||||
const errorMessage = userError || calendarError || "Unknown error";
|
||||
const errorMessage = userError || calendarError || t("error.unknown");
|
||||
|
||||
return (
|
||||
<Fade in timeout={500}>
|
||||
@@ -56,7 +58,7 @@ export function Error() {
|
||||
</Box>
|
||||
|
||||
<Typography variant="h5" fontWeight={600}>
|
||||
Something went wrong
|
||||
{t("error.title")}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}>
|
||||
@@ -67,9 +69,7 @@ export function Error() {
|
||||
variant="contained"
|
||||
color="error"
|
||||
startIcon={<ReplayIcon />}
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
onClick={() => window.location.reload()}
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
fontWeight: 600,
|
||||
@@ -79,7 +79,7 @@ export function Error() {
|
||||
boxShadow: "none",
|
||||
}}
|
||||
>
|
||||
Try Again
|
||||
{t("error.retry")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -4,6 +4,7 @@ import Button from "@mui/material/Button";
|
||||
import { useAppDispatch } from "../../app/hooks";
|
||||
import { clearError as calendarClearError } from "../../features/Calendars/CalendarSlice";
|
||||
import { clearError as userClearError } from "../../features/User/userSlice";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
export function ErrorSnackbar({
|
||||
error,
|
||||
@@ -12,7 +13,9 @@ export function ErrorSnackbar({
|
||||
error: string | null;
|
||||
type: "user" | "calendar";
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleCloseSnackbar = () => {
|
||||
dispatch(type === "calendar" ? calendarClearError() : userClearError());
|
||||
};
|
||||
@@ -29,11 +32,11 @@ export function ErrorSnackbar({
|
||||
sx={{ width: "100%" }}
|
||||
action={
|
||||
<Button color="inherit" size="small" onClick={handleCloseSnackbar}>
|
||||
OK
|
||||
{t("common.ok")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{error}
|
||||
{error || t("error.unknown")}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
);
|
||||
@@ -46,11 +49,13 @@ export function EventErrorSnackbar({
|
||||
messages: string[];
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const open = messages.length > 0;
|
||||
|
||||
const summary =
|
||||
messages.length === 1
|
||||
? messages[0]
|
||||
: `${messages.length} events with errors`;
|
||||
: t("error.multipleEvents", { count: messages.length });
|
||||
|
||||
return (
|
||||
<Snackbar
|
||||
@@ -65,7 +70,7 @@ export function EventErrorSnackbar({
|
||||
sx={{ width: "100%" }}
|
||||
action={
|
||||
<Button color="inherit" size="small" onClick={onClose}>
|
||||
OK
|
||||
{t("common.ok")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@mui/material";
|
||||
import { CalendarEvent } from "../../features/Events/EventsTypes";
|
||||
import { useState } from "react";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
export function EditModeDialog({
|
||||
type,
|
||||
@@ -23,6 +24,7 @@ export function EditModeDialog({
|
||||
event: CalendarEvent;
|
||||
eventAction: (type: "solo" | "all" | undefined) => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const [typeOfAction, setTypeOfAction] = useState<"solo" | "all" | undefined>(
|
||||
"solo"
|
||||
);
|
||||
@@ -37,8 +39,8 @@ export function EditModeDialog({
|
||||
return (
|
||||
<Dialog open={Boolean(type)} onClose={handleClose}>
|
||||
<DialogTitle>
|
||||
{type === "edit" && "Update the reccurent event"}
|
||||
{type === "attendance" && "Update the participation status"}
|
||||
{type === "edit" && t("editModeDialog.updateRecurrentEvent")}
|
||||
{type === "attendance" && t("editModeDialog.updateParticipationStatus")}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<RadioGroup
|
||||
@@ -50,19 +52,19 @@ export function EditModeDialog({
|
||||
<FormControlLabel
|
||||
value="solo"
|
||||
control={<Radio />}
|
||||
label="This event"
|
||||
label={t("editModeDialog.thisEvent")}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="all"
|
||||
control={<Radio />}
|
||||
label="All the events"
|
||||
label={t("editModeDialog.allEvents")}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<ButtonGroup>
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
<Button onClick={handleEvent}>Ok</Button>
|
||||
<Button onClick={handleClose}>{t("common.cancel")}</Button>
|
||||
<Button onClick={handleEvent}>{t("common.ok")}</Button>
|
||||
</ButtonGroup>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MenuItem } from "@mui/material";
|
||||
import { CalendarEvent } from "../../features/Events/EventsTypes";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
export default function EventDuplication({
|
||||
onClose,
|
||||
@@ -10,13 +11,14 @@ export default function EventDuplication({
|
||||
event: CalendarEvent;
|
||||
onOpenDuplicate?: () => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
onOpenDuplicate?.();
|
||||
}}
|
||||
>
|
||||
Duplicate event
|
||||
{t("eventDuplication.duplicateEvent")}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
} from "../../utils/videoConferenceUtils";
|
||||
import { TimezoneAutocomplete } from "../Timezone/TimezoneAutocomplete";
|
||||
import { CalendarItemList } from "../Calendar/CalendarItemList";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
import { FieldWithLabel } from "./components/FieldWithLabel";
|
||||
import { DateTimeFields } from "./components/DateTimeFields";
|
||||
import { useAllDayToggle } from "./hooks/useAllDayToggle";
|
||||
@@ -84,7 +85,7 @@ interface EventFormFieldsProps {
|
||||
isOpen?: boolean;
|
||||
|
||||
// Data
|
||||
userPersonnalCalendars: Calendars[];
|
||||
userPersonalCalendars: Calendars[];
|
||||
timezoneList: {
|
||||
zones: string[];
|
||||
browserTz: string;
|
||||
@@ -145,7 +146,7 @@ export default function EventFormFields({
|
||||
showRepeat,
|
||||
setShowRepeat,
|
||||
isOpen = false,
|
||||
userPersonnalCalendars,
|
||||
userPersonalCalendars,
|
||||
timezoneList,
|
||||
onStartChange,
|
||||
onEndChange,
|
||||
@@ -155,6 +156,8 @@ export default function EventFormFields({
|
||||
showValidationErrors = false,
|
||||
onHasEndDateChangedChange,
|
||||
}: EventFormFieldsProps) {
|
||||
const { t } = useI18n();
|
||||
|
||||
// Internal state for 4 separate fields
|
||||
const [startDate, setStartDate] = React.useState("");
|
||||
const [startTime, setStartTime] = React.useState("");
|
||||
@@ -406,15 +409,15 @@ export default function EventFormFields({
|
||||
<FieldWithLabel
|
||||
label={
|
||||
<>
|
||||
Title <span style={{ color: "red" }}>*</span>
|
||||
{t("event.form.title")} <span style={{ color: "red" }}>*</span>
|
||||
</>
|
||||
}
|
||||
isExpanded={showMore}
|
||||
>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={!showMore ? "Title" : ""}
|
||||
placeholder="Add title"
|
||||
label={!showMore ? t("event.form.title") : ""}
|
||||
placeholder={t("event.form.titlePlaceholder")}
|
||||
value={title}
|
||||
onChange={(e) => {
|
||||
setTitle(e.target.value);
|
||||
@@ -439,18 +442,21 @@ export default function EventFormFields({
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
Add description
|
||||
{t("event.form.addDescription")}
|
||||
</Button>
|
||||
</Box>
|
||||
</FieldWithLabel>
|
||||
)}
|
||||
|
||||
{showDescription && (
|
||||
<FieldWithLabel label="Description" isExpanded={showMore}>
|
||||
<FieldWithLabel
|
||||
label={t("event.form.description")}
|
||||
isExpanded={showMore}
|
||||
>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={!showMore ? "Description" : ""}
|
||||
placeholder="Add description"
|
||||
label={!showMore ? t("event.form.description") : ""}
|
||||
placeholder={t("event.form.descriptionPlaceholder")}
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
size="small"
|
||||
@@ -471,7 +477,7 @@ export default function EventFormFields({
|
||||
</FieldWithLabel>
|
||||
)}
|
||||
|
||||
<FieldWithLabel label="Date & Time" isExpanded={showMore}>
|
||||
<FieldWithLabel label={t("event.form.dateTime")} isExpanded={showMore}>
|
||||
<DateTimeFields
|
||||
startDate={startDate}
|
||||
startTime={startTime}
|
||||
@@ -496,7 +502,7 @@ export default function EventFormFields({
|
||||
control={
|
||||
<Checkbox checked={allday} onChange={handleAllDayToggle} />
|
||||
}
|
||||
label="All day"
|
||||
label={t("event.form.allDay")}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
@@ -528,7 +534,7 @@ export default function EventFormFields({
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Repeat"
|
||||
label={t("event.form.repeat")}
|
||||
/>
|
||||
<TimezoneAutocomplete
|
||||
value={timezone}
|
||||
@@ -538,7 +544,7 @@ export default function EventFormFields({
|
||||
showIcon={true}
|
||||
width={240}
|
||||
size="small"
|
||||
placeholder="Select timezone"
|
||||
placeholder={t("event.form.timezonePlaceholder")}
|
||||
/>
|
||||
</Box>
|
||||
</FieldWithLabel>
|
||||
@@ -554,11 +560,17 @@ export default function EventFormFields({
|
||||
</FieldWithLabel>
|
||||
)}
|
||||
|
||||
<FieldWithLabel label="Participants" isExpanded={showMore}>
|
||||
<FieldWithLabel
|
||||
label={t("event.form.participants")}
|
||||
isExpanded={showMore}
|
||||
>
|
||||
<AttendeeSelector attendees={attendees} setAttendees={setAttendees} />
|
||||
</FieldWithLabel>
|
||||
|
||||
<FieldWithLabel label="Video meeting" isExpanded={showMore}>
|
||||
<FieldWithLabel
|
||||
label={t("event.form.videoMeeting")}
|
||||
isExpanded={showMore}
|
||||
>
|
||||
<Box display="flex" gap={1} alignItems="center">
|
||||
<Button
|
||||
startIcon={<VideocamIcon />}
|
||||
@@ -570,7 +582,7 @@ export default function EventFormFields({
|
||||
display: hasVideoConference ? "none" : "flex",
|
||||
}}
|
||||
>
|
||||
Add Visio conference
|
||||
{t("event.form.addVisioConference")}
|
||||
</Button>
|
||||
|
||||
{hasVideoConference && meetingLink && (
|
||||
@@ -585,14 +597,14 @@ export default function EventFormFields({
|
||||
mr: 1,
|
||||
}}
|
||||
>
|
||||
Join Visio conference
|
||||
{t("event.form.joinVisioConference")}
|
||||
</Button>
|
||||
<IconButton
|
||||
onClick={handleCopyMeetingLink}
|
||||
size="small"
|
||||
sx={{ color: "primary.main" }}
|
||||
aria-label="Copy meeting link"
|
||||
title="Copy meeting link"
|
||||
aria-label={t("event.form.copyMeetingLink")}
|
||||
title={t("event.form.copyMeetingLink")}
|
||||
>
|
||||
<CopyIcon />
|
||||
</IconButton>
|
||||
@@ -600,8 +612,8 @@ export default function EventFormFields({
|
||||
onClick={handleDeleteVideoConference}
|
||||
size="small"
|
||||
sx={{ color: "error.main" }}
|
||||
aria-label="Remove video conference"
|
||||
title="Remove video conference"
|
||||
aria-label={t("event.form.removeVideoConference")}
|
||||
title={t("event.form.removeVideoConference")}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
@@ -610,11 +622,11 @@ export default function EventFormFields({
|
||||
</Box>
|
||||
</FieldWithLabel>
|
||||
|
||||
<FieldWithLabel label="Location" isExpanded={showMore}>
|
||||
<FieldWithLabel label={t("event.form.location")} isExpanded={showMore}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={!showMore ? "Location" : ""}
|
||||
placeholder="Add location"
|
||||
label={!showMore ? t("event.form.location") : ""}
|
||||
placeholder={t("event.form.locationPlaceholder")}
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
size="small"
|
||||
@@ -622,65 +634,102 @@ export default function EventFormFields({
|
||||
/>
|
||||
</FieldWithLabel>
|
||||
|
||||
<FieldWithLabel label="Calendar" isExpanded={showMore}>
|
||||
<FieldWithLabel label={t("event.form.calendar")} isExpanded={showMore}>
|
||||
<FormControl fullWidth margin="dense" size="small">
|
||||
{!showMore && (
|
||||
<InputLabel id="calendar-select-label">Calendar</InputLabel>
|
||||
<InputLabel id="calendar-select-label">
|
||||
{t("event.form.calendar")}
|
||||
</InputLabel>
|
||||
)}
|
||||
<Select
|
||||
labelId="calendar-select-label"
|
||||
value={calendarid ?? ""}
|
||||
label={!showMore ? "Calendar" : ""}
|
||||
label={!showMore ? t("event.form.calendar") : ""}
|
||||
displayEmpty
|
||||
onChange={(e: SelectChangeEvent) =>
|
||||
handleCalendarChange(e.target.value)
|
||||
}
|
||||
>
|
||||
{CalendarItemList(userPersonnalCalendars)}
|
||||
{CalendarItemList(userPersonalCalendars)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</FieldWithLabel>
|
||||
|
||||
{showMore && (
|
||||
<>
|
||||
<FieldWithLabel label="Notification" isExpanded={showMore}>
|
||||
<FieldWithLabel
|
||||
label={t("event.form.notification")}
|
||||
isExpanded={showMore}
|
||||
>
|
||||
<FormControl fullWidth margin="dense" size="small">
|
||||
<Select
|
||||
labelId="notification"
|
||||
value={alarm}
|
||||
onChange={(e: SelectChangeEvent) => setAlarm(e.target.value)}
|
||||
>
|
||||
<MenuItem value={""}>No Notification</MenuItem>
|
||||
<MenuItem value={"-PT1M"}>1 minute</MenuItem>
|
||||
<MenuItem value={"-PT5M"}>2 minutes</MenuItem>
|
||||
<MenuItem value={"-PT10M"}>10 minutes</MenuItem>
|
||||
<MenuItem value={"-PT15M"}>15 minutes</MenuItem>
|
||||
<MenuItem value={"-PT30M"}>30 minutes</MenuItem>
|
||||
<MenuItem value={"-PT1H"}>1 hours</MenuItem>
|
||||
<MenuItem value={"-PT2H"}>2 hours</MenuItem>
|
||||
<MenuItem value={"-PT5H"}>5 hours</MenuItem>
|
||||
<MenuItem value={"-PT12H"}>12 hours</MenuItem>
|
||||
<MenuItem value={"-PT1D"}>1 day</MenuItem>
|
||||
<MenuItem value={"-PT2D"}>2 days</MenuItem>
|
||||
<MenuItem value={"-PT1W"}>1 week</MenuItem>
|
||||
<MenuItem value="">{t("event.form.notifications.")}</MenuItem>
|
||||
<MenuItem value="-PT1M">
|
||||
{t("event.form.notifications.-PT1M")}
|
||||
</MenuItem>
|
||||
<MenuItem value="-PT5M">
|
||||
{t("event.form.notifications.-PT5M")}
|
||||
</MenuItem>
|
||||
<MenuItem value="-PT10M">
|
||||
{t("event.form.notifications.-PT10M")}
|
||||
</MenuItem>
|
||||
<MenuItem value="-PT15M">
|
||||
{t("event.form.notifications.-PT15M")}
|
||||
</MenuItem>
|
||||
<MenuItem value="-PT30M">
|
||||
{t("event.form.notifications.-PT30M")}
|
||||
</MenuItem>
|
||||
<MenuItem value="-PT1H">
|
||||
{t("event.form.notifications.-PT1H")}
|
||||
</MenuItem>
|
||||
<MenuItem value="-PT2H">
|
||||
{t("event.form.notifications.-PT2H")}
|
||||
</MenuItem>
|
||||
<MenuItem value="-PT5H">
|
||||
{t("event.form.notifications.-PT5H")}
|
||||
</MenuItem>
|
||||
<MenuItem value="-PT12H">
|
||||
{t("event.form.notifications.-PT12H")}
|
||||
</MenuItem>
|
||||
<MenuItem value="-PT1D">
|
||||
{t("event.form.notifications.-PT1D")}
|
||||
</MenuItem>
|
||||
<MenuItem value="-PT2D">
|
||||
{t("event.form.notifications.-PT2D")}
|
||||
</MenuItem>
|
||||
<MenuItem value="-PT1W">
|
||||
{t("event.form.notifications.-PT1W")}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</FieldWithLabel>
|
||||
|
||||
<FieldWithLabel label="Show me as" isExpanded={showMore}>
|
||||
<FieldWithLabel
|
||||
label={t("event.form.showMeAs")}
|
||||
isExpanded={showMore}
|
||||
>
|
||||
<FormControl fullWidth margin="dense" size="small">
|
||||
<Select
|
||||
labelId="busy"
|
||||
value={busy}
|
||||
onChange={(e: SelectChangeEvent) => setBusy(e.target.value)}
|
||||
>
|
||||
<MenuItem value={"TRANSPARENT"}>Free</MenuItem>
|
||||
<MenuItem value={"OPAQUE"}>Busy </MenuItem>
|
||||
<MenuItem value={"TRANSPARENT"}>
|
||||
{t("event.form.free")}
|
||||
</MenuItem>
|
||||
<MenuItem value={"OPAQUE"}>{t("event.form.busy")}</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</FieldWithLabel>
|
||||
|
||||
<FieldWithLabel label="Visible to" isExpanded={showMore}>
|
||||
<FieldWithLabel
|
||||
label={t("event.form.visibleTo")}
|
||||
isExpanded={showMore}
|
||||
>
|
||||
<ToggleButtonGroup
|
||||
value={eventClass}
|
||||
exclusive
|
||||
@@ -693,11 +742,11 @@ export default function EventFormFields({
|
||||
>
|
||||
<ToggleButton value="PUBLIC" sx={{ width: "140px" }}>
|
||||
<PublicIcon sx={{ mr: 1, fontSize: "16px" }} />
|
||||
All
|
||||
{t("event.form.visibleAll")}
|
||||
</ToggleButton>
|
||||
<ToggleButton value="PRIVATE" sx={{ width: "140px" }}>
|
||||
<LockIcon sx={{ mr: 1, fontSize: "16px" }} />
|
||||
Participants
|
||||
{t("event.form.visibleParticipants")}
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</FieldWithLabel>
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import { RepetitionObject } from "../../features/Events/EventsTypes";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
|
||||
export default function RepeatEvent({
|
||||
repetition,
|
||||
@@ -27,6 +28,7 @@ export default function RepeatEvent({
|
||||
setRepetition: Function;
|
||||
isOwn?: boolean;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const days = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"];
|
||||
const day = new Date(eventStart);
|
||||
// derive endOption based on repetition
|
||||
@@ -60,12 +62,25 @@ export default function RepeatEvent({
|
||||
});
|
||||
};
|
||||
|
||||
const getDayLabel = (day: string) => {
|
||||
const dayMap: { [key: string]: string } = {
|
||||
MO: t("event.repeat.days.monday"),
|
||||
TU: t("event.repeat.days.tuesday"),
|
||||
WE: t("event.repeat.days.wednesday"),
|
||||
TH: t("event.repeat.days.thursday"),
|
||||
FR: t("event.repeat.days.friday"),
|
||||
SA: t("event.repeat.days.saturday"),
|
||||
SU: t("event.repeat.days.sunday"),
|
||||
};
|
||||
return dayMap[day] || day;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack>
|
||||
{/* Interval */}
|
||||
<Box display="flex" alignItems="center" gap={2} mb={2}>
|
||||
<Typography>Repeat every</Typography>
|
||||
<Typography>{t("event.repeat.repeatEvery")}</Typography>
|
||||
<TextField
|
||||
type="number"
|
||||
value={repetition.interval ?? 1}
|
||||
@@ -104,10 +119,18 @@ export default function RepeatEvent({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem value={"daily"}>Day(s)</MenuItem>
|
||||
<MenuItem value={"weekly"}>Week(s)</MenuItem>
|
||||
<MenuItem value={"monthly"}>Month(s)</MenuItem>
|
||||
<MenuItem value={"yearly"}>Year(s)</MenuItem>
|
||||
<MenuItem value={"daily"}>
|
||||
{t("event.repeat.frequency.days")}
|
||||
</MenuItem>
|
||||
<MenuItem value={"weekly"}>
|
||||
{t("event.repeat.frequency.weeks")}
|
||||
</MenuItem>
|
||||
<MenuItem value={"monthly"}>
|
||||
{t("event.repeat.frequency.months")}
|
||||
</MenuItem>
|
||||
<MenuItem value={"yearly"}>
|
||||
{t("event.repeat.frequency.years")}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
@@ -116,7 +139,7 @@ export default function RepeatEvent({
|
||||
{repetition.freq === "weekly" && (
|
||||
<Box>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
Repeat on:
|
||||
{t("event.repeat.repeatOn")}
|
||||
</Typography>
|
||||
<FormGroup row>
|
||||
{days.map((day) => (
|
||||
@@ -129,7 +152,7 @@ export default function RepeatEvent({
|
||||
onChange={() => handleDayChange(day)}
|
||||
/>
|
||||
}
|
||||
label={day}
|
||||
label={getDayLabel(day)}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
@@ -139,7 +162,7 @@ export default function RepeatEvent({
|
||||
{/* End options */}
|
||||
<Box>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
End:
|
||||
{t("event.repeat.end.label")}
|
||||
</Typography>
|
||||
<RadioGroup
|
||||
value={endOption}
|
||||
@@ -170,7 +193,7 @@ export default function RepeatEvent({
|
||||
disabled={!isOwn}
|
||||
value="never"
|
||||
control={<Radio />}
|
||||
label="Never"
|
||||
label={t("event.repeat.end.never")}
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
@@ -179,7 +202,7 @@ export default function RepeatEvent({
|
||||
control={<Radio />}
|
||||
label={
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
After
|
||||
{t("event.repeat.end.after")}
|
||||
<TextField
|
||||
type="number"
|
||||
size="small"
|
||||
@@ -195,7 +218,7 @@ export default function RepeatEvent({
|
||||
inputProps={{ min: 1, "data-testid": "occurrences-input" }}
|
||||
disabled={!isOwn || endOption !== "after"}
|
||||
/>
|
||||
occurrences
|
||||
{t("event.repeat.end.occurrences")}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
@@ -206,7 +229,7 @@ export default function RepeatEvent({
|
||||
control={<Radio />}
|
||||
label={
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
On
|
||||
{t("event.repeat.end.on")}
|
||||
<TextField
|
||||
type="date"
|
||||
inputProps={{ "data-testid": "end-date" }}
|
||||
|
||||
@@ -6,8 +6,10 @@ import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
import { LONG_DATE_FORMAT } from "../utils/dateTimeFormatters";
|
||||
|
||||
import "dayjs/locale/fr";
|
||||
import "dayjs/locale/en";
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
/**
|
||||
@@ -54,15 +56,21 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
onEndDateChange,
|
||||
onEndTimeChange,
|
||||
}) => {
|
||||
const { t, lang } = useI18n();
|
||||
const isExpanded = showMore;
|
||||
const shouldShowEndDateNormal = allday || !!showEndDate;
|
||||
const shouldShowFullFieldsInNormal = !allday && hasEndDateChanged;
|
||||
const showSingleDateField =
|
||||
!isExpanded && !shouldShowEndDateNormal && !shouldShowFullFieldsInNormal;
|
||||
const startDateLabel = showSingleDateField ? "Date" : "Start Date";
|
||||
const startDateLabel = showSingleDateField
|
||||
? t("dateTimeFields.date")
|
||||
: t("dateTimeFields.startDate");
|
||||
|
||||
return (
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="en">
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDayjs}
|
||||
adapterLocale={lang ?? "en"}
|
||||
>
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
@@ -73,7 +81,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
<Box display="flex" gap={1} flexDirection="row" alignItems="center">
|
||||
<Box sx={{ maxWidth: "300px", width: "48%" }}>
|
||||
<DatePicker
|
||||
label="Start Date"
|
||||
label={t("dateTimeFields.startDate")}
|
||||
format={LONG_DATE_FORMAT}
|
||||
value={startDate ? dayjs(startDate) : null}
|
||||
onChange={(newValue) => {
|
||||
@@ -99,7 +107,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
{!allday && (
|
||||
<Box sx={{ width: "110px" }}>
|
||||
<TimePicker
|
||||
label="Start Time"
|
||||
label={t("dateTimeFields.startTime")}
|
||||
ampm={false}
|
||||
value={startTime ? dayjs(startTime, "HH:mm") : null}
|
||||
onChange={(newValue) => {
|
||||
@@ -127,7 +135,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
<Box display="flex" gap={1} flexDirection="row" alignItems="center">
|
||||
<Box sx={{ maxWidth: "300px", width: "48%" }}>
|
||||
<DatePicker
|
||||
label="End Date"
|
||||
label={t("dateTimeFields.endDate")}
|
||||
format={LONG_DATE_FORMAT}
|
||||
value={endDate ? dayjs(endDate) : null}
|
||||
onChange={(newValue) => {
|
||||
@@ -154,7 +162,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
{!allday && (
|
||||
<Box sx={{ width: "110px" }}>
|
||||
<TimePicker
|
||||
label="End Time"
|
||||
label={t("dateTimeFields.endTime")}
|
||||
ampm={false}
|
||||
value={endTime ? dayjs(endTime, "HH:mm") : null}
|
||||
onChange={(newValue) => {
|
||||
@@ -185,7 +193,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
<Box display="flex" gap={1} flexDirection="row" alignItems="center">
|
||||
<Box sx={{ maxWidth: "300px", width: "48%" }}>
|
||||
<DatePicker
|
||||
label="Start Date"
|
||||
label={t("dateTimeFields.startDate")}
|
||||
format={LONG_DATE_FORMAT}
|
||||
value={startDate ? dayjs(startDate) : null}
|
||||
onChange={(newValue) => {
|
||||
@@ -210,7 +218,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
</Box>
|
||||
<Box sx={{ maxWidth: "300px", width: "48%" }}>
|
||||
<DatePicker
|
||||
label="End Date"
|
||||
label={t("dateTimeFields.endDate")}
|
||||
format={LONG_DATE_FORMAT}
|
||||
value={endDate ? dayjs(endDate) : null}
|
||||
onChange={(newValue) => {
|
||||
@@ -264,7 +272,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
</Box>
|
||||
<Box sx={{ maxWidth: "110px" }}>
|
||||
<TimePicker
|
||||
label="Start Time"
|
||||
label={t("dateTimeFields.startTime")}
|
||||
ampm={false}
|
||||
value={startTime ? dayjs(startTime, "HH:mm") : null}
|
||||
onChange={(newValue) => {
|
||||
@@ -290,7 +298,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
</Box>
|
||||
<Box sx={{ maxWidth: "110px" }}>
|
||||
<TimePicker
|
||||
label="End Time"
|
||||
label={t("dateTimeFields.endTime")}
|
||||
ampm={false}
|
||||
value={endTime ? dayjs(endTime, "HH:mm") : null}
|
||||
onChange={(newValue) => {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { formatDateToYYYYMMDDTHHMMSS } from "../../../utils/dateUtils";
|
||||
export function renderAttendeeBadge(
|
||||
a: userAttendee,
|
||||
key: string,
|
||||
t: Function,
|
||||
isFull?: boolean,
|
||||
isOrganizer?: boolean
|
||||
) {
|
||||
@@ -28,7 +29,7 @@ export function renderAttendeeBadge(
|
||||
) : null;
|
||||
|
||||
if (!isFull) {
|
||||
return <Avatar {...stringAvatar(a.cn || a.cal_address)} />;
|
||||
return <Avatar key={key} {...stringAvatar(a.cn || a.cal_address)} />;
|
||||
} else {
|
||||
return (
|
||||
<Box
|
||||
@@ -77,7 +78,7 @@ export function renderAttendeeBadge(
|
||||
</Typography>
|
||||
{isOrganizer && (
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Organizer
|
||||
{t("event.organizer")}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -20,6 +20,8 @@ import {
|
||||
} from "@mui/material";
|
||||
import { push } from "redux-first-history";
|
||||
import { CalendarApi } from "@fullcalendar/core";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
import { setLanguage } from "../../features/Settings/SettingsSlice";
|
||||
|
||||
export type AppIconProps = {
|
||||
name: string;
|
||||
@@ -44,9 +46,11 @@ export function Menubar({
|
||||
currentView,
|
||||
onViewChange,
|
||||
}: MenubarProps) {
|
||||
const { t, f, lang } = useI18n();
|
||||
const user = useAppSelector((state) => state.user.userData);
|
||||
const applist: AppIconProps[] = (window as any).appList ?? [];
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [langAnchorEl, setLangAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
if (!user) {
|
||||
@@ -99,6 +103,18 @@ export function Menubar({
|
||||
};
|
||||
|
||||
const open = Boolean(anchorEl);
|
||||
const langOpen = Boolean(langAnchorEl);
|
||||
|
||||
const handleLangClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setLangAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleLangClose = () => setLangAnchorEl(null);
|
||||
|
||||
const availableLangs = [
|
||||
{ code: "en", label: "English" },
|
||||
{ code: "fr", label: "Français" },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="menubar">
|
||||
@@ -112,7 +128,9 @@ export function Menubar({
|
||||
<Button onClick={() => handleNavigation("prev")}>
|
||||
<ChevronLeftIcon />
|
||||
</Button>
|
||||
<Button onClick={() => handleNavigation("today")}>Today</Button>
|
||||
<Button onClick={() => handleNavigation("today")}>
|
||||
{t("menubar.today")}
|
||||
</Button>
|
||||
<Button onClick={() => handleNavigation("next")}>
|
||||
<ChevronRightIcon />
|
||||
</Button>
|
||||
@@ -122,17 +140,18 @@ export function Menubar({
|
||||
<div className="menu-items">
|
||||
<div className="current-date-time">
|
||||
<Typography variant="h6" component="div">
|
||||
{currentDate.toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
})}
|
||||
{f(currentDate, "MMMM yyyy")}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="right-menu">
|
||||
<div className="menu-items">
|
||||
<IconButton onClick={onRefresh}>
|
||||
<IconButton
|
||||
onClick={onRefresh}
|
||||
aria-label={t("menubar.refresh")}
|
||||
title={t("menubar.refresh")}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
@@ -145,37 +164,53 @@ export function Menubar({
|
||||
value={currentView}
|
||||
onChange={(e) => handleViewChange(e.target.value)}
|
||||
variant="outlined"
|
||||
aria-label={t("menubar.viewSelector")}
|
||||
>
|
||||
<MenuItem value="dayGridMonth">Month</MenuItem>
|
||||
<MenuItem value="timeGridWeek">Week</MenuItem>
|
||||
<MenuItem value="timeGridDay">Day</MenuItem>
|
||||
<MenuItem value="dayGridMonth">
|
||||
{t("menubar.views.month")}
|
||||
</MenuItem>
|
||||
<MenuItem value="timeGridWeek">
|
||||
{t("menubar.views.week")}
|
||||
</MenuItem>
|
||||
<MenuItem value="timeGridDay">
|
||||
{t("menubar.views.day")}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className="menu-items">
|
||||
{applist.length > 0 && (
|
||||
<IconButton onClick={handleOpen} style={{ marginRight: 8 }}>
|
||||
<IconButton
|
||||
onClick={handleOpen}
|
||||
style={{ marginRight: 8 }}
|
||||
aria-label={t("menubar.apps")}
|
||||
title={t("menubar.apps")}
|
||||
>
|
||||
<AppsIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="menu-items">
|
||||
<Avatar
|
||||
style={{
|
||||
backgroundColor: stringToColor(
|
||||
user && user.family_name
|
||||
? user.family_name
|
||||
: user && user.email
|
||||
? user.email
|
||||
: ""
|
||||
),
|
||||
}}
|
||||
sizes="large"
|
||||
>
|
||||
{user?.name && user?.family_name
|
||||
? `${user.name[0]}${user.family_name[0]}`
|
||||
: (user?.email?.[0] ?? "")}
|
||||
</Avatar>
|
||||
<IconButton onClick={handleLangClick}>
|
||||
<Avatar
|
||||
style={{
|
||||
backgroundColor: stringToColor(
|
||||
user && user.family_name
|
||||
? user.family_name
|
||||
: user && user.email
|
||||
? user.email
|
||||
: ""
|
||||
),
|
||||
}}
|
||||
sizes="large"
|
||||
aria-label={t("menubar.userProfile")}
|
||||
>
|
||||
{user?.name && user?.family_name
|
||||
? `${user.name[0]}${user.family_name[0]}`
|
||||
: (user?.email?.[0] ?? "")}
|
||||
</Avatar>
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -198,13 +233,46 @@ export function Menubar({
|
||||
))}
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
<Popover
|
||||
open={langOpen}
|
||||
anchorEl={langAnchorEl}
|
||||
onClose={handleLangClose}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "right",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
>
|
||||
<FormControl size="small" style={{ minWidth: 100, marginRight: 8 }}>
|
||||
<Select
|
||||
value={lang}
|
||||
onChange={(e) => {
|
||||
dispatch(setLanguage(e.target.value));
|
||||
}}
|
||||
variant="outlined"
|
||||
aria-label={t("menubar.languageSelector")}
|
||||
>
|
||||
{availableLangs.map(({ code, label }) => (
|
||||
<MenuItem key={code} value={code}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function MainTitle() {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<div className="menubar-item tc-home">
|
||||
<img className="logo" src={logo} alt="Calendar" />
|
||||
<img className="logo" src={logo} alt={t("menubar.logoAlt")} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user