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/components/mui/formik-inputs/mui-formik-async-select.js b/src/components/mui/formik-inputs/mui-formik-async-select.js index 852417e1a..2293a3e16 100644 --- a/src/components/mui/formik-inputs/mui-formik-async-select.js +++ b/src/components/mui/formik-inputs/mui-formik-async-select.js @@ -18,7 +18,8 @@ const MuiFormikAsyncAutocomplete = ({ formatOption = (item) => ({ value: item.id.toString(), label: item.name }), formatSelectedValue = null, queryParams = [], - isMulti = false + isMulti = false, + ...rest }) => { const [field, meta, helpers] = useField(name); const [options, setOptions] = useState([]); @@ -120,6 +121,7 @@ const MuiFormikAsyncAutocomplete = ({ {option.label} )} + {...rest} /> ); }; 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/__tests__/track-chair-list-page.test.js b/src/pages/track_chairs/__tests__/track-chair-list-page.test.js new file mode 100644 index 000000000..9a9f68b57 --- /dev/null +++ b/src/pages/track_chairs/__tests__/track-chair-list-page.test.js @@ -0,0 +1,207 @@ +import React from "react"; +import { act, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { renderWithRedux } from "utils/test-utils"; +import { + saveTrackChair, + addTrackChair, + deleteTrackChair +} from "../../../actions/track-chair-actions"; +import TrackChairListPage from "../track-chair-list-page"; + +jest.mock("../../../actions/track-chair-actions", () => ({ + getTrackChairs: jest.fn(() => () => Promise.resolve()), + deleteTrackChair: jest.fn(() => () => Promise.resolve()), + saveTrackChair: jest.fn(() => () => Promise.resolve()), + addTrackChair: jest.fn(() => () => Promise.resolve()), + exportTrackChairs: jest.fn(() => () => Promise.resolve()) +})); + +jest.mock( + "openstack-uicore-foundation/lib/components/mui/table", + () => + function MockMuiTable({ data, onEdit, onDelete }) { + return ( +
+ {data.map((item) => ( +
+ + +
+ ))} +
+ ); + } +); + +jest.mock( + "openstack-uicore-foundation/lib/components/mui/search-input", + () => () => null +); + +// Capture the dialog's props so tests can call onSave/onClose with specific values +let capturedDialogProps = null; +jest.mock("../components/track-chair-dialog", () => ({ + __esModule: true, + default: (props) => { + capturedDialogProps = props; + return
; + } +})); + +const mockTracks = [ + { id: 1, name: "Track A", chair_visible: true }, + { id: 2, name: "Track B", chair_visible: true } +]; + +const mockTrackChair = { + id: 10, + name: "Jane Doe (jane@example.com)", + member: { + id: 42, + first_name: "Jane", + last_name: "Doe", + email: "jane@example.com" + }, + categories: [{ id: 1, name: "Track A" }], + trackIds: [1], + trackNames: "Track A" +}; + +const initialState = { + currentSummitState: { + currentSummit: { id: 1, name: "Test Summit", tracks: mockTracks } + }, + trackChairListState: { + trackChairs: [], + trackId: null, + term: "", + order: "id", + orderDir: 1, + currentPage: 1, + lastPage: 1, + perPage: 10, + totalTrackChairs: 0 + } +}; + +describe("TrackChairListPage", () => { + beforeEach(() => { + jest.clearAllMocks(); + capturedDialogProps = null; + }); + + const renderPage = (stateOverrides = {}) => + renderWithRedux(, { + initialState: { + ...initialState, + trackChairListState: { + ...initialState.trackChairListState, + ...stateOverrides + } + } + }); + + describe("dialog visibility", () => { + it("shows after clicking Add and closes when onClose is called", async () => { + renderPage(); + expect( + screen.queryByTestId("track-chair-dialog") + ).not.toBeInTheDocument(); + + await act(async () => { + await userEvent.click(screen.getByRole("button", { name: /add/i })); + }); + expect(screen.getByTestId("track-chair-dialog")).toBeInTheDocument(); + + await act(async () => { + capturedDialogProps.onClose(); + }); + expect( + screen.queryByTestId("track-chair-dialog") + ).not.toBeInTheDocument(); + }); + }); + + describe("handleEdit", () => { + it("passes the correct entity shape to the dialog", async () => { + renderPage({ trackChairs: [mockTrackChair] }); + + await act(async () => { + await userEvent.click(screen.getByTestId(`edit-${mockTrackChair.id}`)); + }); + + expect(capturedDialogProps.entity).toEqual({ + id: mockTrackChair.id, + member: mockTrackChair.member, + originalMemberId: mockTrackChair.member.id, + trackIds: [1] // mapped from categories + }); + }); + }); + + describe("handleDelete", () => { + it("calls deleteTrackChair with the row id", async () => { + renderPage({ trackChairs: [mockTrackChair] }); + + await act(async () => { + await userEvent.click( + screen.getByTestId(`delete-${mockTrackChair.id}`) + ); + }); + + expect(deleteTrackChair).toHaveBeenCalledWith(mockTrackChair.id); + }); + }); + + describe("handleSave", () => { + it("calls addTrackChair when saving a new chair (no id)", async () => { + renderPage(); + + await act(async () => { + await userEvent.click(screen.getByRole("button", { name: /add/i })); + }); + + await act(async () => { + await capturedDialogProps.onSave({ + id: 0, + member: { value: 99, label: "New Member" }, + trackIds: [1] + }); + }); + + expect(addTrackChair).toHaveBeenCalledWith({ id: 99 }, [1]); + expect(saveTrackChair).not.toHaveBeenCalled(); + }); + + it("calls saveTrackChair when only tracks change on an existing chair", async () => { + renderPage({ trackChairs: [mockTrackChair] }); + + await act(async () => { + await userEvent.click(screen.getByTestId(`edit-${mockTrackChair.id}`)); + }); + + // Same member (value: 42 === originalMemberId: 42), different tracks + await act(async () => { + await capturedDialogProps.onSave({ + id: mockTrackChair.id, + member: { value: 42 }, + trackIds: [1, 2] + }); + }); + + expect(saveTrackChair).toHaveBeenCalledWith(mockTrackChair.id, [1, 2]); + expect(addTrackChair).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/pages/track_chairs/components/__tests__/track-chair-dialog.test.js b/src/pages/track_chairs/components/__tests__/track-chair-dialog.test.js new file mode 100644 index 000000000..7f60e2911 --- /dev/null +++ b/src/pages/track_chairs/components/__tests__/track-chair-dialog.test.js @@ -0,0 +1,227 @@ +import React from "react"; +import { act, screen, render } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import TrackChairDialog from "../track-chair-dialog"; + +jest.mock("../../../../hooks/useScrollToError", () => jest.fn()); + +jest.mock("openstack-uicore-foundation/lib/utils/query-actions", () => ({ + queryMembers: jest.fn() +})); + +jest.mock( + "../../../../components/mui/formik-inputs/mui-formik-async-select", + () => + function MockMuiFormikAsyncSelect({ name, disabled }) { + const { useFormikContext } = require("formik"); + const { setFieldValue } = useFormikContext(); + return ( + + ); + } +); + +const mockTracks = [ + { id: 1, name: "Track A" }, + { id: 2, name: "Track B" } +]; + +describe("TrackChairDialog", () => { + const onSave = jest.fn(); + const onClose = jest.fn(); + + const defaultProps = { + entity: {}, + tracks: mockTracks, + onSave, + onClose + }; + + beforeEach(() => { + jest.clearAllMocks(); + onSave.mockResolvedValue(); + }); + + const renderDialog = (props = {}) => + render(); + + const selectMember = async () => { + await act(async () => { + await userEvent.click(screen.getByTestId("async-select-member")); + }); + }; + + const selectTrack = async (trackName = "Track A") => { + await act(async () => { + await userEvent.click(screen.getByRole("combobox")); + }); + await act(async () => { + await userEvent.click(screen.getByRole("option", { name: trackName })); + }); + // Close the dropdown before interacting with other elements + await act(async () => { + await userEvent.keyboard("{Escape}"); + }); + }; + + const clickSave = async () => { + await act(async () => { + await userEvent.click(screen.getByRole("button", { name: /save/i })); + }); + }; + + const setupPendingSave = () => { + let resolveSave; + let rejectSave; + const pendingPromise = new Promise((resolve, reject) => { + resolveSave = resolve; + rejectSave = reject; + }); + onSave.mockReturnValue(pendingPromise); + return { resolveSave, rejectSave, pendingPromise }; + }; + + describe("validation", () => { + it("blocks submit when member is empty", async () => { + renderDialog(); + await clickSave(); + expect(onSave).not.toHaveBeenCalled(); + }); + + it("blocks submit and shows error when trackIds is empty", async () => { + renderDialog(); + await selectMember(); + await clickSave(); + expect(onSave).not.toHaveBeenCalled(); + expect(screen.getByText("validation.required")).toBeInTheDocument(); + }); + }); + + describe("valid submit", () => { + it("calls onSave with { id, member, trackIds } and then onClose on valid submit", async () => { + renderDialog(); + await selectMember(); + await selectTrack("Track A"); + await clickSave(); + expect(onSave).toHaveBeenCalledWith({ + id: 0, + member: { value: 42, label: "John Doe (john@example.com)" }, + trackIds: [1] + }); + expect(onClose).toHaveBeenCalledTimes(1); + }); + + it("pre-fills entity values and submits member as { value, label }", async () => { + const entity = { + id: 5, + member: { + id: 10, + first_name: "Jane", + last_name: "Doe", + email: "jane@example.com" + }, + originalMemberId: 10, + trackIds: [2] + }; + renderDialog({ entity }); + await clickSave(); + expect(onSave).toHaveBeenCalledWith({ + id: 5, + member: { value: 10, label: "Jane Doe (jane@example.com)" }, + trackIds: [2] + }); + }); + + it("disables the member field when editing an existing chair", () => { + const entity = { + id: 5, + member: { + id: 10, + first_name: "Jane", + last_name: "Doe", + email: "jane@example.com" + }, + trackIds: [1] + }; + renderDialog({ entity }); + expect(screen.getByTestId("async-select-member")).toBeDisabled(); + }); + }); + + describe("isSaving behavior", () => { + it("disables save and close buttons while saving, re-enables after resolve", async () => { + const { resolveSave } = setupPendingSave(); + renderDialog(); + await selectMember(); + await selectTrack(); + await clickSave(); + + expect(screen.getByRole("button", { name: /save/i })).toBeDisabled(); + expect(screen.getByRole("button", { name: /close/i })).toBeDisabled(); + + await act(async () => { + resolveSave(); + await Promise.resolve(); + }); + + expect(screen.getByRole("button", { name: /save/i })).not.toBeDisabled(); + expect(screen.getByRole("button", { name: /close/i })).not.toBeDisabled(); + }); + + it("re-enables buttons after save rejects", async () => { + const { rejectSave, pendingPromise } = setupPendingSave(); + renderDialog(); + await selectMember(); + await selectTrack(); + await clickSave(); + + await act(async () => { + rejectSave(new Error("save failed")); + await pendingPromise.catch(() => {}); + }); + + expect(screen.getByRole("button", { name: /save/i })).not.toBeDisabled(); + expect(screen.getByRole("button", { name: /close/i })).not.toBeDisabled(); + }); + + it("does not call onClose when Escape is pressed during a save", async () => { + const { resolveSave } = setupPendingSave(); + renderDialog(); + await selectMember(); + await selectTrack(); + await clickSave(); + + // disableEscapeKeyDown={true} while isSaving — onClose must not fire + await act(async () => { + await userEvent.keyboard("{Escape}"); + }); + + expect(onClose).not.toHaveBeenCalled(); + + await act(async () => { + resolveSave(); + await Promise.resolve(); + }); + }); + + it("calls onClose when the close button is clicked and not saving", async () => { + renderDialog(); + await act(async () => { + await userEvent.click(screen.getByRole("button", { name: /close/i })); + }); + expect(onClose).toHaveBeenCalledTimes(1); + }); + }); +}); 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..e92582bf6 --- /dev/null +++ b/src/pages/track_chairs/components/track-chair-dialog.js @@ -0,0 +1,241 @@ +/** + * 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, { useState } 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 [isSaving, setIsSaving] = useState(false); + + 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) => { + if (isSaving) return; + setIsSaving(true); + onSave(values) + .then(() => onClose()) + .catch(() => {}) + .finally(() => setIsSaving(false)); + } + }); + + 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")}{" "} + * + + 0} + /> + + + + {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..2cce7da3a 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); + return action; }; - 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