careerViewTable.js
return (
<tbody>
{careerList.map((e) =>
<CareerRow
key={e.id}
career={e}
checkedRowId {checkedRowId}
onDeleteCareer={onDeleteCareer}
onSelect={onSelect}
/>)}
</tbody>
);
careerRow.js
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import EditIcon from "@mui/icons-material/Edit";
import { useState } from "react";
const CareerRow = (props) => {
// 만일 isEdit에 true가 들어있으면 해당 행이 수정 상태임 을 의미하고
// isEdit에 false가 들어있으면 해당 행이 수정상태가 아님 을 의미한다
const [isEdit, setIsEdit] = useState(false);
const dateFormat = (date) => {
// '2023년10월01일' --> '2023-10-01'
return date
.replace("년", "-")
.replace("월", "-")
.replace("일", "")
.replace(/ /g, ""); // / /g --> 모든 띄어쓰기 공백 을 의미
};
// props.career = e;
// props.checkedRowId
const e = props.career;
const { checkedRowId, onSelect, onDeleteRow } = props;
return (
<tr>
<td>
<input
type="checkbox"
checked={checkedRowId.includes(e.id)}
onChange={() => {
onSelect(e.id);
}}
/>
</td>
<td> {isEdit ? <input defaultValue={e.company} /> : e.company} </td>
<td> {isEdit ? <input defaultValue={e.position} /> : e.position} </td> //isEdit이 true면 input, false이면 e.position 원래값.
<td style={{ display: "flex" }}>
{isEdit ? (
<input defaultValue={dateFormat(e.start_date)} type="date" />
) : (
e.start_date
)}
-
{isEdit ? (
<input type="date" defaultValue={dateFormat(e.end_date)} />
) : (
e.end_date
)}
</td>
<td
onClick={()=> onDeleteRow(e.id);
style={{
cursor: "pointer",
}}
>
{isEdit ? <EditIcon /> : <DeleteOutlineIcon />}
</td>
</tr>
);
};
export default CareerRow;
careerRow.js
// 상태를 수정상태로 변경해주는 함수
const onEditState = () => {
setIsEdit(true);
};
return(
<td onClick={onEditState}>
{isEdit ? <input ref={companyInputRef} defaultValue={e.company} /> : e.company}
</td>
<td onClick={onEditState}>
{isEdit ? <input ref={positionInputRef} defaultValue={e.position} /> : e.position}
//isEdit이 true면 input, false이면 e.position 원래값.
</td>
<td onClick={onEditState} style={{ display: "flex" }}>
{isEdit ? (
<input ref={startDateInputRef} defaultValue={dateFormat(e.start_date)} type="date" />
) : (
e.start_date
)}
-
{isEdit ? (
<input ref={endDateInputRef} type="date" defaultValue={dateFormat(e.end_date)} />
) : (
e.end_date
)}
</td>
);
state변수를 사용해도 되지만, 사용하면 렌더링이 되는데 굳이 렌더링이되어 화면이 다시 그려지게할 필요는 없다.
그래서, 여기서 useRef() 객체를 를 사용한다. (자기 자신은 변경해도 re-rendering은 하지 않는다. )
careerRow.js
const onEditClick = async (id) => {
// id에는 수정할 행의 id가 들어있음
// 수정하기 버튼 클릭시 실행될 함수
// 1. 사용자가 입력한 company, postion, startDate, endDate 가져오기
const company = companyInputRef.current.value;
const position = positionInputRef.current.value;
const startDate = startDateInputRef.current.value;
const endDate = endDateInputRef.current.value;
return(
<td onClick={onEditState}>
{isEdit ? <input ref={companyInputRef} defaultValue={e.company} /> : e.company}
</td>
<td onClick={onEditState}>
{isEdit ? <input ref={positionInputRef} defaultValue={e.position} /> : e.position}
</td>
<td onClick={onEditState} style={{ display: "flex" }}>
{isEdit ? (
<input ref={startDateInputRef} defaultValue={dateFormat(e.start_date)} type="date" />
) : (
e.start_date
)}
-
{isEdit ? (
<input ref={endDateInputRef} type="date" defaultValue={dateFormat(e.end_date)} />
) : (
e.end_date
)}
</td>
);
careerRow.js
import { useRef } from "react";
const onEditClick = async (id) => {
// id에는 수정할 행의 id가 들어있음
// 수정하기 버튼 클릭시 실행될 함수
// 1. 사용자가 입력한 company, postion, startDate, endDate 가져오기
const company = companyInputRef.current.value;
const position = positionInputRef.current.value;
const startDate = startDateInputRef.current.value;
const endDate = endDateInputRef.current.value;
// 유효성 검사
if (company === '') {
alert('회사명은 필수입력입니다');
return;
}
if (position === '') {
alert('직책은 필수입력 입니다');
return;
}
if (startDate === '') {
alert('시작일은 필수입력 입니다');
return;
}
const today = new Date();
const startDateTmp = new Date(startDate);
if (startDateTmp > today) {
alert('시작일은 오늘 날짜를 넘어가면 안됩니다');
return;
}
if (endDate !== '') {
const endDateTmp = new Date(endDate);
if (endDateTmp < startDateTmp) {
alert('종료일은 시작일보다 이전으로 설정할 수 없습니다')
return;
}
if (endDateTmp > today) {
alert('종료일은 오늘 날짜를 넘어가면 안됩니다');
return;
}
}
return (
<td onClick={() => {
if (isEdit) {
onEditClick(e.id);
} else {
onDeleteRow(e.id);
}
}}
style={{
cursor: "pointer",
}}
>
{isEdit ? <EditIcon /> : <DeleteOutlineIcon />}
</td>
);
careerRow.js
import axios from "axios";
import { useContext, useRef, useState } from "react";
import { UserContext } from "../../App";
const { accessToken } = useContext(UserContext); //전역변수에 저장한거를 가져온다 accessToken안에 토큰이 들어있다.
// props.career = e;
// props.checkedRowId
const e = props.career;
const { checkedRowId, onSelect, onDeleteRow } = props;
// 유효한 값들이 입력되었음을 확인한다면
// express 서버에 입력한 값들을 전달하여 수정 요청하기
try {
await axios.put('/api/careers', { company, position, startDate, endDate, id }, // body에 넣어서
{ headers: { Authorization: `Bearer ${accessToken}` } } //로그인했을때만 사용할수있으니까, headers에 토큰 전달.
)
alert('수정완료~!');
setIsEdit(false); // 수정이 완료된후, -> 수정상태를 false 로바꾸기
// e 안에 있는 company, position , startDate, endDate 변경
e.company = company;
e.position = position;
e.start_date = startDate;
e.end_date = endDate;
} catch (err) {
console.log(err);
alert('오류발생');
}
};
app.js
app.put('/api/careers', async (req, res) => {
//react에서 넘겨준 값들(수정값들)
console.log(req.body); //body안에는 {company: 'starbuck' , position: 'barista', startDate: '2022-01-01', endDate: '2023-01-01' } 가 들어있다.
const { company, position, startDate, endDate, id } = req.body; //req.body 안에서 꺼내올, company, position, startDate, endDate, id를 적는다.
// 로그인 유저가 요청했는지 여부 검사
console.log(req.headers.authorization);
let token = req.headers.authorization.replace('Bearer ', ''); //토큰을 가져오고. -> 여기서, req.headers.authorization
try {
jwt.verify(token, process.env.JWT_SECRET)
} catch (err) {
res.status(403).json('토큰이 만료되었으니 다시 로그인 필요');
return;
}
// 정상적인 토큰이라면 mysql 가서 수정 요청
let sql = `
UPDATE tbl_careers
SET company = ?, position= ?, start_date = ?, end_date= ?
WHERE id = ?
`;
try {
await pool.query(sql, [company, position, startDate, endDate, id]);
res.send('수정 완료!');
} catch (err) {
console.log(err);
res.status(500).json('오류 발생함!');
}
});
📝 result
sidebar.js
const DashboardSidebar = (props) => {
// props.isOpen
let items = [
{ icon: <DashboardOutlinedIcon />, title: 'Overview', path: '/overview' },
{ icon: <AssignmentIndOutlinedIcon />, title: '경력', path: '/career' },
{ icon: <TopicOutlinedIcon />, title: '활동게시판', path: '/activity' },
{ icon: <ChecklistOutlinedIcon />, title: '할일목록', path: '/todo' },
];
return(
<Menu>
{items.map((el) =>
<li key={el.title} onClick={() => { navigate(el.path) }}>
<AsideMenuItem active={el.title === props.target}>
//'경력' -->true
{el.icon}
<p>{el.title}</p>
<KeyboardArrowRightOutlinedIcon />
</AsideMenuItem>
</li>
)}
</Menu>
);
career.js
const CareerPage = () => {
useAuth();
return (
<DashboardLayout target="경력">
<Title>나의 경력을 관리하세요</Title>
<p>회사, 직위, 일자를 입력한 후 경력을 추가해 보세요!</p>
<CareerViewTable />
</DashboardLayout>
)
}
layout.js
const DashboardLayout = (props)=>{
<Wrapper isOpen={isOpen}>
<DashboardSidebar isOpen={isOpen} target={props.target}/> //props.target ->경력
<MainWrapper>
<DashboardHeader isOpen={isOpen} setIsOpen={setIsOpen}/>
<Main>
{props.children}
</Main>
<DashboardFooter/>
</MainWrapper>
</Wrapper>
);
}
export default DashboardLayout;
✏️ target="할일목록"
을 써준다
todo.js
import DashboardLayout from "../../components/common/layout";
import { useAuth } from "../../components/hooks/hooks";
const TodoPage = () => {
useAuth();
return (
<DashboardLayout target="할일목록">
</DashboardLayout>
);
};
export default TodoPage;
li tag
가 클릭됬을때, ->onClick()
사용
sidebar.js
const navigate = useNavigate();
//path까지 설정해주기
let items = [
{ icon: <DashboardOutlinedIcon />, title: 'Overview', path: '/overview' },
{ icon: <AssignmentIndOutlinedIcon />, title: '경력', path: '/career' },
{ icon: <TopicOutlinedIcon />, title: '활동게시판', path: '/activity' },
{ icon: <ChecklistOutlinedIcon />, title: '할일목록', path: '/todo' },
];
return(
<Menu>
{items.map((el) =>
<li key={el.title} onClick={() => { navigate(el.path) }}>
<AsideMenuItem active={el.title === props.target}>
{el.icon}
<p>{el.title}</p>
<KeyboardArrowRightOutlinedIcon />
</AsideMenuItem>
</li>
)}
</Menu>
);
state변수
를 한개 만들수있다.(true/false)
로 구현가능.
layout에 있는
<DashboardHeader isOpen={isOpen} setIsOpen={setIsOpen}/>
layout.js
const DashboardLayout = (props)=>{
// 사이드바가 열려있는지 닫혀있는지 확인하는 스테이트 변수
const [isOpen , setIsOpen] = useState(true);
return(
<Wrapper isOpen={isOpen}>
<DashboardSidebar isOpen={isOpen} target={props.target}/>
<MainWrapper>
<DashboardHeader isOpen={isOpen} setIsOpen={setIsOpen}/> //전달하기
<Main>
{props.children}
</Main>
<DashboardFooter/>
</MainWrapper>
</Wrapper>
);
}
export default DashboardLayout;
header에 있는 MenuOpenIcon을 클릭했을떄,
onClick()
사용
header.js
const DashboardHeader = (props) => {
// DashboardLayout에서 만든 state변수와 setState함수를 자식인
// DashboardHeader에서 받아옴
const { isOpen, setIsOpen } = props;
}
return (
<div onClick={() => { setIsOpen(!isOpen) }}
style={{ cursor: "pointer" }}>
{isOpen ? <MenuOpenIcon /> : <MenuIcon />}
</div>
)
sidebar.js
const DashboardSidebar = (props) => {
return (
<DashboardAside style={{transform : props.isOpen ? '' : 'translateX(-100%'}} >
</DashboardAside>
)
}
css
state변수
만들고props전달
해서 사용
layout.styles.js
export const Wrapper = styled.div` display: flex; border-width: 10px; padding-left: ${(props)=> props.isOpen ? '240px' : '0'}; transition: 300ms; `;
layout.js
const DashboardLayout = (props)=> { // 사이드바가 열려있는지 닫혀있는지 확인하는 스테이트 변수 const [isOpen , setIsOpen] = useState(true); return( <Wrapper isOpen={isOpen}></Wrapper> ) }