[SEB 41] Main Project (2) 목표 게시글 CRUD 기능 구현

동화·2023년 2월 1일
0

Main-Project

목록 보기
2/6
post-thumbnail

화면 🎥

아직 css는 미완성 상태.. 그리고 검색 부분도 다른 분이 추가해주셔야
목록 부분이 완성이 된다.
프리 프로젝트 때 CRUD 구현을 못해서, 이번에 이 파트 해보겠다고 맡았는데
못하지 않을까 지레 겁부터 먹고 시작했으나 그래도 결국엔 해냈다.




초기 작업

API 명세서 📃

백엔드 분도 덩달아 고생하셨다...
너무 잘하시는 분이셔서 진짜 다행이도 별 문제 없이 끝났던...


로그인 토큰

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);

나는 로그인 된 회원 정보만 받아오면 되기때문에 가져오는 토큰만 적음.



헤더와 url

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> 나의 목표&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</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>&nbsp;&nbsp;&nbsp;&nbsp;(개월)</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>생성 날짜&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</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">&apos;나의 목표&apos;</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시까지
주말 내 끝내기만 했어도 됐었는데 마음이 불안해서...
한끼도 안먹고 잠도 못잤는데 뭔가 빡집중하고 있으니까 배도 안 고프고 졸리지도 않았음
요정도 마무리하고 나니까 배가 갑자기 확 고파서 갈 때 고추바사삭 사감ㅋ
가는 길에 버스에서 거의 기ㅡㅡㅡㅡ절 😴

0개의 댓글