diff --git a/src/actions/track-chair-actions.js b/src/actions/track-chair-actions.js index 65d9d4024..890a2e7e1 100644 --- a/src/actions/track-chair-actions.js +++ b/src/actions/track-chair-actions.js @@ -35,6 +35,7 @@ import { DEFAULT_PER_PAGE, DOUBLE_PER_PAGE } from "../utils/constants"; +import { snackbarErrorHandler } from "./base-actions"; URI.escapeQuerySpace = false; @@ -95,7 +96,7 @@ export const getTrackChairs = expand: "member,categories", relations: "member.none,categories.none", fields: - "id,categories.id,categories.name,member.first_name,member.last_name,member.email" + "id,categories.id,categories.name,member.first_name,member.last_name,member.email,member.id" }; if (filter.length > 0) { @@ -125,9 +126,9 @@ export const getTrackChairs = createAction(REQUEST_TRACK_CHAIRS), createAction(RECEIVE_TRACK_CHAIRS), `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/track-chairs`, - authErrorHandler, - { trackId, term, order, orderDir } - )(params)(dispatch).then(() => { + snackbarErrorHandler, + { trackId, term, order, orderDir, perPage } + )(params)(dispatch).finally(() => { dispatch(stopLoading()); }); }; @@ -150,7 +151,7 @@ export const addTrackChair = createAction(TRACK_CHAIR_ADDED), `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/track-chairs`, { member_id: member.id, categories: trackIds }, - authErrorHandler + snackbarErrorHandler )(params)(dispatch).then(() => { dispatch(stopLoading()); }); @@ -175,7 +176,7 @@ export const saveTrackChair = `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/track-chairs/${trackChairId}`, { categories: trackIds }, authErrorHandler - )(params)(dispatch).then(() => { + )(params)(dispatch).finally(() => { dispatch(stopLoading()); }); }; @@ -195,8 +196,8 @@ export const deleteTrackChair = createAction(TRACK_CHAIR_DELETED)({ trackChairId }), `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/track-chairs/${trackChairId}`, null, - authErrorHandler - )(params)(dispatch).then(() => { + snackbarErrorHandler + )(params)(dispatch).finally(() => { dispatch(stopLoading()); }); }; diff --git a/src/i18n/en.json b/src/i18n/en.json index bf2730055..83e3b4b29 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -3459,7 +3459,7 @@ "no_items": "No items found for this search criteria.", "name": "Name", "track": "Track", - "delete_warning": "Are you sure you want to remove track chair ", + "delete_warning": "Are you sure you want to remove track chair for", "saved": "Track chair assigned successfully.", "placeholders": { "search": "Search track chairs by name", diff --git a/src/pages/track_chairs/components/track-chair-dialog.js b/src/pages/track_chairs/components/track-chair-dialog.js new file mode 100644 index 000000000..14b0bc5aa --- /dev/null +++ b/src/pages/track_chairs/components/track-chair-dialog.js @@ -0,0 +1,219 @@ +/** + * Copyright 2024 OpenStack Foundation + * Licensed under the Apache 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://www.apache.org/licenses/LICENSE-2.0 + * 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. + * */ + +import React from "react"; +import PropTypes from "prop-types"; +import T from "i18n-react/dist/i18n-react"; +import { FormikProvider, useFormik } from "formik"; +import * as yup from "yup"; +import { + Box, + Button, + Checkbox, + Chip, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Divider, + FormControl, + FormHelperText, + Grid2, + IconButton, + InputLabel, + ListItemText, + MenuItem, + Select, + Typography +} from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; +import ClearIcon from "@mui/icons-material/Clear"; +import { queryMembers } from "openstack-uicore-foundation/lib/utils/query-actions"; +import MuiFormikAsyncAutocomplete from "../../../components/mui/formik-inputs/mui-formik-async-select"; +import useScrollToError from "../../../hooks/useScrollToError"; + +const formatMemberOption = (m) => ({ + value: m.id, + label: m.email + ? `${m.first_name} ${m.last_name} (${m.email})` + : `${m.first_name} ${m.last_name} (${m.id})` +}); + +const toMemberOption = (member) => { + if (!member) return null; + return formatMemberOption(member); +}; + +const TrackChairDialog = ({ entity, tracks, onSave, onClose }) => { + const formik = useFormik({ + initialValues: { + id: entity?.id ?? 0, + member: toMemberOption(entity?.member ?? null), + trackIds: entity?.trackIds ?? [] + }, + enableReinitialize: true, + validationSchema: yup.object().shape({ + member: yup + .object() + .nullable() + .required(T.translate("validation.required")), + trackIds: yup + .array() + .min(1, T.translate("validation.required")) + .required(T.translate("validation.required")) + }), + onSubmit: (values) => onSave(values) + }); + + useScrollToError(formik, true); + + const title = entity?.id + ? `${T.translate("general.edit")} ${T.translate( + "track_chairs.track_chair" + )}` + : T.translate("track_chairs.add"); + + const tracks_ddl = tracks.map((t) => ({ label: t.name, value: t.id })); + + return ( + + + {title} + + + + + + + + + + + + {T.translate("track_chairs.placeholders.select_track_chair")}{" "} + * + + + + + + {T.translate("track_chairs.track")} * + + + + {formik.touched.trackIds && formik.errors.trackIds && ( + {formik.errors.trackIds} + )} + + + + + + + + + + + + ); +}; + +TrackChairDialog.propTypes = { + entity: PropTypes.object, + tracks: PropTypes.array.isRequired, + onClose: PropTypes.func.isRequired, + onSave: PropTypes.func.isRequired +}; + +export default TrackChairDialog; diff --git a/src/pages/track_chairs/track-chair-list-page.js b/src/pages/track_chairs/track-chair-list-page.js index e77abcebd..866f08cb0 100644 --- a/src/pages/track_chairs/track-chair-list-page.js +++ b/src/pages/track_chairs/track-chair-list-page.js @@ -9,17 +9,25 @@ * 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. - **/ + * */ -import React from "react"; +import React, { useEffect, useState } from "react"; import { connect } from "react-redux"; import T from "i18n-react/dist/i18n-react"; -import Swal from "sweetalert2"; -import { Pagination } from "react-bootstrap"; -import Dropdown from "openstack-uicore-foundation/lib/components/inputs/dropdown" -import FreeTextSearch from "openstack-uicore-foundation/lib/components/free-text-search" -import MemberInput from "openstack-uicore-foundation/lib/components/inputs/member-input" -import Table from "openstack-uicore-foundation/lib/components/table"; +import { + Box, + Button, + FormControl, + Grid2, + IconButton, + InputAdornment, + MenuItem, + Select +} from "@mui/material"; +import AddIcon from "@mui/icons-material/Add"; +import ClearIcon from "@mui/icons-material/Clear"; +import MuiTable from "openstack-uicore-foundation/lib/components/mui/table"; +import SearchInput from "openstack-uicore-foundation/lib/components/mui/search-input"; import { getTrackChairs, deleteTrackChair, @@ -27,272 +35,262 @@ import { addTrackChair, exportTrackChairs } from "../../actions/track-chair-actions"; +import { DEFAULT_CURRENT_PAGE } from "../../utils/constants"; +import TrackChairDialog from "./components/track-chair-dialog"; -import "../../styles/track-chair-list-page.less"; - -class TrackChairListPage extends React.Component { - constructor(props) { - super(props); +const TrackChairListPage = ({ + currentSummit, + trackChairs, + currentPage, + perPage, + term, + order, + orderDir, + totalTrackChairs, + trackId, + getTrackChairs, + deleteTrackChair, + saveTrackChair, + addTrackChair, + exportTrackChairs +}) => { + const [dialogEntity, setDialogEntity] = useState(null); - this.state = { - showForm: false, - member: null, - trackIds: [], - trackId: null - }; - } + useEffect(() => { + if (currentSummit?.id) getTrackChairs(); + }, [currentSummit?.id]); - componentDidMount() { - const { currentSummit } = this.props; - if (currentSummit) { - this.props.getTrackChairs(); - } - } + const chairTracks = currentSummit.tracks.filter((t) => t.chair_visible); - toggleForm = (open) => { - this.setState((state) => ({ showForm: open, member: null, trackIds: [] })); + const handleSearch = (searchTerm) => { + getTrackChairs( + trackId, + searchTerm, + DEFAULT_CURRENT_PAGE, + perPage, + order, + orderDir + ); }; - handleChange = (ev) => { - const { value, id } = ev.target; - const isNew = id === "member"; - - this.setState((state) => ({ - [id]: value, - trackId: isNew ? 0 : state.trackId - })); + const handleFilterByTrack = (ev) => { + getTrackChairs( + ev.target.value, + term, + DEFAULT_CURRENT_PAGE, + perPage, + order, + orderDir + ); }; - handleEdit = (trackChairId) => { - const { trackChairs } = this.props; - const trackChair = trackChairs.find((s) => s.id === trackChairId); - - this.setState({ - member: trackChair.member, - trackIds: trackChair.categories.map((c) => c.id), - showForm: true, - trackId: trackChairId - }); + const handleSort = (key, dir) => { + getTrackChairs(trackId, term, currentPage, perPage, key, dir); }; - handleSave = () => { - const { member, trackIds, trackId } = this.state; - - if (trackId) { - this.props.saveTrackChair(trackId, trackIds).then(() => { - this.setState({ member: null, trackIds: [], showForm: false }); - }); - } else { - this.props.addTrackChair(member, trackIds).then(() => { - this.setState({ member: null, trackIds: [], showForm: false }); - }); - } + const handlePageChange = (page) => { + getTrackChairs(trackId, term, page, perPage, order, orderDir); }; - handleFilterByTrack = (ev) => { - const { value } = ev.target; - const { term, page, order, orderDir, perPage } = this.props; - this.props.getTrackChairs(value, term, page, perPage, order, orderDir); + const handlePerPageChange = (newPerPage) => { + getTrackChairs( + trackId, + term, + DEFAULT_CURRENT_PAGE, + newPerPage, + order, + orderDir + ); }; - handlePageChange = (page) => { - const { trackId, term, order, orderDir, perPage } = this.props; - this.props.getTrackChairs(trackId, term, page, perPage, order, orderDir); + const handleNewTrackChair = () => { + setDialogEntity({}); }; - handleSort = (index, key, dir, func) => { - const { trackId, term, page, perPage } = this.props; - - this.props.getTrackChairs(trackId, term, page, perPage, key, dir); + const handleEdit = (trackChair) => { + setDialogEntity({ + id: trackChair.id, + member: trackChair.member, + originalMemberId: trackChair.member.id, + trackIds: trackChair.categories.map((c) => c.id) + }); }; - handleSearch = (term) => { - const { trackId, order, orderDir, page, perPage } = this.props; - this.props.getTrackChairs(trackId, term, page, perPage, order, orderDir); + const handleDelete = (trackChairId) => { + deleteTrackChair(trackChairId); }; - handleDelete = (trackChairId) => { - const { deleteTrackChair, trackChairs } = this.props; - const trackChair = trackChairs.find((s) => s.id === trackChairId); - - Swal.fire({ - title: T.translate("general.are_you_sure"), - text: `${T.translate("track_chairs.delete_warning")} ${trackChair.name}`, - type: "warning", - showCancelButton: true, - confirmButtonColor: "#DD6B55", - confirmButtonText: T.translate("general.yes_remove") - }).then(function (result) { - if (result.value) { - deleteTrackChair(trackChairId); - } - }); + const handleSave = ({ id, member, trackIds }) => { + const newMember = dialogEntity?.originalMemberId !== member?.value; + const action = + !id || newMember + ? addTrackChair({ id: member.value }, trackIds) + : saveTrackChair(id, trackIds); + action.then(() => setDialogEntity(null)); }; - handleExport = () => { - const { trackChairs } = this.props; - this.props.exportTrackChairs(trackChairs); + const handleClose = () => { + setDialogEntity(null); }; - render() { - const { - currentSummit, - trackChairs, - lastPage, - currentPage, - term, - order, - orderDir, - totalTrackChairs - } = this.props; - const { showForm, member, trackIds } = this.state; - const disabledSave = trackIds.length === 0 || !member; + const columns = [ + { + columnKey: "name", + header: T.translate("track_chairs.name"), + sortable: true + }, + { columnKey: "trackNames", header: T.translate("track_chairs.track") } + ]; - const columns = [ - { - columnKey: "name", - value: T.translate("track_chairs.name"), - sortable: true - }, - { columnKey: "trackNames", value: T.translate("track_chairs.track") } - ]; + const table_options = { sortCol: order, sortDir: orderDir }; - const table_options = { - sortCol: order, - sortDir: orderDir, - actions: { - edit: { onClick: this.handleEdit }, - delete: { onClick: this.handleDelete } - } - }; + const tracks_ddl = chairTracks.map((t) => ({ label: t.name, value: t.id })); - const tracks_ddl = currentSummit.tracks - .filter((t) => t.chair_visible) - .map((t) => ({ label: t.name, value: t.id })); + const buttonSx = { + height: "36px", + padding: "6px 16px", + fontSize: "1.4rem", + lineHeight: "2.4rem", + letterSpacing: "0.4px" + }; - if (!currentSummit.id) return
; + if (!currentSummit?.id) return
; - return ( - <> -
-

- {" "} - {T.translate("track_chairs.list")} ({totalTrackChairs}) -

-
-
- -
-
- -
-
- - + + + + - {showForm && ( -
-
- { - return member.hasOwnProperty("email") - ? `${member.first_name} ${member.last_name} (${member.email})` - : `${member.first_name} ${member.last_name} (${member.id})`; - }} - placeholder={T.translate( - "track_chairs.placeholders.select_track_chair" - )} - /> -
-
- -
-
- - -
-
- )} + {trackChairs.length === 0 ? ( +
{T.translate("track_chairs.no_items")}
+ ) : ( + + `${T.translate("track_chairs.delete_warning")} ${name}` + } + /> + )} - {trackChairs.length === 0 ? ( -
- {T.translate("track_chairs.no_items")} -
- ) : ( -
- - - - )} - - - ); - } -} + {dialogEntity !== null && ( + + )} + + ); +}; const mapStateToProps = ({ currentSummitState, trackChairListState }) => ({ currentSummit: currentSummitState.currentSummit, diff --git a/src/reducers/track_chairs/track-chair-list-reducer.js b/src/reducers/track_chairs/track-chair-list-reducer.js index b3f0bdede..0c21d31e4 100644 --- a/src/reducers/track_chairs/track-chair-list-reducer.js +++ b/src/reducers/track_chairs/track-chair-list-reducer.js @@ -9,7 +9,9 @@ * 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. - **/ + * */ + +import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; import { RECEIVE_TRACK_CHAIRS, @@ -18,9 +20,7 @@ import { TRACK_CHAIR_DELETED, TRACK_CHAIR_UPDATED } from "../../actions/track-chair-actions"; - import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; -import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; const DEFAULT_STATE = { trackChairs: [], @@ -42,9 +42,9 @@ const trackChairListReducer = (state = DEFAULT_STATE, action) => { return DEFAULT_STATE; } case REQUEST_TRACK_CHAIRS: { - const { order, orderDir, term, trackId } = payload; + const { order, orderDir, term, trackId, perPage } = payload; - return { ...state, order, orderDir, term, trackId }; + return { ...state, order, orderDir, term, trackId, perPage }; } case RECEIVE_TRACK_CHAIRS: { const { total, last_page, current_page, data } = payload.response; @@ -57,7 +57,7 @@ const trackChairListReducer = (state = DEFAULT_STATE, action) => { return { ...state, - trackChairs: trackChairs, + trackChairs, currentPage: current_page, totalTrackChairs: total, lastPage: last_page