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;
}
`;