diff --git a/src/components/events/partials/ModalTabsAndPages/EventDetailsSchedulingTab.tsx b/src/components/events/partials/ModalTabsAndPages/EventDetailsSchedulingTab.tsx index c992d69bdb..bbec1c0c49 100644 --- a/src/components/events/partials/ModalTabsAndPages/EventDetailsSchedulingTab.tsx +++ b/src/components/events/partials/ModalTabsAndPages/EventDetailsSchedulingTab.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from "react"; import cn from "classnames"; import _ from "lodash"; -import { DatePicker } from "@mui/x-date-pickers/DatePicker"; +import DatePicker from "react-datepicker"; import { Formik, FormikErrors, FormikProps } from "formik"; import { Field } from "../../../shared/Field"; import Notifications from "../../../shared/Notifications"; @@ -195,12 +195,12 @@ const EventDetailsSchedulingTab = ({ : []; return { - scheduleStartDate: startDate.setHours(0, 0, 0).toString(), + scheduleStartDate: startDate.toString(), scheduleStartHour: source.start.hour != null ? makeTwoDigits(source.start.hour) : "", scheduleStartMinute: source.start.minute != null ? makeTwoDigits(source.start.minute) : "", scheduleDurationHours: source.duration.hour != null ? makeTwoDigits(source.duration.hour) : "", scheduleDurationMinutes: source.duration.minute != null ? makeTwoDigits(source.duration.minute): "", - scheduleEndDate: endDate.setHours(0, 0, 0).toString(), + scheduleEndDate: endDate.toString(), scheduleEndHour: source.end.hour != null ? makeTwoDigits(source.end.hour): "", scheduleEndMinute: source.end.minute != null ? makeTwoDigits(source.end.minute): "", captureAgent: source.device.name, @@ -286,8 +286,7 @@ const EventDetailsSchedulingTab = ({ /* date picker for start date */ value && changeStartDate( value, @@ -297,6 +296,14 @@ const EventDetailsSchedulingTab = ({ checkConflictsWrapper ) } + showYearDropdown + showMonthDropdown + yearDropdownItemNumber={2} + dateFormat="P" + popperClassName="datepicker-custom" + className="datepicker-custom-input" + portalId="root" + locale={currentLanguage?.dateLocale} /> ) : ( <> diff --git a/src/components/events/partials/ModalTabsAndPages/NewSourcePage.tsx b/src/components/events/partials/ModalTabsAndPages/NewSourcePage.tsx index 6a1e3aa5c3..d85537eec2 100644 --- a/src/components/events/partials/ModalTabsAndPages/NewSourcePage.tsx +++ b/src/components/events/partials/ModalTabsAndPages/NewSourcePage.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import cn from "classnames"; import Notifications from "../../../shared/Notifications"; -import { DatePicker } from "@mui/x-date-pickers/DatePicker"; +import DatePicker from "react-datepicker"; import { getCurrentLanguageInformation, getTimezoneOffset, @@ -444,7 +444,7 @@ const Schedule = { if (formik.values.sourceMode === "SCHEDULE_MULTIPLE") { value && changeStartDateMultiple( @@ -460,6 +460,14 @@ const Schedule = @@ -474,7 +482,7 @@ const Schedule = value && changeEndDateMultiple( value, @@ -482,6 +490,14 @@ const Schedule = diff --git a/src/components/shared/TableFilters.tsx b/src/components/shared/TableFilters.tsx index 130d3793ab..43dcfefef7 100644 --- a/src/components/shared/TableFilters.tsx +++ b/src/components/shared/TableFilters.tsx @@ -1,6 +1,6 @@ -import React, { useRef, useState, useEffect } from "react"; +import React, { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { DatePicker } from "@mui/x-date-pickers/DatePicker"; +import DatePicker from "react-datepicker"; import { getFilters, getSecondFilter, @@ -26,6 +26,7 @@ import { useHotkeys } from "react-hotkeys-hook"; import moment from "moment"; import { AppThunk, useAppDispatch, useAppSelector } from "../../store"; import { renderValidDate } from "../../utils/dateUtils"; +import { getCurrentLanguageInformation } from "../../utils/utils"; import { Tooltip } from "./Tooltip"; import DropDown from "./DropDown"; import { AsyncThunk } from "@reduxjs/toolkit"; @@ -146,77 +147,34 @@ const TableFilters = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [itemValue]); - // Set the sate of startDate and endDate picked with datepicker - const handleDatepickerChange = async (date: Date | null, isStart = false) => { - if (date === null) { - return; - } - - if (isStart) { - date.setHours(0); - date.setMinutes(0); - date.setSeconds(0); - setStartDate(date); - } else { - date.setHours(23); - date.setMinutes(59); - date.setSeconds(59); - setEndDate(date); - } - }; - - // If both dates are set, set the value for the startDate filter - // If the just changed, it can be passed here so we don't have wait a render - // cycle for the useState state to update - const handleDatepickerConfirm = async (date?: Date | null, isStart = false) => { - if (date === null) { - return; - } - - let myStartDate = startDate; - let myEndDate = endDate; - if (date && isStart) { - myStartDate = date; - myStartDate.setHours(0); - myStartDate.setMinutes(0); - myStartDate.setSeconds(0); - } - if (date && !isStart) { - myEndDate = date; - myEndDate.setHours(23); - myEndDate.setMinutes(59); - myEndDate.setSeconds(59); - } - - if (myStartDate && myEndDate && moment(myStartDate).isValid() && moment(myEndDate).isValid()) { - let filter = filterMap.find(({ name }) => name === selectedFilter); - if (filter) { - dispatch(editFilterValue({ - filterName: filter.name, - value: myStartDate.toISOString() + "/" + myEndDate.toISOString() - })); - setFilterSelector(false); - dispatch(removeSelectedFilter()); - // Reload of resource after going to very first page. - dispatch(goToPage(0)) - await dispatch(loadResource()); - dispatch(loadResourceIntoTable()); + const handleDatepicker = async (dates?: [Date | undefined | null, Date | undefined | null]) => { + if (dates != null) { + let [start, end] = dates; + + start?.setHours(0); + start?.setMinutes(0); + start?.setSeconds(0) + end?.setHours(23); + end?.setMinutes(59); + end?.setSeconds(59); + + if (start && end && moment(start).isValid() && moment(end).isValid()) { + let filter = filterMap.find(({ name }) => name === selectedFilter); + if (filter) { + dispatch(editFilterValue({ + filterName: filter.name, + value: start.toISOString() + "/" + end.toISOString() + })); + setFilterSelector(false); + dispatch(removeSelectedFilter()); + // Reload of resource after going to very first page. + dispatch(goToPage(0)) + await dispatch(loadResource()); + dispatch(loadResourceIntoTable()); + } } - } - - if (myStartDate && isStart && !endDate) { - let tmp = new Date(myStartDate.getTime()); - tmp.setHours(23); - tmp.setMinutes(59); - tmp.setSeconds(59); - setEndDate(tmp); - } - if (myEndDate && !isStart && !startDate) { - let tmp = new Date(myEndDate.getTime()); - tmp.setHours(0); - tmp.setMinutes(0); - tmp.setSeconds(0); - setStartDate(tmp); + if (start) setStartDate(start); + if (end) setEndDate(end); } } @@ -315,8 +273,7 @@ const TableFilters = ({ secondFilter={secondFilter} startDate={startDate} endDate={endDate} - handleDate={handleDatepickerChange} - handleDateConfirm={handleDatepickerConfirm} + handleDate={handleDatepicker} handleChange={handleChange} openSecondFilterMenu={openSecondFilterMenu} setOpenSecondFilterMenu={setOpenSecondFilterMenu} @@ -398,7 +355,6 @@ const FilterSwitch = ({ startDate, endDate, handleDate, - handleDateConfirm, secondFilter, openSecondFilterMenu, setOpenSecondFilterMenu, @@ -407,17 +363,13 @@ const FilterSwitch = ({ handleChange: (name: string, value: string) => void, startDate: Date | undefined, endDate: Date | undefined, - handleDate: (date: Date | null, isStart?: boolean) => void, - handleDateConfirm: (date: Date | undefined | null, isStart?: boolean) => void, + handleDate: (dates: [Date | undefined | null, Date | undefined | null]) => void, secondFilter: string, openSecondFilterMenu: boolean, setOpenSecondFilterMenu: (open: boolean) => void, }) => { const { t } = useTranslation(); - const startDateRef = useRef(null); - const endDateRef = useRef(null); - if (!filter) { return null; } @@ -471,51 +423,25 @@ const FilterSwitch = ({ case "period": return (
- {/* Show datepicker for start date */} - handleDate(date as Date | null, true)} - // FixMe: onAccept does not trigger if the already set value is the same as the selected value - // This prevents us from confirming from confirming our filter, if someone wants to selected the same - // day for both start and end date (since we automatically set one to the other) - onAccept={(e) => {handleDateConfirm(e as Date | null, true)}} - slotProps={{ - textField: { - onKeyDown: (event) => { - if (event.key === "Enter") { - handleDateConfirm(undefined, true) - if (endDateRef.current && startDate && moment(startDate).isValid()) { - endDateRef.current.focus(); - } - } - }, - }, - }} - /> handleDate(date as Date | null)} - // FixMe: See above - onAccept={(e) => handleDateConfirm(e as Date | null, false)} - slotProps={{ - textField: { - onKeyDown: (event) => { - if (event.key === "Enter") { - handleDateConfirm(undefined, false) - if (startDateRef.current && endDate && moment(endDate).isValid()) { - startDateRef.current.focus(); - } - } - }, - }, - }} + startOpen + autoFocus + selected={startDate} + onChange={(dates) => handleDate(dates)} + startDate={startDate} + endDate={endDate} + selectsRange + showYearDropdown + showMonthDropdown + yearDropdownItemNumber={2} + swapRange + allowSameDay + dateFormat="P" + popperPlacement="bottom" + popperClassName="datepicker-custom" + className="datepicker-custom-input" + locale={getCurrentLanguageInformation()?.dateLocale} + />
); diff --git a/src/components/shared/wizard/RenderField.tsx b/src/components/shared/wizard/RenderField.tsx index 1a72718c97..2479be37f3 100644 --- a/src/components/shared/wizard/RenderField.tsx +++ b/src/components/shared/wizard/RenderField.tsx @@ -1,9 +1,10 @@ import React from "react"; import { useTranslation } from "react-i18next"; -import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker"; +import DatePicker from "react-datepicker"; import cn from "classnames"; import { useClickOutsideField } from "../../../hooks/wizardHooks"; import { getMetadataCollectionFieldName } from "../../../utils/resourceUtils"; +import { getCurrentLanguageInformation } from "../../../utils/utils"; import DropDown, { DropDownType } from "../DropDown"; import RenderDate from "../RenderDate"; import { parseISO } from "date-fns"; @@ -179,24 +180,21 @@ const EditableDateValue = ({ handleKeyDown: (event: React.KeyboardEvent, type: string) => void }) => editMode ? (
- setFieldValue(field.name, value)} - onClose={() => setEditMode(false)} - slotProps={{ - textField: { - fullWidth: true, - onKeyDown: (event) => { - if (event.key === "Enter") { - handleKeyDown(event, "date") - } - }, - onBlur: (event) => { - setEditMode(false) - } - } - }} + onClickOutside={() => setEditMode(false)} + showTimeInput + showYearDropdown + showMonthDropdown + yearDropdownItemNumber={2} + dateFormat="Pp" + popperPlacement="bottom-start" + popperClassName="datepicker-custom" + className="datepicker-custom-input" + wrapperClassName="datepicker-custom-wrapper" + locale={getCurrentLanguageInformation()?.dateLocale} />
) : ( @@ -388,24 +386,19 @@ const EditableSingleValueTime = ({ return editMode ? (
- setFieldValue(field.name, value)} - onClose={() => setEditMode(false)} - slotProps={{ - textField: { - fullWidth: true, - onKeyDown: (event) => { - if (event.key === "Enter") { - handleKeyDown(event, "date") - } - }, - onBlur: () => { - setEditMode(false) - } - } - }} + onClickOutside={() => setEditMode(false)} + showTimeSelect + showTimeSelectOnly + dateFormat="p" + popperPlacement="bottom-start" + popperClassName="datepicker-custom" + className="datepicker-custom-input" + wrapperClassName="datepicker-custom-wrapper" + locale={getCurrentLanguageInformation()?.dateLocale} />
) : ( diff --git a/src/styles/components/_components-config.scss b/src/styles/components/_components-config.scss index 17c8b27cf4..0860459220 100644 --- a/src/styles/components/_components-config.scss +++ b/src/styles/components/_components-config.scss @@ -35,6 +35,7 @@ @import "footer"; @import "tables"; @import "date-picker"; +@import "datepicker-custom"; @import "alerts"; @import "inputs"; @import "stats"; diff --git a/src/styles/components/_datepicker-custom.scss b/src/styles/components/_datepicker-custom.scss new file mode 100644 index 0000000000..b0816d9ffe --- /dev/null +++ b/src/styles/components/_datepicker-custom.scss @@ -0,0 +1,61 @@ +/** + * Licensed to The Apereo Foundation under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * + * The Apereo Foundation licenses this file to you under the Educational + * Community License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License + * at: + * + * http://opensource.org/licenses/ecl2.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + * + */ + @use "sass:color"; + + .datepicker-custom { + z-index: 10000 !important; + + &-wrapper { + width: 100%; + } + + &-input { + height: 25px !important; + } + } + + .react-datepicker__navigation--years { + &::before { + border-color: #ccc; + border-style: solid; + border-width: 3px 3px 0 0; + content: ''; + display: block; + height: 9px; + left: 11px; + position: absolute; + width: 9px; + } + + &-upcoming::before { + top: 17px; + transform: rotate(315deg); + } + + &-previous::before { + top: 6px; + transform: rotate(135deg); + } + + &:hover::before { + border-color: color.adjust(#ccc, $lightness: -15%); + } + } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 8e6857d36b..0344429ec2 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -23,7 +23,11 @@ export const getCurrentLanguageInformation = () => { // Get code, flag, name and date locale of the current language let currentLang = languages.find(({ code }) => code === i18n.language); if (typeof currentLang === "undefined") { - currentLang = languages.find(({ code }) => code === "en-US"); + // If detected language code, like "de-CH", isn't part of translations try 2-digit language code + currentLang = languages.find(({ code }) => code === i18n.language.split("-")[0]); + if (typeof currentLang === "undefined") { + currentLang = languages.find(({ code }) => code === "en-US"); + } } return currentLang;