I T H

[MySchedule project] 11. 관리자 - 사용자관리 로직 mui x-data-grid와 mui modal 사용 본문

React + Node.js

[MySchedule project] 11. 관리자 - 사용자관리 로직 mui x-data-grid와 mui modal 사용

thdev 2024. 2. 7. 21:55
관리자용 페이지중에서 사용자 데이터를 관리하는 페이지를 만들려고 한다.
먼저 db에 저장되있는 사용자 데이터들을 조회해 오고, 조회해온 데이터를 수정, 삭제하는 기능과 사용자를 등록하는 기능까지 구현할 것이다.
사용 라이브러리는 mui/x-data grid 를 사용해서 테이블을 구현했고, row를 클릭할 경우 modal 이 뜨게 할 것이다.
modal 라이브러리는 mui/material의 modal을 사용했다.

 

Frontend

- 구현에 들어가기 앞서, 필요한 라이브러리( mui )를 먼저 다운로드 받아준다.

 

https://mui.com/material-ui/getting-started/installation/

 

Installation - Material UI

Install Material UI, the world's most popular React UI framework.

mui.com

 

- 설치

npm install @mui/x-data-grid
npm install @mui/material @emotion/react @emotion/styled

 

- import

import {
  DataGrid,
  GridColDef,
  GridRowSelectionModel,
  GridRowsProp,
} from "@mui/x-data-grid";

 

[ src / types / userManagement.ts ]

 

- 타입 지정

export interface userManagementInput {
  textUserId: string;
  textUserName: string;
}

export interface userData {
  userId: string;
  userEmail: string;
  userName: string;
  userPhone: string;
  userImage: string;
  //userPassword: string;
  role: number;
}

export interface modalInsertData extends userData {
  userPassword: string;
  userPasswordCheck: string;
}

 

[ pages / UserManagement / index.tsx ]

 

1. 조회 

- db에 있는 사용자데이터들을 가져와서 mui grid 양식에 맞게 넣어서 출력하였다.

- 관리자로부터 입력받는 input은 이름과 아이디에 따라 db에서 다르게 조회 할 수 있도록 하였음.

 

2. 수정

- 관리자가 특정 row 클릭시에 해당되는 사용자 데이터를 가져올수 있게 하였고, row 클릭시에는 모달창에 뜨도록 구현함.

- 업데이트의 modal은 한 사용자의 데이터만 들어가므로, 따로 state로 상태를 관리해 ModalUpdatePage.tsx 컴포넌트로 해당 사용자의 데이터를 props로 보내주었음.

- ModalUpdatePage.tsx 에서 props로 받은 데이터는 화면에 출력되고, 관리자가 id(pk)를 제외한 나머지 요소들을 수정할수 있게 하였음.

 

3. 추가

- 추가버튼 클릭 이벤트시에 ModalInsertPage.tsx컴포넌트의 modal이 open 됨.

- 새로운 사용자 데이터를 등록하는 것이므로, validation check(유효성검사) 부분을 작성해야되고, id중복확인 부분도 작성해야됨.

- validation check가 정상적으로 통과되었다면 db에 사용자를 추가할수 있게 구현하였음.

- 리덕스의 stroe부분은 회원가입 이후의 로그인 로직부터 토큰 생성후 사용자 데이터를 가져오므로, dispatch를 통해 따로 하진 않고, 이번에는 바로 axios를 사용함.

 

4. 삭제

- mui grid에서 제공해주는 checkbox 클릭시에 사용자 데이터를 가져올수 있는 기능인 rowSelectionModal을 이용해서 사용자 데이터를 파라미터에 담아 백앤드에 보낼수 있음.

- rowSelectionModal 타입이 배열이므로, 여러개의 체크가 된 상태에서도 삭제가 가능하도록 구현함.

import React, { ChangeEvent, useEffect, useState } from "react";
import {
  DataGrid,
  GridColDef,
  GridRowSelectionModel,
  GridRowsProp,
} from "@mui/x-data-grid";
import axiosInstance from "../../utils/axios";
import {
  modalData,
  responseUserData,
  userManagementType,
} from "../../types/userManagementType";
import { toast } from "react-toastify";
import ModalUpdatePage from "./ModalUpdatePage";
import ModalInsertPage from "./ModalInsertPage";

const UserManagement = () => {
  const [textUserData, setTextUserdata] = useState<userManagementType>({
    textUserId: "",
    textUserName: "",
  });
  const [response, setResponse] = useState<responseUserData[]>([]); //백앤드에서 조회해온 데이터
  const [modalData, setModalData] = useState<modalData>(); //모달에 넣을 데이터
  const [modalInsertYn, setModalInsertYn] = useState<boolean>(false); 
  const [open, setOpen] = React.useState(false);
  const [insertOpen, setInsertOpen] = React.useState(false);
  const handleUpdateOpen = () => setOpen(true);
  const handleInsertOpen = () => setInsertOpen(true);

  //화면 랜더링시 데이터조회 함수 호출
  useEffect(() => {
    handleSelectUserData();
  }, []);

  //input onChange 함수
  const handleChangeInput = (e: ChangeEvent<HTMLInputElement>) => {
    e.preventDefault();
    const { name, value } = e.target;
    setTextUserdata((prevState) => ({
      ...prevState,
      [name]: value,
    }));
  };

  //데이터 조회
  const handleSelectUserData = async () => {
    const body = {
      ...textUserData,
    };
    try {
      const response = await axiosInstance.post(`/userManagement`, body);
      setResponse(response.data);
    } catch (error: any) {
      console.log(error);
    }
  };

  // console.log(response);

  // 데이터 맵핑 start
  // data 배열에 데이터 갯수만큼 push 해야 함
  let rows: GridRowsProp = [];
  rows = response.map((data) => {
    //console.log(data);

    return {
      id: data.userId,
      userId: data.userId,
      userName: data.userName,
      userEmail: data.userEmail,
      userPhone: data.userPhone,
      userImage: data.userImage,
    };
  });

  const columns: GridColDef[] = [
    { field: "userId", headerName: "아이디", width: 150 },
    { field: "userName", headerName: "이름", width: 150 },
    { field: "userEmail", headerName: "이메일", width: 150 },
    { field: "userPhone", headerName: "폰번호", width: 150 },
    { field: "userImage", headerName: "이미지파일이름", width: 600 },
  ];
  // 데이터 맵핑 end

  //데이터 삭제 start
  const [rowSelectionModel, setRowSelectionModel] =
    useState<GridRowSelectionModel>([]); // checkbox 선택 후 데이터삭제
  //console.log(rowSelectionModel);

  const deleteUserData = async (
    e: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    e.preventDefault();
    if (rowSelectionModel === null || rowSelectionModel.length <= 0) {
      toast.info("삭제할 데이터를 체크해주세요.");
      return;
    }

    const body = {
      ...rowSelectionModel,
    };
    // eslint-disable-next-line no-restricted-globals
    const dataDeleteYn = confirm("데이터를 삭제하시겠습니까?");
    if (dataDeleteYn === true) {
      try {
        const response = await axiosInstance.post(
          `/userManagement/userDataDelete`,
          body
        );
        console.log(response);
        if (response.data.deleteSuccess === true) {
          toast.success("사용자 데이터가 삭제되었습니다.");
          handleSelectUserData(); //데이터 재조회
        }
      } catch (error: any) {
        console.log(error);
      }
    }
  };
  //데이터삭제 end

  //데이터 추가 start
  const insertUserData = () => {
    setModalInsertYn(true);
    handleInsertOpen();
  };

  //데이터 추가 end

  //더블클릭 이밴트시 팝업창 openn / 한 행의 데이터 불러오는 함수
  const getSelectedRows = (object: any) => {
    // console.log(object);
    // console.log(object.row);
    // console.log(response);

    response.map((item, idx) => {
      if (item.userId === object.row.id) {
        // 그리드에 선택한 1개의 row를 찾아낼 수 있다.
        console.log(item);
        // 조건에 맞는 한행의 데이터만 저장
        setModalData(item);
        // 모달 open
        handleUpdateOpen();
      }
    });
  };

  return (
    <section>
      <div className="m-auto p-4 my-6 ">
        <div className="w-full">
          <div className="sm:text-right my-4">
            <input
              className="placeholder:text-sm text-md my-2 mx-2 sm:w-2/12 w-11/12 rounded-md border-[1px] border-gray-200  border-t-[3px] h-[45px] p-3 hover:border-t-[3px] hover:border-t-gray-400 focus:focus:outline-none"
              type="text"
              placeholder="아이디를 입력해주세요."
              id="textUserId"
              name="textUserId"
              onChange={handleChangeInput}
              value={textUserData.textUserId}
            />{" "}
            <input
              className="placeholder:text-sm my-2 mx-2 sm:w-2/12 w-11/12 rounded-md text-md border-[1px] border-gray-200  border-t-[3px] h-[45px] p-3 hover:border-t-[3px] hover:border-t-gray-400 focus:focus:outline-none"
              type="text"
              placeholder="성함을 입력해주세요."
              id="textUserName"
              name="textUserName"
              onChange={handleChangeInput}
              value={textUserData.textUserName}
            />{" "}
            <button
              onClick={handleSelectUserData}
              className="sm:w-2/12 w-3/12 bg-blue-500 h-[45px] mx-2 my-2 text-white sm:font-bold font-semibold sm:text-sm text-[12px] py-2 px-2 rounded-md hover:bg-blue-600"
            >
              조회
            </button>
            <button
              onClick={insertUserData}
              className="sm:w-2/12 w-3/12 bg-blue-500 h-[45px] mx-2 text-white sm:font-bold font-semibold sm:text-sm text-[12px] py-2 px-2 rounded-md hover:bg-blue-600"
            >
              추가
            </button>
            <button
              onClick={deleteUserData}
              className="sm:w-2/12 w-3/12 bg-blue-500 h-[45px] mx-2 text-white sm:font-bold font-semibold sm:text-sm text-[12px] py-2 px-2 rounded-md hover:bg-blue-600"
            >
              삭제
            </button>
          </div>
          <p className="text-sm my-3">
            - 한 행을 클릭 시 데이터를 수정 할 수 있습니다.
          </p>
        </div>
        <div>
          {" "}
          {/* grid 사용 */}
          <DataGrid
            rows={rows}
            columns={columns}
            onRowClick={getSelectedRows}
            checkboxSelection
            //onRowDoubleClick={getSelectedRows}
            onRowSelectionModelChange={(newRowSelectionModel) => {
              setRowSelectionModel(newRowSelectionModel);
            }}
            rowSelectionModel={rowSelectionModel}
          />
          {/* modal update 호출 */}
          {modalData && (
            <ModalUpdatePage
              open={open}
              setOpen={setOpen}
              modalData={modalData}
              handleSelectUserData={handleSelectUserData}
            />
          )}
          {/* modal insert 호출 */}
          {modalInsertYn && (
            <ModalInsertPage
              insertOpen={insertOpen}
              setInsertOpen={setInsertOpen}
              handleSelectUserData={handleSelectUserData}
            />
          )}
        </div>
      </div>
    </section>
  );
};

export default UserManagement;

 

[ pages / UserManagement / ModalUpdatePage.tsx ]

 

- modalData 는 관리자가 gird에서 한 행을 클릭시에 ModalUpdatePage.tsx컴포넌트의 props로 보내는 state값으로, 클릭된 한명의 사용자 데이터가 들어있다.

- useEffect를 사용해서 modalData의 값들이 바뀔 때마다(관리자가 다른 행을 클릭한 경우) myData로 새로운 값들이 들어오게 하였음.

- 들어온 값들은 modal 라이브러리를 통해 화면에 출력되게 하였고, 관리자는 출력된 데이터들을 수정할 수 있게 구현함.

import React, { ChangeEvent, useEffect, useState } from "react";
import { Backdrop, Box, Fade, Modal, Typography } from "@mui/material";
import { style } from "./ModalStyle";
import { modalData } from "../../types/userManagementType";
import axiosInstance from "../../utils/axios";
import { toast } from "react-toastify";

interface ownProps {
  open: boolean;
  setOpen: any;
  modalData: modalData;
  handleSelectUserData(): void;
}

const ModalUpdatePage = ({
  modalData,
  open,
  setOpen,
  handleSelectUserData,
}: ownProps) => {
  console.log("111111");
  console.log(modalData); //정상작동
  const [myData, setMyData] = useState<modalData>({
    userId: "",
    userName: "",
    role: 0,
    userEmail: "",
    userPhone: "",
    userImage: "",
  });

  useEffect(() => {
    setMyData({
      userId: modalData.userId,
      userName: modalData.userName,
      role: modalData.role,
      userEmail: modalData.userEmail,
      userPhone: modalData.userPhone,
      userImage: modalData.userImage,
    });
  }, [modalData]);

  console.log(myData);

  // const [open, setOpen] = React.useState(false);
  // const handleOpen = () => setOpen(true);
  const handleClose = () => setOpen(false);

  //공통 onChange함수
  const handleChange = (
    e: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLSelectElement>
  ) => {
    console.log("run....>");
    e.preventDefault();
    console.log(e.target);
    const { name, value } = e.target;
    setMyData((prevState: modalData) => ({
      ...prevState,
      [name]: value,
    }));
  };

  //데이터 업데이트
  const handleUpdate = async (
    e: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    e.preventDefault();
    const body = {
      ...myData,
    };
    try {
      const response = await axiosInstance.post(
        `/userManagement/userDataUpdate`,
        body
      );
      if (response.data.updateSuccess === true) {
        toast.success("사용자 데이터가 정상적으로 수정되었습니다.");
        handleClose();
        handleSelectUserData(); //데이터 재조회
      }
    } catch (error) {
      console.log(error);
    }
  };
  return (
    <div>
      {/* <Button onClick={handleOpen}>Open modal</Button> */}
      <Modal
        aria-labelledby="transition-modal-title"
        aria-describedby="transition-modal-description"
        open={open}
        onClose={handleClose}
        closeAfterTransition
        slots={{ backdrop: Backdrop }}
        slotProps={{
          backdrop: {
            timeout: 500,
          },
        }}
      >
        <Fade in={open}>
          <Box sx={style} className="rounded-md border-[1px] border-gray-300 ">
            <button onClick={handleClose} className="float-right text-md ">
              X
            </button>
            <Typography
              id="transition-modal-title"
              variant="h6"
              component="h2"
              className="text-center"
            >
              사용자관리
            </Typography>
            <div className="w-full my-3">
              <div>
                <label className="text-left">아이디</label>
              </div>
              <div>
                <input
                  className="w-full border-b-[1px] h-9 bg-gray-200 border-gray-300 outline-none "
                  type="text"
                  onChange={handleChange}
                  value={myData.userId}
                  name="userId"
                  readOnly
                />
              </div>
            </div>
            <div className="w-full my-3">
              <div>
                <label className="text-left">이름</label>
              </div>
              <div>
                <input
                  className="w-full border-b-[1px] h-9 border-gray-300 outline-none "
                  type="text"
                  onChange={handleChange}
                  value={myData.userName}
                  name="userName"
                />
              </div>
            </div>
            <div className="w-full my-3">
              <div>
                <label className="text-left">폰번호</label>
              </div>
              <div>
                <input
                  className="w-full border-b-[1px] h-9 border-gray-300 outline-none "
                  type="text"
                  onChange={handleChange}
                  value={myData.userPhone}
                  name="userPhone"
                />
              </div>
            </div>
            <div className="w-full my-3">
              <div>
                <label className="text-left">이메일</label>
              </div>
              <div>
                <input
                  className="w-full border-b-[1px] h-9 border-gray-300 outline-none "
                  type="text"
                  onChange={handleChange}
                  value={myData.userEmail}
                  name="userEmail"
                />
              </div>
            </div>
            <div className="w-full my-3">
              <div>
                <label className="text-left">권한 설정</label>
              </div>
              <div>
                <select
                  className="border-[1px] border-gray-300 rounded-md w-full h-7 my-2"
                  value={myData.role}
                  name="role"
                  onChange={handleChange}
                >
                  <option value="1">관리자</option>
                  <option value="0">사용자</option>
                </select>
              </div>
            </div>
            <div className="w-full my-3">
              <div>
                <button
                  onClick={handleUpdate}
                  className="bg-blue-600 w-full text-gray-50 rounded-md h-10 hover:bg-blue-700"
                >
                  UPDATE
                </button>
              </div>
            </div>
          </Box>
        </Fade>
      </Modal>
    </div>
  );
};

export default ModalUpdatePage;

 

[ pages / UserManagement / ModalInsertPage.tsx ]

 

- 사용자추가 버튼 클릭했을때 해당되는 컴포넌트로 이부분 역시 modal이 뜨게 하였다.

- 회원가입 로직과 비슷해서 useForm을 사용했고, 추가적으로 mui/material에서 제공해주는 modal라이브러리를 사용했다.

import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { Backdrop, Box, Fade, Modal, Typography } from "@mui/material";
import { style } from "./ModalStyle";
import { modalInsertData } from "../../types/userManagementType";
import axiosInstance from "../../utils/axios";
import { toast } from "react-toastify";
import { useForm } from "react-hook-form";

interface ownProps {
  insertOpen: boolean;
  setInsertOpen: any;
  handleSelectUserData(): void;
}

const ModalInsertPage = ({
  insertOpen,
  setInsertOpen,
  handleSelectUserData,
}: ownProps) => {
  const {
    register, //등록함수
    handleSubmit,
    formState: { errors },
    reset,
    watch,
  } = useForm<modalInsertData>({ mode: "onChange" });

  // const [open, setOpen] = React.useState(false);
  // const handleOpen = () => setOpen(true);
  const handleClose = () => setInsertOpen(false);

  const [userEmail, setUserEmail] = useState("");
  const [emailList, setEmailList] = useState<string[]>([]);
  const [idcheck, setIdCheck] = useState(false);
  const domainEmails = [
    "@naver.com",
    "@gmail.com",
    "@daum.net",
    "@nate.com",
    "@kakao.com",
  ];
  const emailForm =
    /^(([^<>()\[\].,;:\s@"]+(\.[^<>()\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;

  //이메일 자동완성 함수
  const emailHandleChange = (e: ChangeEvent<HTMLInputElement>) => {
    e.preventDefault();
    const userEmails = domainEmails.map((email) =>
      e.target.value.includes("@")
        ? e.target.value.split("@")[0] + email
        : e.target.value + email
    );
    setUserEmail(e.target.value);
    setEmailList(userEmails);
  };

  useEffect(() => {
    reset();
    setIdCheck(false);
    setUserEmail("");
  }, [insertOpen, reset]);

  // VALIDATION CHECK START
  // register등록함수와 함께 사용한다. {...register("userId", userId)}
  const userId = {
    required: "필수입력 요소입니다.",
    minLength: {
      value: 3,
      message: "최소 3글자 이상으로 입력해 주세요.",
    },
  };
  const userPassword = {
    required: "필수입력 요소입니다.",
    pattern: {
      value:
        /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{11,20}$/,
      message: "영문, 숫자, 특수문자 포함 11자 이상 20 자 이하로 입력해 주세요",
    },
  };
  const userName = {
    required: "필수입력 요소입니다.",
  };
  const userPhone = {
    required: "필수입력 요소입니다.",
    minLength: {
      value: 11,
      message: "숫자 11자리 이상으로 입력해 주세요.",
    },
  };

  //비밀번호 일치여부 체크 - watch사용해서 input의 userPwd의 값을 가져옴
  const passwordRef = useRef<string | null>(null);
  passwordRef.current = watch("userPassword");
  //VALIDATION CHECK END

  //아이디 체크
  const handleIdCheck = async (
    e: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    e.preventDefault();
    let id = watch("userId");
    if (id === "" || id === null) {
      toast.info("아이디를 입력해주세요.");
      return;
    }
    const body = {
      userId: id,
    };
    try {
      const idCheckValue = await axiosInstance.post(
        "/userManagement/idCheck",
        body
      );
      console.log(idCheckValue);
      if (idCheckValue.data !== "") {
        //값이 있으면
        toast.info("이미 등록된 아이디 입니다.");
      } else {
        toast.info("사용가능한 아이디 입니다.");
        setIdCheck(true);
      }
    } catch (error) {
      console.error(error);
    }
  };

  //데이터 추가
  const onSubmit = async ({
    userId,
    userPassword,
    userName,
    userPhone,
  }: modalInsertData) => {
    const body = {
      userId,
      userName,
      userPhone,
      userEmail,
      userPassword: userPassword,
      userImage: "",
    };
    if (idcheck) {
      try {
        await axiosInstance.post(`/userManagement/userDataInsert`, body);
        reset();
        setIdCheck(false);
        setUserEmail("");
        toast.success("사용자 데이터가 저장되었습니다.");
        handleClose(); //모달 닫기
        handleSelectUserData(); //데이터 재조회
      } catch (error) {
        console.log(error);
      }
    } else {
      toast.info("아이디 중복확인을 먼저 해주세요.");
    }
  };

  return (
    <div>
      {/* <Button onClick={handleOpen}>Open modal</Button> */}
      <Modal
        aria-labelledby="transition-modal-title"
        aria-describedby="transition-modal-description"
        open={insertOpen}
        onClose={handleClose}
        closeAfterTransition
        slots={{ backdrop: Backdrop }}
        slotProps={{
          backdrop: {
            timeout: 500,
          },
        }}
      >
        <Fade in={insertOpen}>
          <Box sx={style} className="rounded-md border-[1px] border-gray-300 ">
            <button onClick={handleClose} className="float-right text-md ">
              X
            </button>
            <Typography
              id="transition-modal-title"
              variant="h6"
              component="h2"
              className="text-center"
            >
              사용자 추가
            </Typography>
            <form onSubmit={handleSubmit(onSubmit)}>
              <div className="w-full my-3">
                <div>
                  <label className="text-left">아이디</label>
                </div>
                <div>
                  <input
                    className="w-8/12 mr-3 border-b-[1px] h-9 border-gray-300 outline-none "
                    type="text"
                    id="userId"
                    disabled={idcheck}
                    {...register("userId", userId)}
                  />
                  {idcheck ? (
                    <button disabled={idcheck}></button>
                  ) : (
                    <button
                      onClick={handleIdCheck}
                      className="float-right sm:w-18 w-3/12 m-auto bg-blue-500 h-[37px] text-white sm:font-bold font-semibold sm:text-sm text-xs px-2 rounded-md hover:bg-blue-600"
                    >
                      중복확인
                    </button>
                  )}
                  {errors?.userId && (
                    <div className="py-1">
                      <span className="text-red-500 pl-2 text-sm">
                        {errors.userId.message}
                      </span>
                    </div>
                  )}
                </div>
              </div>
              <div className="w-full my-3">
                <div>
                  <label className="text-left">비밀번호</label>
                </div>
                <div>
                  <input
                    className="w-full border-b-[1px] h-9 border-gray-300 outline-none "
                    type="password"
                    id="userPassword"
                    {...register("userPassword", userPassword)}
                  />
                  {errors?.userPassword && (
                    <div className="py-1">
                      <span className="text-red-500 pl-2 text-sm">
                        {errors.userPassword.message}
                      </span>
                    </div>
                  )}
                </div>
              </div>
              <div className="w-full my-3">
                <div>
                  <label className="text-left">비밀번호 확인</label>
                </div>
                <div>
                  <input
                    className="w-full border-b-[1px] h-9 border-gray-300 outline-none "
                    type="password"
                    id="userPasswordCheck"
                    minLength={11}
                    {...register("userPasswordCheck", {
                      required: "필수입력 요소입니다.",
                      validate: (value) => value === passwordRef.current,
                    })}
                  />
                  {errors?.userPasswordCheck?.type === "required" && (
                    <div className="py-1">
                      <span className="text-red-500 pl-2 text-sm">
                        {errors.userPasswordCheck.message}
                      </span>
                    </div>
                  )}
                  {errors?.userPasswordCheck?.type === "validate" && (
                    <div className="py-1">
                      <span className="text-red-500 pl-2 text-sm">
                        비밀번호가 맞지 않습니다. 한번더 확인해 주세요.
                      </span>
                    </div>
                  )}
                </div>
              </div>
              <div className="w-full my-3">
                <div>
                  <label className="text-left">사용자명</label>
                </div>
                <div>
                  <input
                    className="w-full border-b-[1px] h-9 border-gray-300 outline-none "
                    type="text"
                    id="userName"
                    {...register("userName", userName)}
                  />
                  {errors?.userName && (
                    <div className="py-1">
                      <span className="text-red-500 pl-2 text-sm">
                        {errors.userName.message}
                      </span>
                    </div>
                  )}
                </div>
              </div>
              <div className="w-full my-3">
                <div>
                  <label className="text-left">휴대폰번호</label>
                </div>
                <div>
                  <input
                    className="w-full border-b-[1px] h-9 border-gray-300 outline-none "
                    type="number"
                    id="userPhone"
                    {...register("userPhone", userPhone)}
                  />
                  {errors?.userPhone && (
                    <div className="py-1">
                      <span className="text-red-500 pl-2 text-sm">
                        {errors.userPhone.message}
                      </span>
                    </div>
                  )}
                </div>
              </div>
              <div className="w-full my-3">
                <div>
                  <label className="text-left">이메일</label>
                </div>
                <div>
                  <input
                    className="w-full border-b-[1px] h-9 border-gray-300 outline-none "
                    type="text"
                    list="email"
                    value={userEmail}
                    onChange={emailHandleChange}
                  />
                  <datalist id="email">
                    {emailList &&
                      emailList.map((email, idx) => (
                        <option value={email} key={idx} />
                      ))}
                  </datalist>
                  {userEmail && !emailForm.test(userEmail) && (
                    <p className="p-2 text-red-500 text-sm">
                      이메일 형식을 확인해주세요.
                    </p>
                  )}
                </div>
              </div>
              <div className="w-full my-3">
                <div>
                  <button
                    type="submit"
                    className="bg-blue-600 w-full text-gray-50 rounded-md h-10 hover:bg-blue-700"
                  >
                    추가
                  </button>
                </div>
              </div>
            </form>
          </Box>
        </Fade>
      </Modal>
    </div>
  );
};

export default ModalInsertPage;

 

[ pages / UserManagement / ModalStyle.tsx ]

export const style = {
  position: "absolute" as "absolute",
  top: "50%",
  left: "50%",
  transform: "translate(-50%, -50%)",
  width: 400,
  bgcolor: "background.paper",
  //border: "2px solid #000",
  //boxShadow: 24,
  p: 4,
};

 

Backend

[ src / index.js ]

 

- rouet 추가

//route start

app.use("/userManagement", require("./routes/userManagement"));

//route end

 

[ src / routes / userManagement.js ]

 

1. 조회

- 관리자가 입력한 사용자 아이디와 이름에 해당되는 데이터를 찾는 로직인데, 특정 데이터를 찾기 위해 $regex 를 사용했다.

- $regex는 mysql의 like연산자와 같은 기능으로 만약 이름으로 th를 써서 조회 버튼을 누를경우에 이름이 th가 들어있는 데이터들을 불러온다.

 

2. 업데이트

-  관리자로부터 수정된 데이터들을 받아와 업데이트하였음.

 

3. 추가

- id중복확인을 거쳐야됨. id중복확인은 입력받은 데이터가 db에 있는지 확인하는 것으로, db에 데이터가 있으면 중복되는 것이므로 데이터의 추가가 되지 않도록 구현함.

 

4. 삭제

- 전달받은 데이터가 mui grid에서 제공해주는 checkbox 기능으로 배열로 들어온다.

- 배열의 데이터가 여러개일수 있으므로, for문을 돌려서 req.body의 인덱스번호를 넣어 데이터를 한개씩 삭제하도록 구현했다.

const express = require("express");
const User = require("../models/User");
const router = express.Router();

//사용자관리 조회
router.post("/", async (req, res, next) => {
  console.log(req.body);
  let textUserId = req.body.textUserId;
  let textUserName = req.body.textUserName;
  let userData = {};
  try {
    //$regex -> mysql의 like연산자
    userData = await User.find({
      userId: { $regex: textUserId },
      userName: { $regex: textUserName },
    });

    console.log(userData);

    return res.status(200).send(userData);
  } catch (error) {
    next(error);
  }
});

//사용자 데이터 업데이트
router.post("/userDataUpdate", async (req, res, next) => {
  try {
    console.log(req.body);

    let commonData = {
      userName: req.body.userName,
      userPhone: req.body.userPhone,
      userEmail: req.body.userEmail,
      role: req.body.role,
    };

    console.log(commonData);

    const updateUser = await User.findOneAndUpdate(
      { userId: req.body.userId },
      commonData,
      { new: true }
    );
    console.log(updateUser);

    return res.json({
      updateSuccess: true,
    });
  } catch (error) {
    next(error);
  }
});

//사용자 데이터 추가 - id중복확인
router.post("/idCheck", async (req, res, next) => {
  let userId = req.body.userId;
  console.log(userId);
  try {
    const userIdCheck = await User.findOne({ userId: userId });
    console.log(userIdCheck);

    return res.status(200).send(userIdCheck);
  } catch (error) {
    next(error);
  }
});

//사용자 데이터 추가 - 데이터등록
router.post("/userDataInsert", async (req, res, next) => {
  console.log(req.body);
  try {
    const user = new User(req.body);
    await user.save();
    return res.sendStatus(200);
  } catch (error) {
    next(error);
  }
});

//사용자 데이터 삭제
router.post("/userDataDelete", async (req, res, next) => {
  console.log(req.body);

  try {
    for (let index in req.body) {
      console.log(index);
      console.log(req.body[index]);
      const delYn = await User.deleteOne({
        userId: req.body[index],
      });
      console.log(delYn);
    }

    return res.json({ deleteSuccess: true });
  } catch (error) {
    next(error);
  }
});

module.exports = router;

 

[ 결과화면 ]

- 사용자 데이터 조회

 

- 데이터 추가

 

- 데이터 수정

 

- 데이터 삭제