diff --git a/js/app/dashboard/components/App.js b/js/app/dashboard/components/App.js new file mode 100644 index 0000000000..2a22faf877 --- /dev/null +++ b/js/app/dashboard/components/App.js @@ -0,0 +1,81 @@ +import React, { createRef } from "react" + +import Split from 'react-split' +import { Rnd } from 'react-rnd' + +import FiltersModalContent from './FiltersModalContent' +import RightPanel from './RightPanel' +import LeafletMap from './LeafletMap' +import Navbar from './Navbar' +import Modals from './Modals' +import { useDispatch } from "react-redux" +import { updateRightPanelSize } from "../redux/actions" +import { useSelector } from "react-redux" + +const App = ({ loadingAnim }) => { + + const mapRef = createRef() + const dispatch = useDispatch() + const showFiltersPanel = useSelector(state => state.filtersModalIsOpen) + + const saveFilterPanelSize = ({ref}) => { + localStorage.setItem('cpccl__dshbd__fltrs__size', JSON.stringify({width: ref.style.width, height: ref.style.height})) + } + const saveFilterPanelPosition = ({d}) => { + // FIXME : I don't really get what x, y values from the "d" variable are indicating... position is not saved correctly between page loads + localStorage.setItem('cpccl__dshbd__fltrs__position', JSON.stringify({x: d.x, y: d.y})) + } + + const initialFilterPanelSize = {width: 1000, height: 510} + // const initialFilterPanelPosition = {x: 0, y: 0,...JSON.parse(localStorage.getItem('cpccl__dshbd__fltrs__position'))} + const initialFilterPanelPosition = {x: 0, y: 0} + + return ( + <> +
+ +
+
+ dispatch(updateRightPanelSize(sizes[1])) } + onDragEnd={ () => mapRef.current.invalidateSize() }> +
+
+ + + + + + + + { + // It seems like a bad way to get a ref to the map, + // but we can't use the ref prop + mapRef.current = e.target + }} /> +
+
+ +
+
+ + saveFilterPanelPosition({e, d})} + onResizeStop={(e, direction, ref, delta, position) => saveFilterPanelSize({e, direction, ref, delta, position})} + > + + + + ) +} + +export { App } \ No newline at end of file diff --git a/js/app/dashboard/components/FiltersModalContent.js b/js/app/dashboard/components/FiltersModalContent.js index d0ff83b1d6..2f7405f944 100644 --- a/js/app/dashboard/components/FiltersModalContent.js +++ b/js/app/dashboard/components/FiltersModalContent.js @@ -1,27 +1,27 @@ -import React from "react"; -import _ from "lodash"; -import { connect } from "react-redux"; -import { withTranslation } from "react-i18next"; -import { Form, Slider, Switch } from "antd"; -import { Formik } from "formik"; +import React, { useEffect, useRef } from "react" +import _ from "lodash" +import { connect } from "react-redux" +import { useTranslation } from "react-i18next" +import { Form, Slider, Switch } from "antd" +import { Formik, useFormikContext } from "formik" -import Avatar from "../../components/Avatar"; +import Avatar from "../../components/Avatar" import { closeFiltersModal, setFilterValue, onlyFilter, -} from "../redux/actions"; +} from "../redux/actions" import { selectAllTags, selectBookedUsernames, selectFiltersSetting, -} from "../redux/selectors"; +} from "../redux/selectors" -import "antd/lib/grid/style/index.css"; -import OrganizationsOrTagsSelect from "./OrganizationsOrTagsSelect"; +import "antd/lib/grid/style/index.css" +import OrganizationsOrTagsSelect from "./OrganizationsOrTagsSelect" function isHidden(hiddenCouriers, username) { - return !!_.find(hiddenCouriers, (u) => u === username); + return !!_.find(hiddenCouriers, (u) => u === username) } const timeSteps = { @@ -43,336 +43,328 @@ const timeSteps = { 21: "21:00", 22: "22:00", 24: "23:59", -}; +} const timeStepsWithStyle = _.mapValues(timeSteps, (value) => ({ label: value, style: { fontSize: "10px", }, -})); +})) + +const onlyFilterColor = "#8250df" + +const FormikObserver = ({ onChange }) => { + const { values, initialValues } = useFormikContext(); + const previousValues = useRef(initialValues); -const onlyFilterColor = "#8250df"; + useEffect(() => { + if (!_.isEqual(previousValues.current, values)) { + previousValues.current = values; + onChange(values); + } + }, [values, initialValues, onChange]); + + return null; +} -class FiltersModalContent extends React.Component { - _onSubmit(values) { - this.props.setFilterValue("onlyFilter", null); - this.props.setFilterValue("showFinishedTasks", values.showFinishedTasks); - this.props.setFilterValue("showCancelledTasks", values.showCancelledTasks); - this.props.setFilterValue( +function FiltersModalContent(props) { + const { t } = useTranslation() + + const onChange = (values) => { + props.setFilterValue("onlyFilter", null) + props.setFilterValue("showFinishedTasks", values.showFinishedTasks) + props.setFilterValue("showCancelledTasks", values.showCancelledTasks) + props.setFilterValue( "showIncidentReportedTasks", values.showIncidentReportedTasks, - ); - this.props.setFilterValue( + ) + props.setFilterValue( "alwayShowUnassignedTasks", values.alwayShowUnassignedTasks, - ); - this.props.setFilterValue("tags", values.tags); - this.props.setFilterValue("excludedTags", values.excludedTags); - this.props.setFilterValue("includedOrgs", values.includedOrgs); - this.props.setFilterValue("excludedOrgs", values.excludedOrgs); - this.props.setFilterValue("hiddenCouriers", values.hiddenCouriers); - this.props.setFilterValue("timeRange", values.timeRange); - - this.props.closeFiltersModal(); + ) + props.setFilterValue("tags", values.tags) + props.setFilterValue("excludedTags", values.excludedTags) + props.setFilterValue("includedOrgs", values.includedOrgs) + props.setFilterValue("excludedOrgs", values.excludedOrgs) + props.setFilterValue("hiddenCouriers", values.hiddenCouriers) + props.setFilterValue("timeRange", values.timeRange) } - render() { - const { onlyFilter } = this.props; - let initialValues = { - showFinishedTasks: this.props.showFinishedTasks, - showCancelledTasks: this.props.showCancelledTasks, - showIncidentReportedTasks: this.props.showIncidentReportedTasks, - alwayShowUnassignedTasks: this.props.alwayShowUnassignedTasks, - tags: this.props.selectedTags, - excludedTags: this.props.excludedTags, - includedOrgs: this.props.includedOrgs, - excludedOrgs: this.props.excludedOrgs, - hiddenCouriers: this.props.hiddenCouriers, - timeRange: this.props.timeRange, - }; + const { onlyFilter } = props + const initialValues = { + showFinishedTasks: props.showFinishedTasks, + showCancelledTasks: props.showCancelledTasks, + showIncidentReportedTasks: props.showIncidentReportedTasks, + alwayShowUnassignedTasks: props.alwayShowUnassignedTasks, + tags: props.selectedTags, + excludedTags: props.excludedTags, + includedOrgs: props.includedOrgs, + excludedOrgs: props.excludedOrgs, + hiddenCouriers: props.hiddenCouriers, + timeRange: props.timeRange, + } - return ( - - {({ values, handleSubmit, setFieldValue }) => ( -
- -
-
+ {({ values, setFieldValue }) => ( +
+

+ {t("ADMIN_DASHBOARD_FILTERS_MODAL_TITLE")} + +

+

+ {t("ADMIN_DASHBOARD_FILTERS_MODAL_HELP")} +

+ +
+
+
+
+ + - - - setFieldValue("showFinishedTasks", checked) - } - /> - - - + setFieldValue("showFinishedTasks", checked) + } + /> + + + { + if (onlyFilter === "showCancelledTasks") { + props.setFilterValue("onlyFilter", null) } - unCheckedChildren={this.props.t( - "ADMIN_DASHBOARD_FILTERS_HIDE", - )} - defaultChecked={values.showCancelledTasks} - style={{ - backgroundColor: - onlyFilter === "showCancelledTasks" - ? onlyFilterColor - : null, - }} - onChange={(checked) => { - if (onlyFilter === "showCancelledTasks") { - this.props.setFilterValue("onlyFilter", null); - } - setFieldValue("showCancelledTasks", checked); - }} - /> - + + + { + if (onlyFilter === "showIncidentReportedTasks") { + props.setFilterValue("onlyFilter", null) } - className="btn btn-link" - > - {this.props.t("ONLY_SHOW_THESE")} - - - + + + + + + {t( + "ADMIN_DASHBOARD_FILTERS_ALWAYS_SHOW_UNASSIGNED_HELP_TEXT", + )} + + + } + > + + setFieldValue("alwayShowUnassignedTasks", checked) + } + /> + + +
+
+
+
+ +
+
+
+
+ + {props.couriers.map((username) => ( +
+
+ {username} +
+
{ - if (onlyFilter === "showIncidentReportedTasks") { - this.props.setFilterValue("onlyFilter", null); + if (checked) { + setFieldValue( + "hiddenCouriers", + _.filter( + values.hiddenCouriers, + (u) => u !== username, + ), + ) + } else { + setFieldValue( + "hiddenCouriers", + values.hiddenCouriers.concat([username]), + ) } - setFieldValue("showIncidentReportedTasks", checked); }} /> - - - - - - {this.props.t( - "ADMIN_DASHBOARD_FILTERS_ALWAYS_SHOW_UNASSIGNED_HELP_TEXT", - )} - - - } - > - - setFieldValue("alwayShowUnassignedTasks", checked) - } - /> - - -
-
-
-
- -
-
-
-
- - {this.props.couriers.map((username) => ( -
- - {username} - -
- { - if (checked) { - setFieldValue( - "hiddenCouriers", - _.filter( - values.hiddenCouriers, - (u) => u !== username, - ), - ); - } else { - setFieldValue( - "hiddenCouriers", - values.hiddenCouriers.concat([username]), - ); - } - }} - /> - setFieldValue("hiddenCouriers", this.props.couriers.filter(c => c !== username))} - > - {this.props.t("ONLY")} - -
+ {t("ONLY")} +
- ))} -
+
+ ))}
-
-
- setFieldValue("timeRange", value)} - /> -
+
+
+
+ setFieldValue("timeRange", value)} + />
-
+
+ - +
+
)} - - ); - } + + ) } function mapStateToProps(state) { @@ -388,7 +380,7 @@ function mapStateToProps(state) { includedOrgs, excludedOrgs, onlyFilter, - } = selectFiltersSetting(state); + } = selectFiltersSetting(state) return { tags: selectAllTags(state), @@ -404,7 +396,7 @@ function mapStateToProps(state) { hiddenCouriers, timeRange, onlyFilter, - }; + } } function mapDispatchToProps(dispatch) { @@ -412,10 +404,10 @@ function mapDispatchToProps(dispatch) { closeFiltersModal: () => dispatch(closeFiltersModal()), setFilterValue: (key, value) => dispatch(setFilterValue(key, value)), setOnlyFilter: (filter) => dispatch(onlyFilter(filter)), - }; + } } export default connect( mapStateToProps, mapDispatchToProps, -)(withTranslation()(FiltersModalContent)); +)(FiltersModalContent) diff --git a/js/app/dashboard/components/Modals.js b/js/app/dashboard/components/Modals.js index bdcb136694..987612d740 100644 --- a/js/app/dashboard/components/Modals.js +++ b/js/app/dashboard/components/Modals.js @@ -24,7 +24,6 @@ import { closeTaskRescheduleModal } from '../redux/actions' import TaskModalContent from './TaskModalContent' -import FiltersModalContent from './FiltersModalContent' import SettingsModalContent from './SettingsModalContent' import ImportModalContent from './ImportModalContent' import AddUserModalContent from './AddUserModalContent' @@ -55,15 +54,6 @@ class Modals extends React.Component { shouldCloseOnOverlayClick={ true }> - this.props.closeFiltersModal() } - className="ReactModal__Content--filters" - shouldCloseOnOverlayClick={ true }> - - * { margin-right: $base-margin-xs; } @@ -396,9 +400,6 @@ html, body { .ReactModal__Content--task-report-incident { min-width: 40%; } -.ReactModal__Content--filters { - min-width: 66.666%; -} .ReactModal__Content--task-form { max-height: 100%; @@ -407,25 +408,16 @@ html, body { } } -.ReactModal__Content--filters { - padding: 15px; - max-height: calc(100vh - 30px); - .dashboard { - &__modal-filters { - &__tabpane { - min-height: 150px; - display: flex; - flex-direction: column; - justify-content: center; - padding-top: 15px; - } - &__courier { - padding: 5px 0; - display: flex; - justify-content: space-between; - } - } - } +.dashboard__filters-panel { + overflow-y: scroll; + height: 100%; + width: 100%; +} + +.dashboard__filters-panel__tabpane { + display: flex; + flex-direction: column; + justify-content: center; } .ReactModal__Content--settings { diff --git a/js/app/dashboard/index.js b/js/app/dashboard/index.js index 0b455d42f6..8f23601328 100644 --- a/js/app/dashboard/index.js +++ b/js/app/dashboard/index.js @@ -1,20 +1,14 @@ -import React, { createRef } from 'react' +import React from 'react' import { createRoot } from 'react-dom/client' import { Provider } from 'react-redux' import lottie from 'lottie-web' import { I18nextProvider } from 'react-i18next' import moment from 'moment' import { ConfigProvider } from 'antd' -import Split from 'react-split' import i18n, { antdLocale } from '../i18n' import { createStoreFromPreloadedState } from './redux/store' -import RightPanel from './components/RightPanel' -import LeafletMap from './components/LeafletMap' -import Navbar from './components/Navbar' -import Modals from './components/Modals' -import { updateRightPanelSize } from './redux/actions' import { recurrenceRulesAdapter } from './redux/selectors' import { initialState as settingsInitialState, defaultFilters, defaultSettings, defaultMapFilters } from './redux/settingsReducers' @@ -24,6 +18,7 @@ import './dashboard.scss' import { organizationAdapter, taskAdapter, taskListAdapter, tourAdapter, trailerAdapter, vehicleAdapter, warehouseAdapter } from '../coopcycle-frontend-js/logistics/redux' import _ from 'lodash' import { createClient } from './utils/client' +import { App } from './components/App' const dashboardEl = document.getElementById('dashboard') const date = moment(dashboardEl.dataset.date) @@ -136,52 +131,16 @@ async function start(tasksRequest, tasksListsRequest, toursRequest) { }) const store = createStoreFromPreloadedState(preloadedState) - - const mapRef = createRef() - const root = createRoot(document.getElementById('dashboard')) root.render( - - - -
- -
-
- store.dispatch(updateRightPanelSize(sizes[1])) } - onDragEnd={ () => mapRef.current.invalidateSize() }> -
-
- - - - - - - - { - // It seems like a bad way to get a ref to the map, - // but we can't use the ref prop - mapRef.current = e.target - }} /> -
-
- -
-
- -
-
-
+ + + + + + + ) // hide export modal after button click diff --git a/js/app/i18n/locales/en.json b/js/app/i18n/locales/en.json index 935e37e86b..17933ecda2 100644 --- a/js/app/i18n/locales/en.json +++ b/js/app/i18n/locales/en.json @@ -79,6 +79,8 @@ "ADMIN_DASHBOARD_ORDERS_TAKEAWAY_ALERT": "This order will be collected by the customer", "ADMIN_DASHBOARD_USERS_SEARCHSTORE": "Search store…", "ADMIN_DASHBOARD_USERS_SEARCHRESTAURANT": "Search restaurant…", + "ADMIN_DASHBOARD_FILTERS_MODAL_TITLE": "Filters", + "ADMIN_DASHBOARD_FILTERS_MODAL_HELP": "Drag me and resize me!", "ADMIN_DASHBOARD_FILTERS_COMPLETED_TASKS": "Finished tasks", "ADMIN_DASHBOARD_FILTERS_CANCELLED_TASKS": "Cancelled tasks", "ADMIN_DASHBOARD_FILTERS_ALWAYS_SHOW_UNASSIGNED": "Always show unassigned tasks", @@ -629,6 +631,7 @@ "SELECT_THE_DATE_AND_TIME_OF_THE_INCIDENT": "Select the date and time of the incident.", "PLEASE_SELECT_A_DATE": "Please select a date", "OPEN": "Open", + "CLOSE": "CLOSE", "CLOSED": "Closed", "PLUS_ADD_TAGS": "+ Add tags", "TRANSPORTER_MESSAGE_TYPE_SCONTR": "Task imported from the carrier", diff --git a/package-lock.json b/package-lock.json index 30b1107ac0..5a8b26a695 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "lodash": "^4.17.21", "promise": "^8.3.0", "react-leaflet": "^4.2.1", + "react-rnd": "^10.5.2", "whatwg-fetch": "^2.0.4" }, "devDependencies": { @@ -14264,7 +14265,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "dev": true, "engines": { "node": ">=6" } @@ -25425,7 +25425,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -27315,7 +27314,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -27325,8 +27323,7 @@ "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/property-information": { "version": "6.1.1", @@ -28290,6 +28287,16 @@ "react-dom": "*" } }, + "node_modules/re-resizable": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.2.tgz", + "integrity": "sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -28508,6 +28515,20 @@ "react": "^18.2.0" } }, + "node_modules/react-draggable": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", + "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", + "license": "MIT", + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, "node_modules/react-dropzone": { "version": "11.7.1", "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.7.1.tgz", @@ -28821,6 +28842,27 @@ "react": ">=16.8.0" } }, + "node_modules/react-rnd": { + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.5.2.tgz", + "integrity": "sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw==", + "license": "MIT", + "dependencies": { + "re-resizable": "6.11.2", + "react-draggable": "4.4.6", + "tslib": "2.6.2" + }, + "peerDependencies": { + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, + "node_modules/react-rnd/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD" + }, "node_modules/react-router": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", @@ -44993,8 +45035,7 @@ "clsx": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "dev": true + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" }, "co": { "version": "4.6.0", @@ -53399,8 +53440,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-copy": { "version": "0.1.0", @@ -54747,7 +54787,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -54757,8 +54796,7 @@ "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" } } }, @@ -55475,6 +55513,12 @@ "rc-util": "^5.36.0" } }, + "re-resizable": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.2.tgz", + "integrity": "sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==", + "requires": {} + }, "react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -55646,6 +55690,15 @@ "scheduler": "^0.23.0" } }, + "react-draggable": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", + "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", + "requires": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + } + }, "react-dropzone": { "version": "11.7.1", "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.7.1.tgz", @@ -55851,6 +55904,23 @@ "shallow-equal": "^1.1.0" } }, + "react-rnd": { + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.5.2.tgz", + "integrity": "sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw==", + "requires": { + "re-resizable": "6.11.2", + "react-draggable": "4.4.6", + "tslib": "2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, "react-router": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", diff --git a/package.json b/package.json index f37d534185..29c68ecc86 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "lodash": "^4.17.21", "promise": "^8.3.0", "react-leaflet": "^4.2.1", + "react-rnd": "^10.5.2", "whatwg-fetch": "^2.0.4" }, "devDependencies": {