diff --git a/docs/user_guide/assets/licenses/frontend_licenses.txt b/docs/user_guide/assets/licenses/frontend_licenses.txt index 88683fd764..6a4858e759 100644 --- a/docs/user_guide/assets/licenses/frontend_licenses.txt +++ b/docs/user_guide/assets/licenses/frontend_licenses.txt @@ -1388,6 +1388,31 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +@mui/x-date-pickers 6.18.7 +MIT +MIT License + +Copyright (c) 2020 Material-UI SAS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + @popperjs/core 2.11.8 MIT The MIT License (MIT) diff --git a/package-lock.json b/package-lock.json index e27cd0c091..98fa6e5948 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@microsoft/signalr": "^8.0.0", "@mui/icons-material": "^5.14.19", "@mui/material": "^5.14.16", + "@mui/x-date-pickers": "^6.18.7", "@redux-devtools/extension": "^3.2.5", "@reduxjs/toolkit": "^1.9.5", "@segment/analytics-next": "^1.55.0", @@ -4301,6 +4302,72 @@ "react-dom": ">=18.0.0" } }, + "node_modules/@material-table/core/node_modules/@mui/x-date-pickers": { + "version": "5.0.20", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.20.tgz", + "integrity": "sha512-ERukSeHIoNLbI1C2XRhF9wRhqfsr+Q4B1SAw2ZlU7CWgcG8UBOxgqRKDEOVAIoSWL+DWT6GRuQjOKvj6UXZceA==", + "dependencies": { + "@babel/runtime": "^7.18.9", + "@date-io/core": "^2.15.0", + "@date-io/date-fns": "^2.15.0", + "@date-io/dayjs": "^2.15.0", + "@date-io/luxon": "^2.15.0", + "@date-io/moment": "^2.15.0", + "@mui/utils": "^5.10.3", + "@types/react-transition-group": "^4.4.5", + "clsx": "^1.2.1", + "prop-types": "^15.7.2", + "react-transition-group": "^4.4.5", + "rifm": "^0.12.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.4.1", + "@mui/system": "^5.4.1", + "date-fns": "^2.25.0", + "dayjs": "^1.10.7", + "luxon": "^1.28.0 || ^2.0.0 || ^3.0.0", + "moment": "^2.29.1", + "react": "^17.0.2 || ^18.0.0", + "react-dom": "^17.0.2 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/@material-table/core/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/@matt-block/react-recaptcha-v2": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@matt-block/react-recaptcha-v2/-/react-recaptcha-v2-2.0.1.tgz", @@ -4656,25 +4723,20 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/@mui/x-date-pickers": { - "version": "5.0.20", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.20.tgz", - "integrity": "sha512-ERukSeHIoNLbI1C2XRhF9wRhqfsr+Q4B1SAw2ZlU7CWgcG8UBOxgqRKDEOVAIoSWL+DWT6GRuQjOKvj6UXZceA==", + "version": "6.18.7", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.18.7.tgz", + "integrity": "sha512-4NoapaCT3jvEk2cuAUjG0ReZvTEk1i4dGDz94Gt1Oc08GuC1AuzYRwCR1/1tdmbDynwkR8ilkKL6AyS3NL1H4A==", "dependencies": { - "@babel/runtime": "^7.18.9", - "@date-io/core": "^2.15.0", - "@date-io/date-fns": "^2.15.0", - "@date-io/dayjs": "^2.15.0", - "@date-io/luxon": "^2.15.0", - "@date-io/moment": "^2.15.0", - "@mui/utils": "^5.10.3", - "@types/react-transition-group": "^4.4.5", - "clsx": "^1.2.1", - "prop-types": "^15.7.2", - "react-transition-group": "^4.4.5", - "rifm": "^0.12.1" + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.22", + "@mui/utils": "^5.14.16", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", @@ -4683,14 +4745,17 @@ "peerDependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", - "@mui/material": "^5.4.1", - "@mui/system": "^5.4.1", + "@mui/material": "^5.8.6", + "@mui/system": "^5.8.0", "date-fns": "^2.25.0", + "date-fns-jalali": "^2.13.0-0", "dayjs": "^1.10.7", - "luxon": "^1.28.0 || ^2.0.0 || ^3.0.0", - "moment": "^2.29.1", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0" + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -4702,6 +4767,9 @@ "date-fns": { "optional": true }, + "date-fns-jalali": { + "optional": true + }, "dayjs": { "optional": true }, @@ -4710,17 +4778,15 @@ }, "moment": { "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true } } }, - "node_modules/@mui/x-date-pickers/node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "engines": { - "node": ">=6" - } - }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", diff --git a/package.json b/package.json index 91ec72b69f..97af8c6156 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@microsoft/signalr": "^8.0.0", "@mui/icons-material": "^5.14.19", "@mui/material": "^5.14.16", + "@mui/x-date-pickers": "^6.18.7", "@redux-devtools/extension": "^3.2.5", "@reduxjs/toolkit": "^1.9.5", "@segment/analytics-next": "^1.55.0", diff --git a/src/components/App/AppLoggedIn.tsx b/src/components/App/AppLoggedIn.tsx index 713866723c..3090bb416f 100644 --- a/src/components/App/AppLoggedIn.tsx +++ b/src/components/App/AppLoggedIn.tsx @@ -4,6 +4,7 @@ import { Theme, ThemeProvider, createTheme } from "@mui/material/styles"; import { ReactElement, useEffect, useMemo, useState } from "react"; import { Route, Routes } from "react-router-dom"; +import DatePickersLocalizationProvider from "components/App/DatePickersLocalizationProvider"; import SignalRHub from "components/App/SignalRHub"; import AppBar from "components/AppBar/AppBarComponent"; import PageNotFound from "components/PageNotFound/component"; @@ -72,7 +73,7 @@ export default function AppWithBar(): ReactElement { : theme; return ( - <> + @@ -113,6 +114,6 @@ export default function AppWithBar(): ReactElement { - + ); } diff --git a/src/components/App/DatePickersLocalizationProvider.tsx b/src/components/App/DatePickersLocalizationProvider.tsx new file mode 100644 index 0000000000..582ca64c31 --- /dev/null +++ b/src/components/App/DatePickersLocalizationProvider.tsx @@ -0,0 +1,26 @@ +import { LocalizationProvider } from "@mui/x-date-pickers"; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +// Import for each non-en uiWritingSystem lang in src/types/writingSystem.ts: +import "dayjs/locale/ar"; +import "dayjs/locale/es"; +import "dayjs/locale/fr"; +import "dayjs/locale/pt"; +import "dayjs/locale/zh"; +import { ReactElement, ReactNode } from "react"; + +import i18n from "i18n"; + +export default function DatePickersLocalizationProvider(props: { + children: ReactNode; +}): ReactElement { + return ( + + {props.children} + + ); +} diff --git a/src/components/ProjectSettings/ProjectSchedule/CalendarView.tsx b/src/components/ProjectSettings/ProjectSchedule/CalendarView.tsx index 67ac753ace..ed4aa71930 100644 --- a/src/components/ProjectSettings/ProjectSchedule/CalendarView.tsx +++ b/src/components/ProjectSettings/ProjectSchedule/CalendarView.tsx @@ -1,55 +1,35 @@ import { Icon } from "@mui/material"; -import { - CalendarPicker, - PickersDay, - PickersDayProps, -} from "@mui/x-date-pickers"; -import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; -import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; +import { DateCalendar } from "@mui/x-date-pickers"; import dayjs, { Dayjs } from "dayjs"; import { ReactElement } from "react"; +import ProjectPickersDay from "components/ProjectSettings/ProjectSchedule/ProjectPickersDay"; + interface CalendarViewProps { projectSchedule: Date[]; } export default function CalendarView(props: CalendarViewProps): ReactElement { - // Custom renderer for CalendarPicker - function customDayRenderer( - day: Dayjs, - _selectedDays: Array, - pickersDayProps: PickersDayProps - ): ReactElement { - const date = day.toDate(); - const selected = - props.projectSchedule && - props.projectSchedule.findIndex( - (d) => - d.getDate() === date.getDate() && - d.getMonth() === date.getMonth() && - d.getFullYear() === date.getFullYear() - ) >= 0; - return ; - } - function handleCalendarView(monthToRender?: Dayjs[]): ReactElement[] { if (!monthToRender) { return []; } return monthToRender.map((tempDayjs) => ( - {}} - date={null} disableHighlightToday - renderDay={customDayRenderer} + slots={{ + day: ProjectPickersDay, + leftArrowIcon: Icon, + rightArrowIcon: Icon, + }} + slotProps={{ day: { days: props.projectSchedule } as any }} /> )); } @@ -60,9 +40,5 @@ export default function CalendarView(props: CalendarViewProps): ReactElement { return Array.from(new Set(months)).sort().map(dayjs); } - return ( - - {handleCalendarView(getScheduledMonths(props.projectSchedule))} - - ); + return <>{handleCalendarView(getScheduledMonths(props.projectSchedule))}; } diff --git a/src/components/ProjectSettings/ProjectSchedule/DateScheduleEdit.tsx b/src/components/ProjectSettings/ProjectSchedule/DateScheduleEdit.tsx index a4fb5b510f..5c4a39c394 100644 --- a/src/components/ProjectSettings/ProjectSchedule/DateScheduleEdit.tsx +++ b/src/components/ProjectSettings/ProjectSchedule/DateScheduleEdit.tsx @@ -1,17 +1,12 @@ import { Button, Grid } from "@mui/material"; -import { - CalendarPicker, - PickersDay, - PickersDayProps, -} from "@mui/x-date-pickers"; -import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; -import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; -import { Dayjs } from "dayjs"; +import { DateCalendar } from "@mui/x-date-pickers"; +import dayjs, { Dayjs } from "dayjs"; import { ReactElement, useState } from "react"; import { useTranslation } from "react-i18next"; import { Project } from "api/models"; import { LoadingButton } from "components/Buttons"; +import ProjectPickersDay from "components/ProjectSettings/ProjectSchedule/ProjectPickersDay"; interface DateScheduleEditProps { close: () => void; @@ -28,23 +23,6 @@ export default function DateScheduleEdit( ); const { t } = useTranslation(); - // Custom renderer for CalendarPicker - function customDayRenderer( - day: Dayjs, - _selectedDays: Array, - pickersDayProps: PickersDayProps - ): ReactElement { - const date = day.toDate(); - const selected = - projectSchedule.findIndex( - (d) => - d.getDate() === date.getDate() && - d.getMonth() === date.getMonth() && - d.getFullYear() === date.getFullYear() - ) >= 0; - return ; - } - async function handleSubmit(): Promise { // update the schedule to the project setting const updatedSchedule = projectSchedule.map((date) => date.toISOString()); @@ -79,15 +57,20 @@ export default function DateScheduleEdit( } return ( - - + - + - + - + ); } diff --git a/src/components/ProjectSettings/ProjectSchedule/DateSelector.tsx b/src/components/ProjectSettings/ProjectSchedule/DateSelector.tsx index 18df63e7e7..7bcd1ec0e6 100644 --- a/src/components/ProjectSettings/ProjectSchedule/DateSelector.tsx +++ b/src/components/ProjectSettings/ProjectSchedule/DateSelector.tsx @@ -1,8 +1,5 @@ import { Button, Grid } from "@mui/material"; -import TextField from "@mui/material/TextField"; -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 { enqueueSnackbar } from "notistack"; import { ReactElement, useState } from "react"; @@ -63,22 +60,20 @@ export default function DateSelector(props: DateSelectorProps): ReactElement { } return ( - + <> setStartDate(newValue)} - renderInput={(params) => } />    setEndDate(newValue)} - renderInput={(params) => } /> - + - + - + ); } diff --git a/src/components/ProjectSettings/ProjectSchedule/ProjectPickersDay.tsx b/src/components/ProjectSettings/ProjectSchedule/ProjectPickersDay.tsx new file mode 100644 index 0000000000..8681b71967 --- /dev/null +++ b/src/components/ProjectSettings/ProjectSchedule/ProjectPickersDay.tsx @@ -0,0 +1,29 @@ +import { PickersDay, PickersDayProps } from "@mui/x-date-pickers"; +import { Dayjs } from "dayjs"; +import { ReactElement } from "react"; + +interface ProjectPickersDayProps extends PickersDayProps { + days?: Date[]; +} + +/** A customized (`@mui/x-date-pickers`) `PickersDay` component. + * To select multiple dates in the `DateCalendar` component, + * add `ProjectPickersDay` to the `DateCalendar` props as follows: + * `slots={{ day: ProjectPickersDay }}` + * `slotProps={{ day: { days: } as any }}` */ +export default function ProjectPickersDay( + props: ProjectPickersDayProps +): ReactElement { + const { days, ...pickersDayProps } = props; + const date = pickersDayProps.day.toDate(); + const selected = days + ? days.findIndex( + (d) => + d.getDate() === date.getDate() && + d.getMonth() === date.getMonth() && + d.getFullYear() === date.getFullYear() + ) > -1 + : false; + + return ; +} diff --git a/src/components/ProjectSettings/ProjectSchedule/index.tsx b/src/components/ProjectSettings/ProjectSchedule/index.tsx index 9db44c07c8..8b63e00511 100644 --- a/src/components/ProjectSettings/ProjectSchedule/index.tsx +++ b/src/components/ProjectSettings/ProjectSchedule/index.tsx @@ -31,6 +31,10 @@ export default function ProjectSchedule( const { t } = useTranslation(); + useEffect(() => { + Modal.setAppElement("body"); + }, []); + /** Remove all elements from workshopSchedule in project settings */ async function handleRemoveAll(): Promise { await props.updateProject({ ...props.project, workshopSchedule: [] }); @@ -135,7 +139,7 @@ export default function ProjectSchedule( {t("projectSettings.schedule.removeAll")} - + - +