UI/update recurring event section (#544)
* Update global recurring event style * #538 update date selector consistance * update test cases
This commit is contained in:
@@ -539,12 +539,16 @@ export default function EventFormFields({
|
||||
const newShowRepeat = !showRepeat;
|
||||
setShowRepeat(newShowRepeat);
|
||||
if (newShowRepeat) {
|
||||
const days = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"];
|
||||
const eventStartDate = new Date(start);
|
||||
const jsDay = eventStartDate.getDay();
|
||||
const icsDay = days[(jsDay + 6) % 7];
|
||||
setRepetition({
|
||||
freq: "daily",
|
||||
freq: "weekly",
|
||||
interval: 1,
|
||||
occurrences: 0,
|
||||
endDate: "",
|
||||
byday: null,
|
||||
byday: [icsDay],
|
||||
} as RepetitionObject);
|
||||
} else {
|
||||
setRepetition({
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { RepetitionObject } from "@/features/Events/EventsTypes";
|
||||
import {
|
||||
Box,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormGroup,
|
||||
MenuItem,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
@@ -14,8 +12,18 @@ import {
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@linagora/twake-mui";
|
||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import dayjs from "dayjs";
|
||||
import "dayjs/locale/en";
|
||||
import "dayjs/locale/fr";
|
||||
import "dayjs/locale/ru";
|
||||
import "dayjs/locale/vi";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useI18n } from "twake-i18n";
|
||||
import { ReadOnlyDateField } from "./components/ReadOnlyPickerField";
|
||||
import { LONG_DATE_FORMAT } from "./utils/dateTimeFormatters";
|
||||
|
||||
export default function RepeatEvent({
|
||||
repetition,
|
||||
@@ -31,6 +39,13 @@ export default function RepeatEvent({
|
||||
const { t } = useI18n();
|
||||
const days = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"];
|
||||
const day = new Date(eventStart);
|
||||
const dateCalendarLayoutSx = {
|
||||
"& .MuiDateCalendar-root.MuiDateCalendar-root": {
|
||||
width: "260px",
|
||||
maxWidth: "260px",
|
||||
padding: "0 15px",
|
||||
},
|
||||
};
|
||||
// derive endOption based on repetition
|
||||
const getEndOption = () => {
|
||||
if (repetition.occurrences && repetition.occurrences > 0) return "after";
|
||||
@@ -79,8 +94,8 @@ export default function RepeatEvent({
|
||||
<Box>
|
||||
<Stack>
|
||||
{/* Interval */}
|
||||
<Box display="flex" alignItems="center" gap={2} mb={2}>
|
||||
<Typography variant="h6">{t("event.repeat.repeatEvery")}</Typography>
|
||||
<Box display="flex" alignItems="center" gap={2} mb={1}>
|
||||
<Typography variant="h6">{t("event.repeat.every")}</Typography>
|
||||
<TextField
|
||||
type="number"
|
||||
value={repetition.interval ?? 1}
|
||||
@@ -93,7 +108,13 @@ export default function RepeatEvent({
|
||||
}
|
||||
size="small"
|
||||
style={{ width: 80 }}
|
||||
inputProps={{ min: 1 }}
|
||||
inputProps={{
|
||||
min: 1,
|
||||
style: {
|
||||
textAlign: "center",
|
||||
paddingRight: 5,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FormControl size="small" style={{ minWidth: 120 }}>
|
||||
<Select
|
||||
@@ -137,25 +158,52 @@ export default function RepeatEvent({
|
||||
|
||||
{/* Weekly selection */}
|
||||
{repetition.freq === "weekly" && (
|
||||
<Box>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
{t("event.repeat.repeatOn")}
|
||||
</Typography>
|
||||
<FormGroup row>
|
||||
{days.map((day) => (
|
||||
<FormControlLabel
|
||||
key={day}
|
||||
disabled={!isOwn}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={repetition.byday?.includes(day) ?? false}
|
||||
onChange={() => handleDayChange(day)}
|
||||
/>
|
||||
}
|
||||
label={getDayLabel(day)}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
<Box mb={2}>
|
||||
<Box display="flex" gap={1}>
|
||||
{days.map((dayCode) => {
|
||||
const isSelected = repetition.byday?.includes(dayCode) ?? false;
|
||||
const fullLabel = getDayLabel(dayCode);
|
||||
const label = fullLabel.charAt(0);
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={dayCode}
|
||||
role="button"
|
||||
aria-label={fullLabel}
|
||||
onClick={() => {
|
||||
if (!isOwn) return;
|
||||
handleDayChange(dayCode);
|
||||
}}
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: "4px",
|
||||
border: "1px solid",
|
||||
borderColor: isSelected ? "primary.main" : "#AEAEC0",
|
||||
color: isSelected ? "#fff" : "#8C9CAF",
|
||||
fontSize: 16,
|
||||
fontWeight: 400,
|
||||
lineHeight: "24px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
textAlign: "center",
|
||||
bgcolor: isSelected ? "primary.main" : "transparent",
|
||||
cursor: isOwn ? "pointer" : "default",
|
||||
"&:hover": isOwn
|
||||
? {
|
||||
borderColor: "primary.main",
|
||||
bgcolor: "primary.main",
|
||||
color: "#fff",
|
||||
}
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -168,6 +216,8 @@ export default function RepeatEvent({
|
||||
value={endOption}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (value === endOption) return;
|
||||
|
||||
setEndOption(value);
|
||||
|
||||
if (value === "never") {
|
||||
@@ -191,7 +241,8 @@ export default function RepeatEvent({
|
||||
setRepetition({
|
||||
...repetition,
|
||||
occurrences: null,
|
||||
endDate: new Date().toISOString().slice(0, 16),
|
||||
endDate:
|
||||
repetition.endDate ?? new Date().toISOString().slice(0, 10),
|
||||
});
|
||||
}
|
||||
}}
|
||||
@@ -209,15 +260,102 @@ export default function RepeatEvent({
|
||||
|
||||
<FormControlLabel
|
||||
disabled={!isOwn}
|
||||
value="after"
|
||||
value="on"
|
||||
sx={{ mt: 1 }}
|
||||
control={<Radio />}
|
||||
label={
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Typography variant="h6">
|
||||
{t("event.repeat.end.on")}
|
||||
</Typography>
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDayjs}
|
||||
adapterLocale={t("locale") ?? "en"}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 220,
|
||||
"& .MuiInputBase-root": { fontSize: 14 },
|
||||
"& .MuiInputBase-input": { fontSize: 14 },
|
||||
}}
|
||||
>
|
||||
<DatePicker
|
||||
sx={{ width: "100%" }}
|
||||
format={LONG_DATE_FORMAT}
|
||||
value={
|
||||
repetition.endDate ? dayjs(repetition.endDate) : null
|
||||
}
|
||||
onChange={(value) => {
|
||||
if (!value || !value.isValid()) return;
|
||||
const newDateStr = value.format("YYYY-MM-DD");
|
||||
setRepetition({
|
||||
...repetition,
|
||||
occurrences: null,
|
||||
endDate: newDateStr,
|
||||
});
|
||||
if (endOption !== "on") {
|
||||
setEndOption("on");
|
||||
}
|
||||
}}
|
||||
onOpen={() => {
|
||||
if (!isOwn || endOption === "on") return;
|
||||
setEndOption("on");
|
||||
if (!repetition.endDate) {
|
||||
setRepetition({
|
||||
...repetition,
|
||||
occurrences: null,
|
||||
endDate: new Date().toISOString().slice(0, 10),
|
||||
});
|
||||
} else {
|
||||
setRepetition({
|
||||
...repetition,
|
||||
occurrences: null,
|
||||
endDate: repetition.endDate,
|
||||
});
|
||||
}
|
||||
}}
|
||||
slots={{ field: ReadOnlyDateField }}
|
||||
slotProps={{
|
||||
field: {},
|
||||
layout: { sx: dateCalendarLayoutSx },
|
||||
}}
|
||||
disabled={!isOwn}
|
||||
/>
|
||||
</Box>
|
||||
</LocalizationProvider>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
disabled={!isOwn}
|
||||
value="after"
|
||||
control={<Radio />}
|
||||
sx={{ mt: 1 }}
|
||||
label={
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={1}
|
||||
onClick={() => {
|
||||
if (!isOwn || endOption === "after") return;
|
||||
setEndOption("after");
|
||||
setRepetition({
|
||||
...repetition,
|
||||
endDate: null,
|
||||
occurrences:
|
||||
repetition.occurrences && repetition.occurrences > 0
|
||||
? repetition.occurrences
|
||||
: 1,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6">
|
||||
{t("event.repeat.end.after")}
|
||||
</Typography>
|
||||
<TextField
|
||||
type="number"
|
||||
inputProps={{ min: 1, "data-testid": "occurrences-input" }}
|
||||
size="small"
|
||||
value={repetition.occurrences ?? 1}
|
||||
onChange={(e) => {
|
||||
@@ -227,10 +365,12 @@ export default function RepeatEvent({
|
||||
endDate: null,
|
||||
occurrences: value > 0 ? value : 1,
|
||||
});
|
||||
if (endOption !== "after") {
|
||||
setEndOption("after");
|
||||
}
|
||||
}}
|
||||
style={{ width: 100 }}
|
||||
inputProps={{ min: 1, "data-testid": "occurrences-input" }}
|
||||
disabled={!isOwn || endOption !== "after"}
|
||||
disabled={!isOwn}
|
||||
/>
|
||||
<Typography variant="h6">
|
||||
{t("event.repeat.end.occurrences")}
|
||||
@@ -238,33 +378,6 @@ export default function RepeatEvent({
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
disabled={!isOwn}
|
||||
value="on"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Typography variant="h6">
|
||||
{t("event.repeat.end.on")}
|
||||
</Typography>
|
||||
<TextField
|
||||
type="date"
|
||||
inputProps={{ "data-testid": "end-date" }}
|
||||
size="small"
|
||||
value={repetition.endDate ?? ""}
|
||||
onChange={(e) =>
|
||||
setRepetition({
|
||||
...repetition,
|
||||
occurrences: null,
|
||||
endDate: e.target.value,
|
||||
})
|
||||
}
|
||||
disabled={!isOwn || endOption !== "on"}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user