diff --git a/src/actions/admin-access-actions.js b/src/actions/admin-access-actions.js index ec960e643..17ac4e785 100644 --- a/src/actions/admin-access-actions.js +++ b/src/actions/admin-access-actions.js @@ -95,7 +95,7 @@ export const getAdminAccesses = createAction(RECEIVE_ADMIN_ACCESSES), `${window.API_BASE_URL}/api/v1/summit-administrator-groups`, authErrorHandler, - { order, orderDir, term } + { order, orderDir, term, page, perPage } )(params)(dispatch).then(() => { dispatch(stopLoading()); }); @@ -129,7 +129,7 @@ export const resetAdminAccessForm = () => (dispatch) => { }; export const saveAdminAccess = - (entity, noAlert = false) => + (entity, redirectOnCreate = true) => async (dispatch) => { const accessToken = await getAccessTokenSafely(); @@ -139,7 +139,7 @@ export const saveAdminAccess = const params = { access_token: accessToken }; if (entity.id) { - putRequest( + return putRequest( createAction(UPDATE_ADMIN_ACCESS), createAction(ADMIN_ACCESS_UPDATED), `${window.API_BASE_URL}/api/v1/summit-administrator-groups/${entity.id}`, @@ -147,32 +147,34 @@ export const saveAdminAccess = authErrorHandler, entity )(params)(dispatch).then(() => { - if (!noAlert) - dispatch(showSuccessMessage(T.translate("admin_access.saved"))); - else dispatch(stopLoading()); + dispatch(showSuccessMessage(T.translate("admin_access.saved"))); }); - } else { - const successMessage = { - title: T.translate("general.done"), - html: T.translate("admin_access.created"), - type: "success" - }; - - postRequest( - createAction(UPDATE_ADMIN_ACCESS), - createAction(ADMIN_ACCESS_ADDED), - `${window.API_BASE_URL}/api/v1/summit-administrator-groups`, - normalizedEntity, - authErrorHandler, - entity - )(params)(dispatch).then((payload) => { + } + return postRequest( + createAction(UPDATE_ADMIN_ACCESS), + createAction(ADMIN_ACCESS_ADDED), + `${window.API_BASE_URL}/api/v1/summit-administrator-groups`, + normalizedEntity, + authErrorHandler, + entity + )(params)(dispatch).then((payload) => { + if (redirectOnCreate) { + const successMessage = { + title: T.translate("general.done"), + html: T.translate("admin_access.created"), + type: "success" + }; + dispatch( showMessage(successMessage, () => { history.push(`/app/admin-access/${payload.response.id}`); }) ); - }); - } + return; + } + + dispatch(showSuccessMessage(T.translate("admin_access.created"))); + }); }; export const deleteAdminAccess = (adminAccessId) => async (dispatch) => { diff --git a/src/components/forms/admin-access-form.js b/src/components/forms/admin-access-form.js index 6207d00d1..c0c908ed8 100644 --- a/src/components/forms/admin-access-form.js +++ b/src/components/forms/admin-access-form.js @@ -9,7 +9,7 @@ * 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 T from "i18n-react/dist/i18n-react"; @@ -17,6 +17,10 @@ import "awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css"; import Input from "openstack-uicore-foundation/lib/components/inputs/text-input" import MemberInput from "openstack-uicore-foundation/lib/components/inputs/member-input" import SummitInput from "openstack-uicore-foundation/lib/components/inputs/summit-input"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Grid2 from "@mui/material/Grid2"; +import Typography from "@mui/material/Typography"; import { scrollToError, hasErrors, @@ -37,7 +41,7 @@ class AdminAccessForm extends React.Component { this.handleSubmit = this.handleSubmit.bind(this); } - componentDidUpdate(prevProps, prevState, snapshot) { + componentDidUpdate(prevProps) { const state = {}; scrollToError(this.props.errors); @@ -63,7 +67,7 @@ class AdminAccessForm extends React.Component { errors[id] = ""; entity[id] = value; - this.setState({ entity: entity, errors: errors }); + this.setState({ entity, errors }); } handleSubmit(ev) { @@ -77,11 +81,17 @@ class AdminAccessForm extends React.Component { const { entity, errors } = this.state; return ( -
+ -
-
- + + + + {T.translate("admin_access.title")} * + -
-
- + + + + {T.translate("admin_access.members")} * + { - return member.hasOwnProperty("email") - ? `${member.first_name} ${member.last_name} (${member.email})` - : `${member.first_name} ${member.last_name} (${member.id})`; - }} + getOptionLabel={(member) => + `${member.first_name} ${member.last_name} (${ + member.hasOwnProperty("email") ? member.email : member.id + })` + } onChange={this.handleChange} - multi={true} + multi /> -
-
- + + + + {T.translate("admin_access.summits")} * + -
-
-
-
- + +
-
- + > + {T.translate("general.save")} + + + +
); } } diff --git a/src/layouts/admin-access-layout.js b/src/layouts/admin-access-layout.js index 738e36794..8e61a069e 100644 --- a/src/layouts/admin-access-layout.js +++ b/src/layouts/admin-access-layout.js @@ -9,52 +9,41 @@ * 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 { Switch, Route, Redirect } from "react-router-dom"; import T from "i18n-react/dist/i18n-react"; -import { connect } from "react-redux"; import { Breadcrumb } from "react-breadcrumbs"; import Restrict from "../routes/restrict"; import AdminAccessListPage from "../pages/admin_access/admin-access-list-page"; -import EditAdminAccessPage from "../pages/admin_access/edit-admin-access-page"; -class AdminAccessLayout extends React.Component { - render() { - const { match } = this.props; +const AdminAccessLayout = ({ match }) => ( +
+ - return ( -
- + + + + + + +
+); - - - - - - -
- ); - } -} - -export default Restrict(connect(null, {})(AdminAccessLayout), "admin-access"); +export default Restrict(AdminAccessLayout, "admin-access"); diff --git a/src/pages/admin_access/__tests__/admin-access-list-page.test.js b/src/pages/admin_access/__tests__/admin-access-list-page.test.js new file mode 100644 index 000000000..ddabdf40b --- /dev/null +++ b/src/pages/admin_access/__tests__/admin-access-list-page.test.js @@ -0,0 +1,145 @@ +import React from "react"; +import { Provider } from "react-redux"; +import { MemoryRouter, Route } from "react-router-dom"; +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import configureStore from "redux-mock-store"; +import thunk from "redux-thunk"; +import AdminAccessListPage from "../admin-access-list-page"; + +const mockGetAdminAccesses = jest.fn(() => () => Promise.resolve()); +const mockDeleteAdminAccess = jest.fn(() => () => Promise.resolve()); +const mockGetAdminAccess = jest.fn(() => () => Promise.resolve()); +const mockResetAdminAccessForm = jest.fn(() => () => Promise.resolve()); +const mockSaveAdminAccess = jest.fn(() => () => Promise.resolve()); + +jest.mock("../../../actions/admin-access-actions", () => ({ + getAdminAccesses: (...args) => mockGetAdminAccesses(...args), + deleteAdminAccess: (...args) => mockDeleteAdminAccess(...args), + getAdminAccess: (...args) => mockGetAdminAccess(...args), + resetAdminAccessForm: (...args) => mockResetAdminAccessForm(...args), + saveAdminAccess: (...args) => mockSaveAdminAccess(...args) +})); + +jest.mock("openstack-uicore-foundation/lib/components/mui/table", () => { + const MockMuiTable = ({ data = [], onEdit, onDelete, onPerPageChange }) => ( +
+ + {data.map((row) => ( +
+ {row.title} + + +
+ ))} +
+ ); + + return MockMuiTable; +}); + +jest.mock("../../../components/forms/admin-access-form", () => { + const MockAdminAccessForm = ({ onSubmit }) => ( +
+ +
+ ); + + return MockAdminAccessForm; +}); + +jest.mock("sweetalert2", () => ({ + fire: jest.fn(() => Promise.resolve({ value: true })) +})); + +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); + +const baseListState = { + admin_accesses: [ + { id: 1, title: "Group A", members: "John Doe", summits: "Summit One" } + ], + term: "", + order: "id", + orderDir: 1, + currentPage: 1, + lastPage: 1, + perPage: 10 +}; + +const baseFormState = { + entity: { id: 0, title: "", members: [], summits: [] }, + errors: {} +}; + +const renderPage = (path = "/app/admin-access", formState = baseFormState) => { + const store = mockStore({ + adminAccessListState: baseListState, + adminAccessState: formState + }); + + render( + + + + + + ); + + return store; +}; + +describe("AdminAccessListPage MUI migration", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("renders grid and opens popup for new item", async () => { + renderPage("/app/admin-access"); + + expect(screen.getByTestId("mui-table")).toBeInTheDocument(); + expect(screen.queryByTestId("admin-access-form")).not.toBeInTheDocument(); + + fireEvent.click(screen.getByRole("button", { name: /create|add/i })); + + await waitFor(() => { + expect(screen.getByTestId("admin-access-form")).toBeInTheDocument(); + }); + }); + + it("opens popup when route has an access id", async () => { + const formState = { + entity: { id: 1, title: "Group A", members: [], summits: [] }, + errors: {} + }; + + renderPage("/app/admin-access/1", formState); + + await waitFor(() => { + expect(screen.getByTestId("admin-access-form")).toBeInTheDocument(); + }); + }); + + it("requests data with selected rows per page", async () => { + renderPage("/app/admin-access"); + + fireEvent.click(screen.getByRole("button", { name: "per-page-25" })); + + await waitFor(() => { + expect(mockGetAdminAccesses).toHaveBeenCalledWith("", 1, 25, "id", 1); + }); + }); +}); diff --git a/src/pages/admin_access/admin-access-list-page.js b/src/pages/admin_access/admin-access-list-page.js index d4c943e15..2b5b21739 100644 --- a/src/pages/admin_access/admin-access-list-page.js +++ b/src/pages/admin_access/admin-access-list-page.js @@ -11,167 +11,255 @@ * limitations under the License. * */ -import React from "react"; +import React, { useEffect, useMemo, 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 FreeTextSearch from "openstack-uicore-foundation/lib/components/free-text-search" -import Table from "openstack-uicore-foundation/lib/components/table"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Dialog from "@mui/material/Dialog"; +import DialogContent from "@mui/material/DialogContent"; +import DialogTitle from "@mui/material/DialogTitle"; +import Divider from "@mui/material/Divider"; +import Grid2 from "@mui/material/Grid2"; +import AddIcon from "@mui/icons-material/Add"; +import MuiTable from "openstack-uicore-foundation/lib/components/mui/table"; +import MuiSearchInput from "openstack-uicore-foundation/lib/components/mui/search-input"; import { getSummitById } from "../../actions/summit-actions"; +import AdminAccessForm from "../../components/forms/admin-access-form"; import { getAdminAccesses, - deleteAdminAccess + deleteAdminAccess, + getAdminAccess, + resetAdminAccessForm, + saveAdminAccess } from "../../actions/admin-access-actions"; +import { DEFAULT_CURRENT_PAGE } from "../../utils/constants"; -class AdminAccessListPage extends React.Component { - constructor(props) { - super(props); +const AdminAccessListPage = ({ + admin_accesses, + totalAdminAccesses, + currentPage, + perPage, + term, + order, + orderDir, + entity, + errors, + match, + history, + getAdminAccesses, + deleteAdminAccess, + getAdminAccess, + resetAdminAccessForm, + saveAdminAccess +}) => { + const [searchTerm, setSearchTerm] = useState(term || ""); + const [open, setOpen] = useState(false); + + useEffect(() => { + getAdminAccesses(term, DEFAULT_CURRENT_PAGE, perPage, order, orderDir); + }, [getAdminAccesses]); - this.handleEdit = this.handleEdit.bind(this); - this.handlePageChange = this.handlePageChange.bind(this); - this.handleSort = this.handleSort.bind(this); - this.handleSearch = this.handleSearch.bind(this); - this.handleNewAdminAccess = this.handleNewAdminAccess.bind(this); - this.handleDeleteAdminAccess = this.handleDeleteAdminAccess.bind(this); + useEffect(() => { + const { access_id: accessId } = match.params; + const isNew = /\/new$/.test(history.location.pathname); - this.state = {}; - } + if (isNew) { + resetAdminAccessForm(); + setOpen(true); + return; + } - componentDidMount() { - this.props.getAdminAccesses(); - } + if (accessId) { + getAdminAccess(accessId).then(() => setOpen(true)); + return; + } - handleEdit(admin_access_id) { - const { history } = this.props; - history.push(`/app/admin-access/${admin_access_id}`); - } + setOpen(false); + }, [match.params.access_id, history.location.pathname]); - handlePageChange(page) { - const { term, order, orderDir, perPage } = this.props; - this.props.getAdminAccesses(term, page, perPage, order, orderDir); - } + const handlePageChange = (page) => { + getAdminAccesses(term, page, perPage, order, orderDir); + }; - handleSort(index, key, dir) { - const { term, page, perPage } = this.props; - this.props.getAdminAccesses(term, page, perPage, key, dir); - } + const handlePerPageChange = (newPerPage) => { + getAdminAccesses(term, DEFAULT_CURRENT_PAGE, newPerPage, order, orderDir); + }; - handleSearch(term) { - const { order, orderDir, page, perPage } = this.props; - this.props.getAdminAccesses(term, page, perPage, order, orderDir); - } + const handleSort = (key, dir) => { + getAdminAccesses(term, currentPage, perPage, key, dir); + }; - handleNewAdminAccess(ev) { - const { history } = this.props; - ev.preventDefault(); + const handleSearch = (value) => { + setSearchTerm(value); + getAdminAccesses(value, DEFAULT_CURRENT_PAGE, perPage, order, orderDir); + }; + const handleNewAdminAccess = () => { history.push("/app/admin-access/new"); - } - - handleDeleteAdminAccess(accessId) { - const { deleteAdminAccess, admin_accesses } = this.props; - const admin_access = admin_accesses.find((t) => t.id === accessId); - - Swal.fire({ - title: T.translate("general.are_you_sure"), - text: `${T.translate("admin_access.delete_warning")} ${ - admin_access.title - }`, - type: "warning", - showCancelButton: true, - confirmButtonColor: "#DD6B55", - confirmButtonText: T.translate("general.yes_delete") - }).then((result) => { - if (result.value) { - deleteAdminAccess(accessId); - } + }; + + const handleEdit = (row) => { + history.push(`/app/admin-access/${row.id}`); + }; + + const handleDeleteAdminAccess = (rowOrId) => { + const accessId = typeof rowOrId === "object" ? rowOrId?.id : rowOrId; + + if (!accessId) return; + + const nextPage = + admin_accesses.length === 1 && currentPage > 1 + ? currentPage - 1 + : currentPage; + + deleteAdminAccess(accessId).then(() => { + getAdminAccesses(term, nextPage, perPage, order, orderDir); }); - } + }; - render() { - const { admin_accesses, lastPage, currentPage, term, order, orderDir } = - this.props; + const closeDialog = () => { + resetAdminAccessForm(); + setOpen(false); + history.push("/app/admin-access"); + }; + + const handleSave = (adminAccessEntity) => { + saveAdminAccess(adminAccessEntity, false).then(() => { + getAdminAccesses(term, currentPage, perPage, order, orderDir); + closeDialog(); + }); + }; - const columns = [ - { columnKey: "id", value: T.translate("general.id"), sortable: true }, + const columns = useMemo( + () => [ + { columnKey: "id", header: T.translate("general.id"), sortable: true }, { columnKey: "title", - value: T.translate("admin_access.title"), + header: T.translate("admin_access.title"), sortable: true }, - { columnKey: "summits", value: T.translate("admin_access.summits") }, - { columnKey: "members", value: T.translate("admin_access.members") } - ]; - - const table_options = { - sortCol: order, - sortDir: orderDir, - actions: { - edit: { onClick: this.handleEdit }, - delete: { onClick: this.handleDeleteAdminAccess } - } - }; - - return ( -
-

{T.translate("admin_access.admin_access_list")}

-
-
- -
-
- -
-
- - {admin_accesses.length === 0 && ( -
{T.translate("admin_access.no_results")}
- )} - - {admin_accesses.length > 0 && ( -
- - +

{T.translate("admin_access.admin_access_list")}

+ + + + {totalItems} {T.translate("general.items")} + + + + + + + + + {admin_accesses.length === 0 && ( +
{T.translate("admin_access.no_results")}
+ )} + + {admin_accesses.length > 0 && ( + adminAccess?.title ?? adminAccess?.id} + deleteDialogBody={(groupName) => + `${T.translate("admin_access.delete_warning")} "${groupName}" ?` + } + confirmButtonColor="error" + onPageChange={handlePageChange} + onPerPageChange={handlePerPageChange} + onSort={handleSort} + onEdit={handleEdit} + onDelete={handleDeleteAdminAccess} + /> + )} + + {open && ( + + + {entity.id + ? T.translate("general.edit") + : T.translate("general.add")}{" "} + {T.translate("admin_access.admin_access")} + + + + - - )} - - ); - } -} - -const mapStateToProps = ({ adminAccessListState }) => ({ - ...adminAccessListState + + + )} + + ); +}; + +const mapStateToProps = ({ adminAccessListState, adminAccessState }) => ({ + ...adminAccessListState, + entity: adminAccessState.entity, + errors: adminAccessState.errors }); export default connect(mapStateToProps, { getSummitById, getAdminAccesses, - deleteAdminAccess + deleteAdminAccess, + getAdminAccess, + resetAdminAccessForm, + saveAdminAccess })(AdminAccessListPage); diff --git a/src/pages/admin_access/edit-admin-access-page.js b/src/pages/admin_access/edit-admin-access-page.js deleted file mode 100644 index 12fe488b6..000000000 --- a/src/pages/admin_access/edit-admin-access-page.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright 2020 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 { connect } from "react-redux"; -import T from "i18n-react/dist/i18n-react"; -import { Breadcrumb } from "react-breadcrumbs"; -import AdminAccessForm from "../../components/forms/admin-access-form"; -import { getSummitById } from "../../actions/summit-actions"; -import { - getAdminAccess, - resetAdminAccessForm, - saveAdminAccess -} from "../../actions/admin-access-actions"; - -//import '../../styles/edit-admin-access-page.less'; - -class EditAdminAccessPage extends React.Component { - constructor(props) { - super(props); - - this.state = {}; - - const accessId = props.match.params.access_id; - - if (!accessId) { - props.resetAdminAccessForm(); - } else { - props.getAdminAccess(accessId); - } - } - - componentDidUpdate(prevProps, prevState, snapshot) { - const oldId = prevProps.match.params.access_id; - const newId = this.props.match.params.access_id; - - if (oldId !== newId) { - if (!newId) { - this.props.resetTemplateForm(); - } else { - this.props.getAdminAccess(newId); - } - } - } - - render() { - const { entity, errors, match } = this.props; - const title = entity.id - ? T.translate("general.edit") - : T.translate("general.add"); - const breadcrumb = entity.id ? entity.title : T.translate("general.new"); - - return ( -
- -

- {title} {T.translate("admin_access.admin_access")} -

-
- -
- ); - } -} - -const mapStateToProps = ({ adminAccessState }) => ({ - ...adminAccessState -}); - -export default connect(mapStateToProps, { - getSummitById, - getAdminAccess, - resetAdminAccessForm, - saveAdminAccess -})(EditAdminAccessPage); diff --git a/src/reducers/admin_access/__tests__/admin-access-list-reducer.test.js b/src/reducers/admin_access/__tests__/admin-access-list-reducer.test.js new file mode 100644 index 000000000..76c65bae0 --- /dev/null +++ b/src/reducers/admin_access/__tests__/admin-access-list-reducer.test.js @@ -0,0 +1,49 @@ +import adminAccessListReducer from "../admin-access-list-reducer"; +import { ADMIN_ACCESS_DELETED } from "../../../actions/admin-access-actions"; + +describe("adminAccessListReducer", () => { + test("decrements totalAdminAccesses when deleting an existing row", () => { + const initialState = { + admin_accesses: [ + { id: 10, title: "Group A" }, + { id: 11, title: "Group B" } + ], + totalAdminAccesses: 2, + term: "", + order: "id", + orderDir: 1, + currentPage: 1, + lastPage: 1, + perPage: 10 + }; + + const result = adminAccessListReducer(initialState, { + type: ADMIN_ACCESS_DELETED, + payload: { adminAccessId: 10 } + }); + + expect(result.admin_accesses).toStrictEqual([{ id: 11, title: "Group B" }]); + expect(result.totalAdminAccesses).toBe(1); + }); + + test("keeps totalAdminAccesses when deleted id does not exist", () => { + const initialState = { + admin_accesses: [{ id: 10, title: "Group A" }], + totalAdminAccesses: 1, + term: "", + order: "id", + orderDir: 1, + currentPage: 1, + lastPage: 1, + perPage: 10 + }; + + const result = adminAccessListReducer(initialState, { + type: ADMIN_ACCESS_DELETED, + payload: { adminAccessId: 999 } + }); + + expect(result.admin_accesses).toStrictEqual([{ id: 10, title: "Group A" }]); + expect(result.totalAdminAccesses).toBe(1); + }); +}); diff --git a/src/reducers/admin_access/admin-access-list-reducer.js b/src/reducers/admin_access/admin-access-list-reducer.js index cbf46006f..decacd9d5 100644 --- a/src/reducers/admin_access/admin-access-list-reducer.js +++ b/src/reducers/admin_access/admin-access-list-reducer.js @@ -9,18 +9,19 @@ * 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_ADMIN_ACCESSES, REQUEST_ADMIN_ACCESSES, ADMIN_ACCESS_DELETED } from "../../actions/admin-access-actions"; -import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; const DEFAULT_STATE = { admin_accesses: [], + totalAdminAccesses: 0, term: null, order: "id", orderDir: 1, @@ -36,37 +37,50 @@ const adminAccessListReducer = (state = DEFAULT_STATE, action) => { return DEFAULT_STATE; } case REQUEST_ADMIN_ACCESSES: { - let { order, orderDir, term } = payload; + const { order, orderDir, term, page, perPage } = payload; - return { ...state, order, orderDir, term }; + return { + ...state, + order, + orderDir, + term, + currentPage: page || state.currentPage, + perPage: perPage || state.perPage + }; } case RECEIVE_ADMIN_ACCESSES: { - let { total, last_page, current_page } = payload.response; - let admin_accesses = payload.response.data.map((aa) => { - return { + const { total, last_page, current_page, per_page } = payload.response; + const admin_accesses = payload.response.data.map((aa) => ({ id: aa.id, title: aa.title, members: aa.members .map((m) => `${m.first_name} ${m.last_name}`) .join(", "), summits: aa.summits.map((s) => s.name).join(", ") - }; - }); + })); return { ...state, - admin_accesses: admin_accesses, + admin_accesses, + totalAdminAccesses: total || 0, currentPage: current_page, - lastPage: last_page + lastPage: last_page, + perPage: per_page || state.perPage }; } case ADMIN_ACCESS_DELETED: { - let { adminAccessId } = payload; + const { adminAccessId } = payload; + const filteredAdminAccesses = state.admin_accesses.filter( + (aa) => aa.id !== adminAccessId + ); + return { ...state, - admin_accesses: state.admin_accesses.filter( - (aa) => aa.id !== adminAccessId - ) + admin_accesses: filteredAdminAccesses, + totalAdminAccesses: + filteredAdminAccesses.length === state.admin_accesses.length + ? state.totalAdminAccesses + : Math.max(0, (state.totalAdminAccesses ?? 0) - 1) }; } default: