Date picker (#3572)

* Add date picker dependency
* Add DateInput component
* Add DateInput to edit panels
* Add DateInput to DateFilter
* Add time to DateInput and add to Timestamp filter
* Use calendar icon for button
This commit is contained in:
WithoutPants 2023-03-22 11:25:50 +11:00 committed by GitHub
parent b602ed2381
commit b6b275edc8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 458 additions and 73 deletions

View file

@ -49,6 +49,7 @@
"normalize-url": "^4.5.1",
"react": "^17.0.2",
"react-bootstrap": "^1.6.6",
"react-datepicker": "^4.10.0",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-intl": "^6.2.8",
@ -83,6 +84,7 @@
"@types/mousetrap": "^1.6.11",
"@types/node": "^18.13.0",
"@types/react": "^17.0.53",
"@types/react-datepicker": "^4.10.0",
"@types/react-dom": "^17.0.19",
"@types/react-helmet": "^6.1.6",
"@types/react-router-bootstrap": "^0.24.5",

View file

@ -39,6 +39,7 @@ import { galleryTitle } from "src/core/galleries";
import { useRatingKeybinds } from "src/hooks/keybinds";
import { ConfigurationContext } from "src/hooks/Config";
import isEqual from "lodash-es/isEqual";
import { DateInput } from "src/components/Shared/DateInput";
interface IProps {
gallery: Partial<GQL.GalleryDataFragment>;
@ -458,11 +459,18 @@ export const GalleryEditPanel: React.FC<IProps> = ({
/>
</Col>
</Form.Group>
{renderTextField(
"date",
intl.formatMessage({ id: "date" }),
"YYYY-MM-DD"
)}
<Form.Group controlId="date" as={Row}>
{FormUtils.renderLabel({
title: intl.formatMessage({ id: "date" }),
})}
<Col xs={9}>
<DateInput
value={formik.values.date}
onValueChange={(value) => formik.setFieldValue("date", value)}
error={formik.errors.date}
/>
</Col>
</Form.Group>
<Form.Group controlId="rating" as={Row}>
{FormUtils.renderLabel({
title: intl.formatMessage({ id: "rating" }),

View file

@ -20,6 +20,7 @@ import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
import { useRatingKeybinds } from "src/hooks/keybinds";
import { ConfigurationContext } from "src/hooks/Config";
import isEqual from "lodash-es/isEqual";
import { DateInput } from "src/components/Shared/DateInput";
interface IProps {
image: GQL.ImageDataFragment;
@ -205,11 +206,18 @@ export const ImageEditPanel: React.FC<IProps> = ({
/>
</Col>
</Form.Group>
{renderTextField(
"date",
intl.formatMessage({ id: "date" }),
"YYYY-MM-DD"
)}
<Form.Group controlId="date" as={Row}>
{FormUtils.renderLabel({
title: intl.formatMessage({ id: "date" }),
})}
<Col xs={9}>
<DateInput
value={formik.values.date}
onValueChange={(value) => formik.setFieldValue("date", value)}
error={formik.errors.date}
/>
</Col>
</Form.Group>
<Form.Group controlId="rating" as={Row}>
{FormUtils.renderLabel({
title: intl.formatMessage({ id: "rating" }),

View file

@ -4,6 +4,7 @@ import { useIntl } from "react-intl";
import { CriterionModifier } from "../../../core/generated-graphql";
import { IDateValue } from "../../../models/list-filter/types";
import { Criterion } from "../../../models/list-filter/criteria/criterion";
import { DateInput } from "src/components/Shared/DateInput";
interface IDateFilterProps {
criterion: Criterion<IDateValue>;
@ -18,11 +19,7 @@ export const DateFilter: React.FC<IDateFilterProps> = ({
const { value } = criterion;
function onChanged(
event: React.ChangeEvent<HTMLInputElement>,
property: "value" | "value2"
) {
const newValue = event.target.value;
function onChanged(newValue: string, property: "value" | "value2") {
const valueCopy = { ...value };
valueCopy[property] = newValue;
@ -36,16 +33,10 @@ export const DateFilter: React.FC<IDateFilterProps> = ({
) {
equalsControl = (
<Form.Group>
<Form.Control
className="btn-secondary"
type="text"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
onChanged(e, "value")
}
<DateInput
value={value?.value ?? ""}
placeholder={
intl.formatMessage({ id: "criterion.value" }) + " (YYYY-MM-DD)"
}
onValueChange={(v) => onChanged(v, "value")}
placeholder={intl.formatMessage({ id: "criterion.value" })}
/>
</Form.Group>
);
@ -59,17 +50,10 @@ export const DateFilter: React.FC<IDateFilterProps> = ({
) {
lowerControl = (
<Form.Group>
<Form.Control
className="btn-secondary"
type="text"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
onChanged(e, "value")
}
<DateInput
value={value?.value ?? ""}
placeholder={
intl.formatMessage({ id: "criterion.greater_than" }) +
" (YYYY-MM-DD)"
}
onValueChange={(v) => onChanged(v, "value")}
placeholder={intl.formatMessage({ id: "criterion.greater_than" })}
/>
</Form.Group>
);
@ -83,25 +67,21 @@ export const DateFilter: React.FC<IDateFilterProps> = ({
) {
upperControl = (
<Form.Group>
<Form.Control
className="btn-secondary"
type="text"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
onChanged(
e,
criterion.modifier === CriterionModifier.LessThan
? "value"
: "value2"
)
}
<DateInput
value={
(criterion.modifier === CriterionModifier.LessThan
? value?.value
: value?.value2) ?? ""
}
placeholder={
intl.formatMessage({ id: "criterion.less_than" }) + " (YYYY-MM-DD)"
onValueChange={(v) =>
onChanged(
v,
criterion.modifier === CriterionModifier.LessThan
? "value"
: "value2"
)
}
placeholder={intl.formatMessage({ id: "criterion.less_than" })}
/>
</Form.Group>
);

View file

@ -4,6 +4,7 @@ import { useIntl } from "react-intl";
import { CriterionModifier } from "../../../core/generated-graphql";
import { ITimestampValue } from "../../../models/list-filter/types";
import { Criterion } from "../../../models/list-filter/criteria/criterion";
import { DateInput } from "src/components/Shared/DateInput";
interface ITimestampFilterProps {
criterion: Criterion<ITimestampValue>;
@ -18,11 +19,7 @@ export const TimestampFilter: React.FC<ITimestampFilterProps> = ({
const { value } = criterion;
function onChanged(
event: React.ChangeEvent<HTMLInputElement>,
property: "value" | "value2"
) {
const newValue = event.target.value;
function onChanged(newValue: string, property: "value" | "value2") {
const valueCopy = { ...value };
valueCopy[property] = newValue;
@ -36,7 +33,13 @@ export const TimestampFilter: React.FC<ITimestampFilterProps> = ({
) {
equalsControl = (
<Form.Group>
<Form.Control
<DateInput
value={value?.value ?? ""}
onValueChange={(v) => onChanged(v, "value")}
placeholder={intl.formatMessage({ id: "criterion.value" })}
isTime
/>
{/* <Form.Control
className="btn-secondary"
type="text"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
@ -47,7 +50,7 @@ export const TimestampFilter: React.FC<ITimestampFilterProps> = ({
intl.formatMessage({ id: "criterion.value" }) +
" (YYYY-MM-DD HH:MM)"
}
/>
/> */}
</Form.Group>
);
}
@ -60,7 +63,13 @@ export const TimestampFilter: React.FC<ITimestampFilterProps> = ({
) {
lowerControl = (
<Form.Group>
<Form.Control
<DateInput
value={value?.value ?? ""}
onValueChange={(v) => onChanged(v, "value")}
placeholder={intl.formatMessage({ id: "criterion.greater_than" })}
isTime
/>
{/* <Form.Control
className="btn-secondary"
type="text"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
@ -71,7 +80,7 @@ export const TimestampFilter: React.FC<ITimestampFilterProps> = ({
intl.formatMessage({ id: "criterion.greater_than" }) +
" (YYYY-MM-DD HH:MM)"
}
/>
/> */}
</Form.Group>
);
}
@ -84,7 +93,24 @@ export const TimestampFilter: React.FC<ITimestampFilterProps> = ({
) {
upperControl = (
<Form.Group>
<Form.Control
<DateInput
value={
(criterion.modifier === CriterionModifier.LessThan
? value?.value
: value?.value2) ?? ""
}
onValueChange={(v) =>
onChanged(
v,
criterion.modifier === CriterionModifier.LessThan
? "value"
: "value2"
)
}
placeholder={intl.formatMessage({ id: "criterion.less_than" })}
isTime
/>
{/* <Form.Control
className="btn-secondary"
type="text"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
@ -104,7 +130,7 @@ export const TimestampFilter: React.FC<ITimestampFilterProps> = ({
intl.formatMessage({ id: "criterion.less_than" }) +
" (YYYY-MM-DD HH:MM)"
}
/>
/> */}
</Form.Group>
);
}

View file

@ -24,6 +24,7 @@ import { MovieScrapeDialog } from "./MovieScrapeDialog";
import { useRatingKeybinds } from "src/hooks/keybinds";
import { ConfigurationContext } from "src/hooks/Config";
import isEqual from "lodash-es/isEqual";
import { DateInput } from "src/components/Shared/DateInput";
interface IMovieEditPanel {
movie: Partial<GQL.MovieDataFragment>;
@ -410,11 +411,18 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
</Col>
</Form.Group>
{renderTextField(
"date",
intl.formatMessage({ id: "date" }),
"YYYY-MM-DD"
)}
<Form.Group controlId="date" as={Row}>
{FormUtils.renderLabel({
title: intl.formatMessage({ id: "date" }),
})}
<Col xs={9}>
<DateInput
value={formik.values.date}
onValueChange={(value) => formik.setFieldValue("date", value)}
error={formik.errors.date}
/>
</Col>
</Form.Group>
<Form.Group controlId="studio" as={Row}>
{FormUtils.renderLabel({

View file

@ -43,6 +43,7 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import { StringListInput } from "src/components/Shared/StringListInput";
import isEqual from "lodash-es/isEqual";
import { DateInput } from "src/components/Shared/DateInput";
const isScraper = (
scraper: GQL.Scraper | GQL.StashBox
@ -927,8 +928,35 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
</Col>
</Form.Group>
{renderField("birthdate", { placeholder: "YYYY-MM-DD" })}
{renderField("death_date", { placeholder: "YYYY-MM-DD" })}
<Form.Group controlId="birthdate" as={Row}>
<Form.Label column xs={labelXS} xl={labelXL}>
<FormattedMessage id="birthdate" />
</Form.Label>
<Col xs={fieldXS} xl={fieldXL}>
<DateInput
value={formik.values.birthdate}
onValueChange={(value) =>
formik.setFieldValue("birthdate", value)
}
error={formik.errors.birthdate}
/>
</Col>
</Form.Group>
<Form.Group controlId="death_date" as={Row}>
<Form.Label column xs={labelXS} xl={labelXL}>
<FormattedMessage id="death_date" />
</Form.Label>
<Col xs={fieldXS} xl={fieldXL}>
<DateInput
value={formik.values.death_date}
onValueChange={(value) =>
formik.setFieldValue("death_date", value)
}
error={formik.errors.death_date}
/>
</Col>
</Form.Group>
<Form.Group as={Row}>
<Form.Label column xs={labelXS} xl={labelXL}>

View file

@ -52,6 +52,7 @@ import { galleryTitle } from "src/core/galleries";
import { useRatingKeybinds } from "src/hooks/keybinds";
import { lazyComponent } from "src/utils/lazyComponent";
import isEqual from "lodash-es/isEqual";
import { DateInput } from "src/components/Shared/DateInput";
const SceneScrapeDialog = lazyComponent(() => import("./SceneScrapeDialog"));
const SceneQueryModal = lazyComponent(() => import("./SceneQueryModal"));
@ -769,11 +770,20 @@ export const SceneEditPanel: React.FC<IProps> = ({
/>
</Col>
</Form.Group>
{renderTextField(
"date",
intl.formatMessage({ id: "date" }),
"YYYY-MM-DD"
)}
<Form.Group controlId="date" as={Row}>
{FormUtils.renderLabel({
title: intl.formatMessage({ id: "date" }),
})}
<Col xs={9}>
<DateInput
value={formik.values.date}
onValueChange={(value) => formik.setFieldValue("date", value)}
error={formik.errors.date}
/>
</Col>
</Form.Group>
{renderTextField(
"director",
intl.formatMessage({ id: "director" })

View file

@ -0,0 +1,105 @@
import { faCalendar } from "@fortawesome/free-regular-svg-icons";
import React, { useMemo } from "react";
import { Button, InputGroup, Form } from "react-bootstrap";
import ReactDatePicker from "react-datepicker";
import TextUtils from "src/utils/text";
import { Icon } from "./Icon";
import "react-datepicker/dist/react-datepicker.css";
import { useIntl } from "react-intl";
interface IProps {
disabled?: boolean;
value: string | undefined;
isTime?: boolean;
onValueChange(value: string): void;
placeholder?: string;
error?: string;
}
export const DateInput: React.FC<IProps> = (props: IProps) => {
const intl = useIntl();
const date = useMemo(() => {
const toDate = props.isTime
? TextUtils.stringToFuzzyDateTime
: TextUtils.stringToFuzzyDate;
if (props.value) {
const ret = toDate(props.value);
if (!ret || isNaN(ret.getTime())) {
return undefined;
}
return ret;
}
}, [props.value, props.isTime]);
function maybeRenderButton() {
if (!props.disabled) {
const ShowPickerButton = ({
onClick,
}: {
onClick: (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => void;
}) => (
<Button variant="secondary" onClick={onClick}>
<Icon icon={faCalendar} />
</Button>
);
const dateToString = props.isTime
? TextUtils.dateTimeToString
: TextUtils.dateToString;
return (
<ReactDatePicker
selected={date}
onChange={(v) => {
props.onValueChange(v ? dateToString(v) : "");
}}
customInput={React.createElement(ShowPickerButton)}
showMonthDropdown
showYearDropdown
scrollableMonthYearDropdown
scrollableYearDropdown
maxDate={new Date()}
yearDropdownItemNumber={100}
portalId="date-picker-portal"
showTimeSelect={props.isTime}
/>
);
}
}
const placeholderText = intl.formatMessage({
id: props.isTime ? "datetime_format" : "date_format",
});
return (
<div>
<InputGroup hasValidation>
<Form.Control
className="date-input text-input"
disabled={props.disabled}
value={props.value}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
props.onValueChange(e.currentTarget.value)
}
placeholder={
!props.disabled
? props.placeholder
? `${props.placeholder} (${placeholderText})`
: placeholderText
: undefined
}
isInvalid={!!props.error}
/>
<InputGroup.Append>{maybeRenderButton()}</InputGroup.Append>
<Form.Control.Feedback type="invalid">
{props.error}
</Form.Control.Feedback>
</InputGroup>
</div>
);
};

View file

@ -323,3 +323,97 @@ button.collapse-button.btn-primary:not(:disabled):not(.disabled):active {
}
/* stylelint-enable */
}
.date-input.form-control:focus {
// z-index gets set to 3 in input groups
z-index: inherit;
}
/* stylelint-disable */
div.react-datepicker {
background-color: $body-bg;
border-color: $card-bg;
color: $text-color;
.react-datepicker__header,
.react-datepicker-time__header {
background-color: $secondary;
color: $text-color;
}
.react-datepicker__day {
color: $text-color;
&.react-datepicker__day--disabled {
color: $text-muted;
}
&:hover {
background: rgba(138, 155, 168, 0.15);
}
}
div.react-datepicker__time-container div.react-datepicker__time {
background-color: $body-bg;
color: $text-color;
ul.react-datepicker__time-list li.react-datepicker__time-list-item:hover {
background-color: rgba(138, 155, 168, 0.15);
}
}
.react-datepicker__day-name {
color: $text-color;
}
// replace the current month with the dropdowns
.react-datepicker__current-month {
display: none;
}
.react-datepicker__triangle {
display: none;
}
.react-datepicker__month-dropdown-container {
margin-left: 0;
margin-right: 0.25rem;
}
.react-datepicker__year-dropdown-container {
margin-left: 0.25rem;
margin-right: 0;
}
.react-datepicker__month-dropdown-container
.react-datepicker__month-read-view,
.react-datepicker__year-dropdown-container .react-datepicker__year-read-view {
font-weight: bold;
font-size: 0.944rem;
// react-datepicker hides these fields when the dropdown is shown
visibility: visible !important;
}
// hide the dropdown arrows
.react-datepicker__month-dropdown-container
.react-datepicker__month-read-view--down-arrow,
.react-datepicker__year-dropdown-container
.react-datepicker__year-read-view--down-arrow {
display: none;
}
.react-datepicker__year-dropdown,
.react-datepicker__month-dropdown {
background-color: $body-bg;
.react-datepicker__year-option:hover,
.react-datepicker__month-option:hover {
background-color: #8a9ba826;
}
}
}
/* stylelint-enable */
#date-picker-portal .react-datepicker-popper {
z-index: 1600;
}

View file

@ -25,6 +25,7 @@ Once migrated, these files can be deleted. The files can be optionally deleted d
* Added toggleable favorite button to Performer cards. ([#3369](https://github.com/stashapp/stash/pull/3369))
### 🎨 Improvements
* Added date/time pickers for date and timestamp fields. ([#3572](https://github.com/stashapp/stash/pull/3572))
* Added folder browser to path filter UI. ([#3570](https://github.com/stashapp/stash/pull/3570))
* Include Organized flag in merge dialog. ([#3565](https://github.com/stashapp/stash/pull/3565))
* Scene cover generation is now optional during scanning, and can be generated using the Generate task. ([#3187](https://github.com/stashapp/stash/pull/3187))

View file

@ -728,6 +728,8 @@
"custom": "Custom",
"date": "Date",
"death_date": "Death Date",
"date_format": "YYYY-MM-DD",
"datetime_format": "YYYY-MM-DD HH:MM",
"death_year": "Death Year",
"descending": "Descending",
"description": "Description",

View file

@ -189,6 +189,70 @@ const stringToDate = (dateString: string) => {
return new Date(year, monthIndex, day, 0, 0, 0, 0);
};
const stringToFuzzyDate = (dateString: string) => {
if (!dateString) return null;
const parts = dateString.split("-");
// Invalid date string
let year = Number(parts[0]);
if (isNaN(year)) year = new Date().getFullYear();
let monthIndex = 0;
if (parts.length > 1) {
monthIndex = Math.max(0, Number(parts[1]) - 1);
if (monthIndex > 11 || isNaN(monthIndex)) monthIndex = 0;
}
let day = 1;
if (parts.length > 2) {
day = Number(parts[2]);
if (day > 31 || isNaN(day)) day = 1;
}
return new Date(year, monthIndex, day, 0, 0, 0, 0);
};
const stringToFuzzyDateTime = (dateString: string) => {
if (!dateString) return null;
const dateTime = dateString.split(" ");
let date: Date | null = null;
if (dateTime.length > 0) {
date = stringToFuzzyDate(dateTime[0]);
}
if (!date) {
date = new Date();
}
if (dateTime.length > 1) {
const timeParts = dateTime[1].split(":");
if (date && timeParts.length > 0) {
date.setHours(Number(timeParts[0]));
}
if (date && timeParts.length > 1) {
date.setMinutes(Number(timeParts[1]));
}
if (date && timeParts.length > 2) {
date.setSeconds(Number(timeParts[2]));
}
}
return date;
};
function dateToString(date: Date) {
return `${date.getFullYear()}-${(date.getMonth() + 1)
.toString()
.padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`;
}
function dateTimeToString(date: Date) {
return `${dateToString(date)} ${date
.getHours()
.toString()
.padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`;
}
const getAge = (dateString?: string | null, fromDateString?: string | null) => {
if (!dateString) return 0;
@ -355,6 +419,10 @@ const TextUtils = {
secondsToTimestamp,
fileNameFromPath,
stringToDate,
stringToFuzzyDate,
stringToFuzzyDateTime,
dateToString,
dateTimeToString,
age: getAge,
bitRate,
resolution,

View file

@ -2139,7 +2139,7 @@
tslib "^2.4.1"
webcrypto-core "^1.7.4"
"@popperjs/core@^2.11.6":
"@popperjs/core@^2.11.6", "@popperjs/core@^2.9.2":
version "2.11.6"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
@ -2339,6 +2339,16 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/react-datepicker@^4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-4.10.0.tgz#fcb0e6a7787491bf2f37fbda2b537062608a0056"
integrity sha512-Cq+ks20vBIU6XN67TbkCHu8M7V46Y6vJrKE2n+8q/GfueJyWWTIKeC3Z7cz/d+qxGDq/VCrqA929R0U4lNuztg==
dependencies:
"@popperjs/core" "^2.9.2"
"@types/react" "*"
date-fns "^2.0.1"
react-popper "^2.2.5"
"@types/react-dom@^17.0.19":
version "17.0.19"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.19.tgz#36feef3aa35d045cacd5ed60fe0eef5272f19492"
@ -3303,7 +3313,7 @@ chardet@^0.7.0:
optionalDependencies:
fsevents "~2.3.2"
classnames@^2.2.5, classnames@^2.3.1, classnames@^2.3.2:
classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
@ -3559,6 +3569,11 @@ dataloader@2.2.2:
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0"
integrity sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==
date-fns@^2.0.1, date-fns@^2.24.0:
version "2.29.3"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==
debounce@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
@ -6449,6 +6464,18 @@ react-bootstrap@^1.6.6:
uncontrollable "^7.2.1"
warning "^4.0.3"
react-datepicker@^4.10.0:
version "4.10.0"
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.10.0.tgz#3f386ac5873dac5ea56544e51cdc01109938796c"
integrity sha512-6IfBCZyWj54ZZGLmEZJ9c4Yph0s9MVfEGDC2evOvf9AmVz+RRcfP2Czqad88Ff9wREbcbqa4dk7IFYeXF1d3Ag==
dependencies:
"@popperjs/core" "^2.9.2"
classnames "^2.2.6"
date-fns "^2.24.0"
prop-types "^15.7.2"
react-onclickoutside "^6.12.2"
react-popper "^2.3.0"
react-dom@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
@ -6463,6 +6490,11 @@ react-fast-compare@^2.0.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
react-fast-compare@^3.0.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.1.tgz#53933d9e14f364281d6cba24bfed7a4afb808b5f"
integrity sha512-xTYf9zFim2pEif/Fw16dBiXpe0hoy5PxcD8+OwBnTtNLfIm3g6WxhKNurY+6OmdH1u6Ta/W/Vl6vjbYP1MFnDg==
react-fast-compare@^3.1.1:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
@ -6504,6 +6536,11 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-onclickoutside@^6.12.2:
version "6.12.2"
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.12.2.tgz#8e6cf80c7d17a79f2c908399918158a7b02dda01"
integrity sha512-NMXGa223OnsrGVp5dJHkuKxQ4czdLmXSp5jSV9OqiCky9LOpPATn3vLldc+q5fK3gKbEHvr7J1u0yhBh/xYkpA==
react-overlays@^5.1.2:
version "5.2.1"
resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-5.2.1.tgz#49dc007321adb6784e1f212403f0fb37a74ab86b"
@ -6526,6 +6563,14 @@ react-photo-gallery@^8.0.0:
prop-types "~15.7.2"
resize-observer-polyfill "^1.5.0"
react-popper@^2.2.5, react-popper@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
dependencies:
react-fast-compare "^3.0.1"
warning "^4.0.2"
react-refresh@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
@ -7957,7 +8002,7 @@ vite@^4.1.1:
optionalDependencies:
fsevents "~2.3.2"
warning@^4.0.0, warning@^4.0.3:
warning@^4.0.0, warning@^4.0.2, warning@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==