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 (
+
+ );
+};
+
+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})
-
-
-
-
-
-
-
-
-
-
-
+ {tracks_ddl.map((t) => (
+
+ ))}
+
+
+
+
+
+ }
+ sx={buttonSx}
+ >
+ {T.translate("track_chairs.add")}
+
+
+
+
- {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