아직 css는 미완성 상태.. 그리고 검색 부분도 다른 분이 추가해주셔야
목록 부분이 완성이 된다.
프리 프로젝트 때 CRUD 구현을 못해서, 이번에 이 파트 해보겠다고 맡았는데
못하지 않을까 지레 겁부터 먹고 시작했으나 그래도 결국엔 해냈다.
백엔드 분도 덩달아 고생하셨다...
너무 잘하시는 분이셔서 진짜 다행이도 별 문제 없이 끝났던...
import { Cookies } from 'react-cookie';
//npm -> react-cookie 문서 option
const ACCESS_TOKEN = 'token';
const REFRESH_TOKEN = 'refreshToken'
//토큰가져오기
export const getACCESS_TOKEN = () => cookies.get(ACCESS_TOKEN);
export const getREFRESH_TOKEN = () => cookies.get(REFRESH_TOKEN);
나는 로그인 된 회원 정보만 받아오면 되기때문에 가져오는 토큰만 적음.
import { getACCESS_TOKEN, getREFRESH_TOKEN } from '../helper/cookieHelper';
// url
const urlGOALS = `${base}${api}${goals}`;
export const getURL_GOALS = (goalID) =>
`${urlGOALS}${goalID ? `/${goalID}` : ''}`;
// header
export const getWITH_PARAMS = (params) => {
return {
params: params,
};
};
export const getWITH_TOKEN = (params) => {
return {
...getWITH_PARAMS(params),
headers: {
Authorization: getACCESS_TOKEN(),
RefreshToken: getREFRESH_TOKEN(),
},
withCredentials: true,
};
};
이제 여기서 getURL_GOALS
, getWITH_TOKEN
만 꺼내서 쓰면 됐었다.
(css 제외 - @emotion/styled)
pages/GoalList.js
import GoalListGroup from '../components/goal/GoalListGroup';
const GoalList = () => {
const [list, setList] = useState([]);
useEffect(() => {
const goalGet = async () => {
axios
.get(getURL_GOALS(), getWITH_TOKEN())
.then((response) => {
const { data } = response;
// console.log(data.data);
setList(data.data);
})
.catch((error) => {
console.log(error);
});
};
goalGet();
}, []);
return (
<>
<TotalListPage>
<TopButton>
<div>
{<h2>💜 총 {list.length}개의 목표가 있습니다 💜</h2>}
<LinkButton>
<Link
to={ROUTE_PATH_GOAL_CREATE}
style={{ textDecoration: 'none' }}
>
{' '}
새로 등록하러 가기{' '}
</Link>
</LinkButton>
</div>
</TopButton>
<GoalListGroup
_list={list}
/>
</TotalListPage>
</>
);
};
components/GoalListGroup.js
const GoalListGroup = ({ _list }) => {
const [list, setList] = useState([]);
useEffect(() => {
setList(_list);
}, [_list]);
return (
<>
{list.map((item, index) => {
return (
<Fragment key={index}>
<Link
to={ROUTE_PATH_GOAL_DETAIL}
style={{ textDecoration: 'none', color: '#b1b2ff' }}
state={{ data: item, goalId: item.id }}
>
<ComponentContain>
<div style={{ display: 'flex' }}>
<Header>나의 목표 </Header>{' '}
<input
className="SettingInput"
defaultValue={item.goalName}
/>
</div>
<div style={{ display: 'flex' }}>
<Header>목표 금액 </Header>{' '}
<input
className="SettingInput"
defaultValue={item.price
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
/>
</div>
<div style={{ display: 'flex' }}>
<Header>월 납입금 </Header>{' '}
<input
className="SettingInput"
defaultValue={item.monthlyPayment
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
/>
</div>
<h2 className="Font">
목표치에 도달하기까지{' '}
<span className="Hilight">
{Math.ceil(item.price / item.monthlyPayment)}개월
</span>{' '}
남았어요!
</h2>
</ComponentContain>
</Link>
</Fragment>
);
})}
</>
);
};
pages/GoalDetail.js
const GoalDetail = () => {
const navigate = useNavigate();
// GoalListGroup에서 받은 props
const location = useLocation();
const detailData = location.state.data;
// 날짜 변환 => 일단 T 제외
const date = new Date(detailData.createdAt);
const createDate = date.toISOString().replace('T', ' ').substring(0, 19);
const { enqueueSnackbar } = useSnackbar();
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
// 삭제
const goalID = detailData.id;
const goalDelete = () => {
Swal.fire({
title: '정말로 삭제하시겠습니까?',
text: '아직 달성하지 못했을 수도 있어요!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '네, 삭제할래요!',
}).then((result) => {
axios
.delete(getURL_GOALS(goalID), getWITH_TOKEN())
.then(() => {
navigate('/goalList');
})
.catch((error) => {
const { message } = error;
enqueueSnackbar(getERROR_TEXT(Number(message.slice(-3))), {
variant: 'error',
});
});
if (result.isConfirmed) {
Swal.fire('삭제되었어요.', 'See You Again!', 'success');
}
});
};
// 수정
const [goal, setGoal] = useState(detailData.goalName); // 수기 목표 이름
const [goalPrice, setGoalPrice] = useState(detailData.price); // 수기 가격
const [monthPrice, setMonthPrice] = useState(detailData.monthlyPayment); // 수기 한 달 입금
const goalPatch = async () => {
const patchdata = {
goalName: goal,
price: goalPrice,
monthlyPayment: monthPrice,
};
axios
.patch(getURL_GOALS(goalID), patchdata, getWITH_TOKEN())
.then(() => {
Swal.fire({
text: '목표가 수정되었어요!',
icon: 'success',
});
navigate('/goalList');
})
.catch((error) => {
const { message } = error;
enqueueSnackbar(getERROR_TEXT(Number(message.slice(-3))), {
variant: 'error',
});
});
};
return (
<>
<GDetailPage>
<h2 style={{ marginTop: '30px' }}>✧ 상세 위시 정보 ✧</h2>
<GDetail>
<div>
<button className="BackButton">
<Link
to={ROUTE_PATH_GOAL_LIST}
style={{ textDecoration: 'none' }}
>
⬅️
</Link>
</button>
</div>
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<img
src={
detailData.url === null ||
detailData.url === 'null' ||
!detailData.url ||
detailData.url === ''
? noimage
: detailData.url
}
alt="no_image"
style={{ width: '300px' }}
/>
</div>
<div style={{ display: 'flex' }}>
<Title> 나의 목표 </Title>
<TextField
className="textField"
id="standard-read-only-input"
defaultValue={detailData.goalName}
InputProps={{
readOnly: true,
}}
variant="standard"
/>
</div>
<div style={{ display: 'flex' }}>
<Title>목표 금액(원)</Title>
<TextField
className="textField"
id="standard-read-only-input"
defaultValue={detailData.price
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
InputProps={{
readOnly: true,
}}
variant="standard"
/>
</div>
<div style={{ display: 'flex' }}>
<Title>월 저축액(원)</Title>
<TextField
className="textField"
id="standard-read-only-input"
defaultValue={detailData.monthlyPayment
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
InputProps={{
readOnly: true,
}}
variant="standard"
/>
</div>
<div style={{ display: 'flex' }}>
<Title>기 간(개월)</Title>
<TextField
className="textField"
id="standard-read-only-input"
defaultValue={Math.ceil(
detailData.price / detailData.monthlyPayment
)}
InputProps={{
readOnly: true,
}}
variant="standard"
/>
</div>
<div style={{ display: 'flex' }}>
<Title>생성 날짜 </Title>
<TextField
className="textField"
id="standard-read-only-input"
defaultValue={createDate}
InputProps={{
readOnly: true,
}}
variant="standard"
/>
</div>
<div style={{ display: 'flex', justifyContent: 'space-evenly' }}>
{
<Button>
<Button className="postButton" onClick={handleOpen}>
EDIT
</Button>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<h2 style={{ textDecoration: 'none', padding: '24px' }}>
목표 수정
</h2>
<form>
<TextField
id="outlined-helperText"
label="목표 이름"
variant="outlined"
defaultValue={detailData.goalName}
onChange={(e) => setGoal(e.target.value)}
style={{ margin: '24px', width: 300 }}
/>
<br />
<TextField
id="outlined-helperText"
label="목표 금액"
variant="outlined"
defaultValue={detailData.price}
onChange={(e) => setGoalPrice(e.target.value)}
style={{ margin: '24px', width: 300 }}
/>
<br />
<TextField
id="outlined-helperText"
label="월 입금액"
variant="outlined"
defaultValue={detailData.monthlyPayment}
onChange={(e) => setMonthPrice(e.target.value)}
style={{ margin: '24px', width: 300 }}
/>
<br />
<Button
style={{
padding: '24px',
textAlign: 'center',
marginRight: '10px',
}}
onClick={goalPatch}
>
CONFIRM
</Button>
</form>
</Box>
</Modal>
</Button>
}
{
<Button className="deleteButton" onClick={goalDelete}>
DELETE
</Button>
}
</div>
</GDetail>
</GDetailPage>
</>
);
};
pages/GoalCreate.js
import GoalSetting from '../components/goal/GoalSetting';
const GoalCreatePage = () => {
const navigate = useNavigate();
const [goal, setGoal] = useState(''); // 수기 목표 이름
const [goalPrice, setGoalPrice] = useState(''); // 수기 가격
const [monthPrice, setMonthPrice] = useState(''); // 수기 한 달 입금
// console.log(setGoalPrice);
// console.log(setMonthPrice);
// 코드 리팩토링
const handlerGoal = (e) => {
// console.log(e.target.value);
setGoal(e.target.value);
};
const handlerGoalPrice = (e) => {
// console.log(e.target.value);
setGoalPrice(e.target.value);
};
const handlerMonthPrice = (e) => {
setMonthPrice(e.target.value);
};
const goalPost = () => {
const postdata = {
goalName: goal,
price: goalPrice,
monthlyPayment: monthPrice,
};
axios
.post(getURL_GOALS(), postdata, getWITH_TOKEN())
.then((response) => {
const { data } = response;
console.log(data);
Swal.fire({
text: '목표가 등록되었어요!',
icon: 'success',
});
setGoal('');
setGoalPrice('');
setMonthPrice('');
navigate('/goalList');
})
.catch((error) => {
console.log(error);
});
};
return (
<CreatePage>
<GuideBox>
<h2 className="TextHeader">나만의 목표를 등록하는 방법</h2>
<br />
<br />
<p className="Text">
- <span className="Hilight">'나의 목표'</span>에 물건을
검색하여 시세를 찾을 수 있어요!
</p>
<br />
<p className="Text">
- <span className="Hilight">검색</span> 으로 나오지 않는다면, 직접
작성하여 등록할 수 있어요!
</p>
<br />
<p className="Text">
- 등록된 물품은 언제든 <span className="Hilight">수정, 삭제</span>가
가능합니다!
</p>
<br />
<p className="Text">
- 한 달에 이 물건을 위해 모을 수 있는 돈을 기입해 보아요!
</p>
<br />
<p className="Text">
- 위시리스트는 최대 <span className="Hilight">5개</span>까지 등록
가능합니다.
</p>
<br />
</GuideBox>
<GoalSetting
goal={goal}
goalPrice={goalPrice}
monthPrice={monthPrice}
setGoal={setGoal}
setGoalPrice={setGoalPrice}
setMonthPrice={setMonthPrice}
goalPost={goalPost}
handlerGoal={handlerGoal}
handlerExtended={handlerGoalPrice}
handlerPeriod={handlerMonthPrice}
/>
</CreatePage>
);
};
components/GoalSetting.js
const AssetSetting = ({
goal,
goalPrice,
monthPrice,
goalPost,
setGoal,
setMonthPrice,
setGoalPrice,
// handlerGoal,
// handlerGoalPrice,
// handlerMonthPrice,
}) => {
return (
<>
<div style={{ display: 'flex' }}>
<ComponentContain>
<br />
<LineBox>
<Header>나의 목표</Header>
<SettingInput
placeholder="제네시스 GV80"
type="text"
onChange={(e) => setGoal(e.target.value)}
value={goal}
/>
</LineBox>
<LineBox>
<Header>목표 금액</Header>
<SettingInput
placeholder="61,360,000"
type="number"
onChange={(e) => setGoalPrice(e.target.value)}
value={goalPrice}
/>
</LineBox>
<LineBox>
<Header>월 입금액</Header>
<SettingInput
placeholder="300,000"
type="number"
onChange={(e) => setMonthPrice(e.target.value)}
value={monthPrice}
/>
</LineBox>
<p className="p">목표달성을 위한 기간은?</p>
<>
<Button className="postButton" onClick={goalPost}>
SUBMIT
</Button>
</>
</ComponentContain>
</div>
</>
);
};
아침 8시에 야간 알바 끝나자마자 바로 삼실로 와서 오후 6시까지
주말 내 끝내기만 했어도 됐었는데 마음이 불안해서...
한끼도 안먹고 잠도 못잤는데 뭔가 빡집중하고 있으니까 배도 안 고프고 졸리지도 않았음
요정도 마무리하고 나니까 배가 갑자기 확 고파서 갈 때 고추바사삭 사감ㅋ
가는 길에 버스에서 거의 기ㅡㅡㅡㅡ절 😴