[React](react-drag-list라이브러리) 순서변경

Hyoyoung Kim·2023년 4월 4일
0
post-thumbnail

😎react-drag-list라이브러리를 활용하여 순서변경리스트를 구현하기

https://www.npmjs.com/package/react-draggable-list

// react-drag-list모듈 설치하기
yarn add react-drag-list
npm install react-drag-list

react-drag-list 라이브러리를 사용하여 서버에서 받은 데이터를 브라우저에 띄었다.

순서변경 코드

import ReactDragList from 'react-drag-list';

export const AdimCgList = () => {
  const [itemList, setItemList] = useState<any[]>([]);


useEffect(() => {
    const getData = async () => {
        await axios({
          method: 'get',
          url: `${process.env.REACT_APP_API_URL}/admin/category/list?sort=sort,asc`,
        }).then((res) => {
          setItemList(res.data.content);
          setSortCate(res.data.content.length);
        });
    };
    getData();
  }, []);
  
   //변경된 데이터 리스트르 서버에 다시 보내는 순서변경 api
  const changeCateApi = async () => {
    await itemList.map((el: any, index: number) => {
      return axios({
        method: 'put',
        url: `${process.env.REACT_APP_API_URL}/admin/category/${el.id}`,
        headers: {
          Authorization: jwt,
        },
        data: {
          name: el.name,
          sort: index + 1,
        },
      });
    });

// 변경된 리스트를 다시 브라우저 상에 나오게 하는 api
    await axios({
      method: 'get',
      url: `${process.env.REACT_APP_API_URL}/admin/category/list?sort=sort,asc`,
      headers: {
        Authorization: jwt,
      },
    });
    }).then((res) => {
      setItemList(res.data.content);
      setSortCate(res.data.content.length);
    });
  };
  
  // 드래그해서 변경된 리스트를 브라우저상에 나타나게 만드는것
    const handleUpdate = (evt: any, updated: any) => {
    // console.log(evt); // tslint:disable-line
    // console.log(updated); // tslint:disable-line
    setItemList([...updated]);
  };
  
  // 브라우저 상에 보여지는 데이터 리스트
    const dragList = (record: any, index: any) => (
    // 여기서 record는 dataSource로 itemList이다.
    <S.DragList key={index}>
      <div>{record.sort}</div>
      <div>{record.name}</div>
      <div>
        <AiOutlineMenu />
      </div>
      <S.AdminCgBtn onClick={() => modalHandler(record)}>변경</S.AdminCgBtn>
      <S.AdminCgBtn2 onClick={() => deleteItemAlert(record.id, record.name, index)}>
        삭제
      </S.AdminCgBtn2>
    </S.DragList>
  );
  
    return (
      <ReactDragList
        dataSource={itemList}//렌더링할 데이터 레코드 배열
        rowKey='name'//렌더링할 행 키
        row={dragList}  //렌더링할 행 데이터
        handles={false} //드래그 핸들 표시
        className='simple-drag'
        rowClassName='simple-drag-row'
        onUpdate={handleUpdate} //정렬 목록이 변경될 때 호출됨
      />)}

전체 코드

import React from 'react';
import * as S from './style';
import ReactDragList from 'react-drag-list';
import { useState } from 'react';
import { AiOutlineMenu } from 'react-icons/ai';
import axios from 'axios';
import { useEffect } from 'react';
import Swal from 'sweetalert2';
import withReactContent from 'sweetalert2-react-content';
import '../../../css/alert.css';
import { Cookies } from 'react-cookie';
import { useNavigate } from 'react-router-dom';

const swal = withReactContent(Swal);

export const AdimCgList = () => {
  const [itemList, setItemList] = useState<any[]>([]);

  //등록api
  const [registerCate, setRegisterCate] = useState('' as any);
  const [sortCate, setSortCate] = useState(0);

  //수정api
  const [putCateName, setPutCateName] = useState('');

  const [selected, setSelected] = useState<any[]>([]);
  //리스트 모달창
  const [popupTogle, setPopupTogle] = useState(false);
  // 모달찰열렸을때 스코롤 안되게 하는것
  // const [styling, setStyling] = useState(null as any);

  //등록 모달창
  const [popupTogle1, setPopupTogle1] = useState(false);

  const navigate = useNavigate();

  const cookies = new Cookies();
  const jwt = cookies.get('accessToken');

  useEffect(() => {
    const getData = async () => {
      try {
        await axios({
          method: 'get',
          url: `${process.env.REACT_APP_API_URL}/admin/category/list?sort=sort,asc`,
          headers: {
            Authorization: jwt,
          },
        }).then((res) => {
          setItemList(res.data.content);
          setSortCate(res.data.content.length);
        });
      } catch (err: any) {
        navigate('/sign-in');
        cookies.remove('accessToken');
        cookies.remove('refreshToken');
        cookies.remove('loginUser');
      }
    };
    getData();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [jwt, navigate]);

  //등록알람

  const registerItemAlert = () => {
    if (itemList.length < 7) {
      swal
        .fire({
          heightAuto: false,
          icon: 'question',
          text: '카테고리명를 등록하시겠습니까?',
          confirmButtonText: '확인',
          confirmButtonColor: '#289951',
          showCancelButton: true,
          cancelButtonText: '취소',
          width: 400,
        })
        .then((result) => {
          if (result.isConfirmed) {
            if (cookies.get('refreshToken')) {
              registerApi();
              swal.fire({
                heightAuto: false,
                icon: 'success',
                text: '카테고리가 등록됐습니다..',
                confirmButtonText: '확인',
                confirmButtonColor: '#289951',
                width: 400,
              });
            } else {
              navigate('/sign-in');
              cookies.remove('accessToken');
              cookies.remove('refreshToken');
              cookies.remove('loginUser');
            }
          }
        });
    } else {
      swal.fire({
        heightAuto: false,
        icon: 'warning',
        text: '카테고리 갯수가 초과하였습니다.',
        confirmButtonText: '확인',
        confirmButtonColor: '#289951',
        width: 400,
      });
    }
  };

  // 등록 api
  const registerApi = async () => {
    await axios({
      method: 'post',
      url: `${process.env.REACT_APP_API_URL}/admin/category`,
      headers: {
        Authorization: jwt,
      },
      data: {
        name: registerCate,
        sort: sortCate + 1,
      },
    }).then((res) => {
      setPopupTogle1(!popupTogle1);
    });

    await axios({
      method: 'get',
      url: `${process.env.REACT_APP_API_URL}/admin/category/list?sort=sort,asc`,
      headers: {
        Authorization: jwt,
      },
    }).then((res) => {
      setItemList(res.data.content);
      setSortCate(res.data.content.length);
    });
  };

  //아이템 삭제 알림
  const deleteItemAlert = (id: number, name: string, idx: number) => {
    swal
      .fire({
        heightAuto: false,
        icon: 'question',
        text: `${name} 카테고리를 삭제하겠습니까? 삭제하면 복구가 불가능합니다.`,
        confirmButtonText: '확인',
        confirmButtonColor: '#289951',
        showCancelButton: true,
        cancelButtonText: '취소',
        width: 400,
      })
      .then((result) => {
        if (result.isConfirmed) {
          if (cookies.get('refreshToken')) {
            deleteApi(id, idx);
            swal.fire({
              heightAuto: false,
              icon: 'success',
              text: `${name} 카테고리가 삭제됐습니다.`,
              confirmButtonText: '확인',
              confirmButtonColor: '#289951',
              width: 400,
            });
          } else {
            navigate('/sign-in');
            cookies.remove('accessToken');
            cookies.remove('refreshToken');
            cookies.remove('loginUser');
          }
        }
      });
  };

  //삭제 api
  const deleteApi = async (id: number, idx: number) => {
    if (cookies.get('refreshToken')) {
      await axios({
        method: 'delete',
        url: `${process.env.REACT_APP_API_URL}/admin/category/${id}`,
        headers: {
          Authorization: jwt,
        },
      });
      await axios({
        method: 'get',
        url: `${process.env.REACT_APP_API_URL}/admin/category/list?sort=sort,asc`,
        headers: {
          Authorization: jwt,
        },
      }).then((res) => {
        setItemList(res.data.content);
        setSortCate(res.data.content.length);
      });
    } else {
      navigate('/sign-in');
      cookies.remove('accessToken');
      cookies.remove('refreshToken');
      cookies.remove('loginUser');
    }
  };

  //수정알람
  const putItemAlert = (id: number, sort: number, idx: number) => {
    swal
      .fire({
        heightAuto: false,
        icon: 'question',
        text: '카테고리명을 변경하시겠습니까?',
        confirmButtonText: '확인',
        confirmButtonColor: '#289951',
        showCancelButton: true,
        cancelButtonText: '취소',
        width: 400,
      })
      .then((result) => {
        if (result.isConfirmed) {
          if (cookies.get('refreshToken')) {
            putApi(id, sort);
            swal.fire({
              heightAuto: false,
              icon: 'success',
              text: '카테고리명이 변경되었습니다.',
              confirmButtonText: '확인',
              confirmButtonColor: '#289951',
              width: 400,
            });
          } else {
            navigate('/sign-in');
            cookies.remove('accessToken');
            cookies.remove('refreshToken');
            cookies.remove('loginUser');
          }
        }
      });
  };

  //수정 api
  const putApi = async (id: number, sort: number) => {
    await axios({
      method: 'put',
      url: `${process.env.REACT_APP_API_URL}/admin/category/${id}`,
      headers: {
        Authorization: jwt,
      },
      data: {
        name: putCateName,
        sort: sort,
      },
    }).then((res) => {
      setPopupTogle(!popupTogle);
    });

    await axios({
      method: 'get',
      url: `${process.env.REACT_APP_API_URL}/admin/category/list?sort=sort,asc`,
      headers: {
        Authorization: jwt,
      },
    }).then((res) => {
      setItemList(res.data.content);
      setSortCate(res.data.content.length);
    });
  };

  //순서변경 알림창
  const cateItemAlert = () => {
    swal
      .fire({
        heightAuto: false,
        icon: 'question',
        text: '카테고리 순서를 변경하시겠습니까? ',
        confirmButtonText: '확인',
        confirmButtonColor: '#289951',
        showCancelButton: true,
        cancelButtonText: '취소',
        width: 400,
      })
      .then((result) => {
        if (result.isConfirmed) {
          if (cookies.get('refreshToken')) {
            changeCateApi();
            swal.fire({
              heightAuto: false,
              icon: 'success',
              text: '카테고리 순서가 변경되었습니다.',
              confirmButtonText: '확인',
              confirmButtonColor: '#289951',
              width: 400,
            });
          } else {
            navigate('/sign-in');
            cookies.remove('accessToken');
            cookies.remove('refreshToken');
            cookies.remove('loginUser');
          }
        }
      });
  };

  //순서변경 api
  const changeCateApi = async () => {
    await itemList.map((el: any, index: number) => {
      return axios({
        method: 'put',
        url: `${process.env.REACT_APP_API_URL}/admin/category/${el.id}`,
        headers: {
          Authorization: jwt,
        },
        data: {
          name: el.name,
          sort: index + 1,
        },
      });
    });

    await axios({
      method: 'get',
      url: `${process.env.REACT_APP_API_URL}/admin/category/list?sort=sort,asc`,
      headers: {
        Authorization: jwt,
      },
    });

    await axios({
      method: 'get',
      url: `${process.env.REACT_APP_API_URL}/admin/category/list?sort=sort,asc`,
      headers: {
        Authorization: jwt,
      },
    }).then((res) => {
      setItemList(res.data.content);
      setSortCate(res.data.content.length);
    });
  };

  const handleUpdate = (evt: any, updated: any) => {
    // console.log(evt); // tslint:disable-line
    // console.log(updated); // tslint:disable-line
    setItemList([...updated]);
  };

  const modalHandler = (record: any) => {
    setSelected([record]);
    setPopupTogle(!popupTogle);
    setPutCateName('');
  };

  const modalHandler1 = () => {
    setPopupTogle1(!popupTogle1);
    setRegisterCate('');
  };

  const dragList = (record: any, index: any) => (
    <S.DragList key={index}>
      <div>{record.sort}</div>
      <div>{record.name}</div>
      <div>
        <AiOutlineMenu />
      </div>
      <S.AdminCgBtn onClick={() => modalHandler(record)}>변경</S.AdminCgBtn>
      <S.AdminCgBtn2 onClick={() => deleteItemAlert(record.id, record.name, index)}>
        삭제
      </S.AdminCgBtn2>
    </S.DragList>
  );

  return (
    <S.Wrapper>
      <div>
        <S.BigTitle>카테고리 관리</S.BigTitle>
      </div>

      <S.AdminCgtitle>
        <div>NO</div>
        <div>카테고리명</div>
        <div>카테고리 순서변경</div>
      </S.AdminCgtitle>
      <ReactDragList
        dataSource={itemList}
        rowKey='name'
        row={dragList}
        handles={false}
        className='simple-drag'
        rowClassName='simple-drag-row'
        onUpdate={handleUpdate}
      />

      {popupTogle && (
        <S.PopUpContainer onClick={modalHandler}>
          <S.PopUpBody onClick={(e: any) => e.stopPropagation()}>
            <div>
              <h2>카테고리수정</h2>
              {selected.map((al: any, index: number) => {
                return (
                  <div key={al.id}>
                    <S.ModalInput2
                      key={al.id}
                      value={putCateName || al.name}
                      onChange={(e: any) => {
                        setPutCateName(e.target.value);
                      }}
                    />
                    <S.PopUpHeader>
                      <S.AdminCgBtn4 onClick={modalHandler}>취소하기</S.AdminCgBtn4>
                      <S.AdminCgBtn5 onClick={() => putItemAlert(al.id, al.sort, index)}>
                        수정하기
                      </S.AdminCgBtn5>
                    </S.PopUpHeader>
                  </div>
                );
              })}
            </div>
          </S.PopUpBody>
        </S.PopUpContainer>
      )}
      <S.CgBtnBox>
        <S.AdminCgBtn3 onClick={() => cateItemAlert()}>순서변경</S.AdminCgBtn3>
        <S.AdminCgBtn onClick={modalHandler1}>등록</S.AdminCgBtn>
      </S.CgBtnBox>
      {popupTogle1 && (
        <S.PopUpContainer onClick={modalHandler1}>
          <S.PopUpBody onClick={(e: any) => e.stopPropagation()}>
            <div>
              <h2>카테고리 등록</h2>
              <S.ModalInput
                placeholder='카테고리명을 입력해주세요.'
                value={registerCate || ''}
                onChange={(e: any) => {
                  setRegisterCate(e.target.value);
                }}
              />
            </div>
            <S.PopUpHeader>
              <S.AdminCgBtn4 onClick={modalHandler1}>취소하기</S.AdminCgBtn4>
              <S.AdminCgBtn5 onClick={() => registerItemAlert()}>등록하기</S.AdminCgBtn5>
            </S.PopUpHeader>
          </S.PopUpBody>
        </S.PopUpContainer>
      )}
    </S.Wrapper>
  );
};

styled-compotent

import styled from 'styled-components';

export const Wrapper = styled.div`
  display: flex;
  width: 100%;
  height: 100%;
  flex-direction: column;
  align-items: center;
  position: relative;
`;

export const Container = styled.div``;

export const BigTitle = styled.div`
  font-size: 0.24rem;
  width: 14.4rem;
  font-weight: 700;
  margin-top: 0.8rem;
`;

export const DragList = styled.div`
  cursor: pointer;
  width: 14.4rem;
  display: flex;
  align-items: center;
  padding: 0.24rem 0.24rem 0.24rem 0.58rem;
  border-bottom: 0.01rem solid ${({ theme }) => theme.palette.lightgray};
  font-size: 0.24rem;
  div {
    &:nth-child(1) {
      width: 1.5rem;
    }
    &:nth-child(2) {
      width: 5rem;
      text-align: center;
    }
    &:nth-child(3) {
      width: 6rem;
      text-align: center;
    }
  }
`;

export const AdminCgBtn = styled.button`
  width: 0.6rem;
  height: 0.4rem;
  margin-left: 0.05rem;
  background-color: ${({ theme }) => theme.palette.green};
  color: white;
`;

export const AdminCgBtn2 = styled.button`
  width: 0.6rem;
  height: 0.4rem;
  margin-left: 0.05rem;
  border: 0.01rem solid ${({ theme }) => theme.palette.green};
  color: ${({ theme }) => theme.palette.green};
`;

export const AdminCgtitle = styled.div`
  // height: 100%;
  width: 14.4rem;
  margin-top: 0.24rem;
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  flex-wrap: wrap;
  background-color: ${({ theme }) => theme.palette.whitegreen};
  align-items: end;
  //   justify-content: space-between;
  padding: 0.24rem 0.5rem 0.24rem 0.54rem;
  align-items: center;
  border-top: 0.03rem solid ${({ theme }) => theme.palette.txtgray};
  font-size: 0.24rem;
  div {
    &:nth-child(1) {
      flex-grow: 2.1;
    }
    &:nth-child(2) {
      flex-grow: 2.7;
    }
    &:nth-child(3) {
      flex-grow: 2;
    }
    &:nth-child(4) {
      flex-grow: 3;
    }
  }
`;

export const CgBtnBox = styled.div`
  margin-top: 0.24rem;
  width: 14.4rem;
  text-align: right;
`;

export const AdminCgBtn3 = styled.button`
  width: 0.84rem;
  height: 0.4rem;
  border: 0.01rem solid ${({ theme }) => theme.palette.green};
  color: ${({ theme }) => theme.palette.green};
`;

// 모달창 css
export const PopUpContainer = styled.div`
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: rgb(0, 0, 0, 0.5);
  padding-top: 4rem;
  /* padding-left: 1%; */
`;

export const PopUpBody = styled.div`
  width: 8.7rem;
  height: 3.8rem;
  background-color: #fff;
  margin: auto;
  flex-direction: column;
  align-items: center;
  text-align: center;
  div {
    justify-content: center;
    h2 {
      padding-top: 0.48rem;
      font-size: 0.24rem;
    }
  }
`;

export const PopUpHeader = styled.div`
  width: 100%;
`;

export const AdminCgBtn4 = styled.button`
  width: 2.84rem;
  height: 0.56rem;
  margin-left: 0.05rem;
  font-size: 0.2rem;
  border-radius: 0.5rem;
  border: 0.01rem solid ${({ theme }) => theme.palette.green};
  color: ${({ theme }) => theme.palette.green};
`;

export const AdminCgBtn5 = styled.button`
  width: 2.84rem;
  border-radius: 0.5rem;
  font-size: 0.2rem;
  height: 0.56rem;
  margin-left: 0.05rem;
  background-color: ${({ theme }) => theme.palette.green};
  color: white;
`;

export const ModalInput = styled.input`
  width: 7.2rem;
  border: 0.01rem solid ${({ theme }) => theme.palette.lightgray};
  height: 0.8rem;
  border-radius: 0.2rem;
  margin-top: 0.4rem;
  text-align: center;
  font-size: 0.2rem;
`;

export const ModalInput2 = styled.input`
  width: 7.2rem;
  border: 0.01rem solid ${({ theme }) => theme.palette.lightgray};
  height: 0.8rem;
  border-radius: 0.2rem;
  text-align: center;
  font-size: 0.2rem;
  ::placeholder {
    color: black;
  }
`;

0개의 댓글