위코드-07.04-1차 프로젝트 후기-controst팀

jin_sk·2020년 7월 4일
1

프로젝트

목록 보기
1/2

1차 프로젝트 (controst팀)

  • 기간 : 20.06.22 ~ 20.07.03
  • 사이트 : trost (상담사 플랫폼 사이트)
  • 구성원 : 프론트엔드 3명, 백엔드 3명
  • 담당 : 프론트엔드 (Partner 페이지 , Partner profile 페이지)
  • 사용 기술 : React.js(Class형 컴포넌트), JavaScript(ES6), SCSS
  • React.js를 사용한 1차 팀 프로젝트이자 Git을 사용한 첫 번째 협업 경험
  • GitHub
  • 프로젝트 영상


1. Partner 페이지

작성한 파일

  • Partner / PartnerNav / PartnerAside / PartnerList / PartnerCard
  • Partner.scss

1-1. 레이아웃

  • 아직은 부족하지만 전체를 <div>로 나누기보단 시맨틱 태그를 사용하려 노력하였다
  • nav, main, aside, article, section, header 등
  • 페이지를 보며 종이에 대략적으로 기획한 후 html 작성
  • 전체 코드 작성 후 컴포넌트 분리

1-2. scss

  • scss를 본격적으로 사용
  • 소속감이 명확해 css보다 더 사용하기 편했다

1-3. 코드

1-3-1. 별점 구현 함수

  • 총 별점 5개를 기준으로 백엔드로 부터 전달받은 별점에 각각 인자로 전달받은 1~5 까지 숫자를 차감하여
    0 이상일 경우 꽉 채운 별 return
    -1 보다 크고 -0.5 보다 작을때, -1보다 작을때 빈 별 return
    -0.5 보다 같거나 크고, 0보다 작을 때 반 쪽 별 return

  • 리뷰 내역
    className을 return 값으로 직접 관리하는 것 보다는 state 값으로 관리
    star-point는 항상 고정으로 있기 때문에 따로 관리해줄 필요는 없을 것 같고 state 값이 point or half 냐에 따라서 className에 둘이 추가 되는지 안되는지 관리하는게 더욱 좋을 것 같다

  ptPointClassName = (num) => {
    let starNum = this.props.point - num;
    if (starNum >= 0) {
      return "star-point full";
    } else if ((-1 < starNum && starNum < -0.5) || starNum <= -1) {
      return "star-point";
    } else if (-0.5 <= starNum && starNum < 0) {
      return "star-point half";
    }
  };
                <div className="partner-info-point">
                  <ol>
                    <li className={this.ptPointClassName(1)}></li>
                    <li className={this.ptPointClassName(2)}></li>
                    <li className={this.ptPointClassName(3)}></li>
                    <li className={this.ptPointClassName(4)}></li>
                    <li className={this.ptPointClassName(5)}></li>
                  </ol>
                </div>

1-3-2. 리스트 구현

  • 버튼 클릭시 리스트 보이고, 리스트에서 클릭한 항목 화면에 보이게 하고 사라지기

  • onClick 이벤트 2개 (항목 클릭시 목록 숨기기, 화면에 보일 state 값 업데이트)

  • 직접 인자를 받아 state 관리
    기본 화면에 보이는 항목은 고민 연관순 리스트 각 항목에 onClick 이벤트로 state 값 업데이트

  • toggle 기능 구현

  constructor() {
    super();

    this.state = {
      ptConditionShow: false,
      ptBtnOptionListShow: "고민 연관순",
    };
  }

  ptConditionClassName = () => {
    this.setState({ ptConditionShow: !this.state.ptConditionShow });
  };

  ptBtnClick = (name) => {
    this.setState({ ptBtnOptionListShow: name });
  };
          <div className="partner-list-search-condition">
            <button
              className="btn-partner-list-search-condition"
              type="button"
              onClick={this.ptConditionClassName}
            >
              {this.state.ptBtnOptionListShow}
            </button>
            <ul
              className={
                this.state.ptConditionShow
                  ? "partner-list-option-list"
                  : "partner-list-option-list not-show"
              }
            >
              <li
                className="partner-list-option-list-element"
                onClick={(event) => {
                  this.ptConditionClassName();
                  this.ptBtnClick("고민 연관순");
                }}
              >
                고민 연관순
              </li>

              <li
                className="partner-list-option-list-element"
                onClick={(event) => {
                  this.ptConditionClassName();
                  this.ptBtnClick("상담 만족도순");
                }}
              >
                상담 만족도순
              </li>

              <li
                className="partner-list-option-list-element"
                onClick={(event) => {
                  this.ptConditionClassName();
                  this.ptBtnClick("낮은 상담 가격순");
                }}
              >
                낮은 상담 가격순
              </li>

              <li
                className="partner-list-option-list-element"
                onClick={(event) => {
                  this.ptConditionClassName();
                  this.ptBtnClick("높은 상담 가격순");
                }}
              >
                높은 상담 가격순
              </li>
            </ul>
          </div>

1-3-3. 자식 component에 stata 값 전달

  • 필터 기능 구현을 위해 자식 component 에 state 값 전달하는 것이 전에는 이해가 잘 가지 않았지만 이번 프로젝트를 통해 어떻게 다루는지 조금은 알 것 같다

  • 항상 fetch 로 최상위 요소에서 어떻게 데이터를 다룰지 생각해야될 것 같다

  constructor() {
    super();
    this.state = {
      partnerData: [],
      ptEndPoint: "",
      inputPtName: "",
      genderPt: "",
      masterPt: true,
      subPt: true,
      nomalPt: true,
    };
  }
          {/* 상담사 찾기 aside */}
          <PartnerAside
            handleChangeInput={this.handleChangeInput}
            handleClickGender={this.handleClickGender}
            handleClickPosition={this.handleClickPosition}
            handleClickReset={this.handleClickReset}
          />

          {/* 파트너 list */}
          <PartnerList partnerData={ptFilter} />

1-3-4. filter 기능 구현

  • 상담사 레벨 , 성별, 이름 필터 구현

  • 상담사 레벨은 기본적으로 전부 체크 되어있어서 클릭하면 제외하는 방식이라 버튼 클릭시 state 값을 클릭한 상담사로 업데이트 하였고, 업데이트 된 값을 제외한 것으로 필터 기능 구현

  • 이름, 성별은 클릭한 값으로 state를 업데이트 하였고 그 값을 포함한 내용으로 필터 기능 구현

  • 필터속의 필터가 가능한지 몰랐으나 동작은 하여 다음에는 for 문으로 구현이 가능하도록 리팩토링이 필요

    this.state = {
      partnerData: [],
      ptEndPoint: "",
      inputPtName: "",
      genderPt: "",
      masterPt: true,
      subPt: true,
      nomalPt: true,
    };
    let ptFilter = partnerData
      .filter((positionData) => {
        return positionData.level !== (masterPt ? "" : "마스터 상담사");
      })
      .filter((positionData) => {
        return positionData.level !== (subPt ? "" : "전문 상담사");
      })
      .filter((positionData) => {
        return positionData.level !== (nomalPt ? "" : "일반 상담사");
      })
      .filter((genderData) => {
        return genderData.gender.includes(genderPt);
      })
      .filter((nameData) => {
        return nameData.name.includes(inputPtName);
      });

1-3-5. map 함수

  • map 함수로 component 재사용

  • 필터된 데이터로 map 함수 사용하여 component return

        <div className="partner-list-content-wrap">
          {this.props.partnerData.map((data) => {
            return (
              <PartnerCard
                key={data.partner_id}
                id={data.partner_id}
                partnerImg={data.profile_image_url}
                name={data.name}
                level={data.level}
                headLine={data.introduction}
                point={data.stars}
                reviewNum={data.review_count}
                textPrice={Number(data.prices[0])}
                phonePrice={Number(data.prices[3])}
                meetPrice={Number(data.prices[6])}
              />
            );
          })}
        </div>

1-3-6. toggle 기능 구현

-toggle 기능 으로 버튼 배경 바꾸기
state 값 false로 설정해 onClick 이벤트로 버튼 클릭시 true로 state 값 없데이트해 배경색 바꾸기

  constructor() {
    super();
    this.state = {
      isWishBtnActive: false,
    };
  }

  handlePtWish = () => {
    this.setState({ isWishBtnActive: !this.state.isWishBtnActive });
  };
        <button
          className={
            this.state.isWishBtnActive
              ? "btn-partner-wish add-wish"
              : "btn-partner-wish"
          }
          onClick={this.handlePtWish}
        >
          상담사 찜하기
        </button>

1-3-7. fetch 함수로 백엔드로 부터 데이터 받기

  • 최초 render 후 한번만 실해오디는 componentDidMount에 fetch로 get 방식을 통해 백엔드로 부터 데이터를 호출

  • 기본이 되는 페이지에서 데이터를 불러와 자식 component에 전달

  • 랜덤으로 정렬하기 추가

  constructor() {
    super();
    this.state = {
      partnerData: [],
  componentDidMount() {
    fetch("http://10.58.7.28:8000/partner")
      .then((res) => res.json())
      .then((res) =>
        this.setState({
          partnerData: res.information.sort(() => {
            return Math.random() - Math.random();
          }),
        })
      );
    //http://localhost:3000/data/data.json
    //http://10.58.7.28:8000/partner
    //http://3.34.141.93:8000/partner
  }

1-3-8. 삼항연산자 중첩 사용

  • 조선이 여러개일 때 삼항연산자 사용
    맨처음 조건이 아닐 경우 또 다른 조건을 붙여 삼항 연산자를 사용한다
    여러 조건일때 사용하기 힘든 줄 알았는데 시야를 좀 더 넓혀야겠다는 생각이 들었다
                <span
                  className={`partner-info-position ${
                    this.props.level === "마스터 상담사"
                      ? "master"
                      : this.props.level === "전문 상담사"
                      ? "sub"
                      : "nomal"
                  }`}
                >

2. Partner profile 페이지

작성한 파일

  • PartnerProfile / PartnerNav / PartnerProfileAside / PartnerProfileCard / PartnerProfileInfo / PartnerProfileReservation / PartnerProfileReview / PartnerProfileReviewData
  • PartnerProfile.scss

2-1. 레이아웃

  • 아직은 부족하지만 전체를 <div>로 나누기보단 시맨틱 태그를 사용하려 노력하였다
  • nav, main, aside, article, section, header 등
  • 페이지를 보며 종이에 대략적으로 기획한 후 html 작성
  • 전체 레이아웃 작성 후 컴포넌트 분리

2-2. scss

  • mixin 기능 구현
    profile 페이지에서 처음 작성해보았는데 재사용되는 css 속성에 사용하기 좋았다
  @mixin info-list($wrap-padding) {
    padding: $wrap-padding;

    .list-el {
      position: relative;

      &::before {
        content: "·";
        width: 27px;
        position: absolute;
        left: -27px;
        text-align: center;
      }

      &.medal {
        &::before {
          content: "";
          width: 27px;
          height: 28px;
          position: absolute;
          left: -27px;
          background: url("https://d2qrvi4l1nprmf.cloudfront.net/images/service/partner/ic_medal.png")
            no-repeat center;
          background-size: 27px auto;
        }
      }
    }
  }

2-3. 코드

2-3-1. url parameter, fetch 로 데이터 받고 자식 component에 데이터를 넘길때 주의 사항

  • Route.js
    id로 설정하여 각 상담사의 고유 id 불러오기
<Route exact path="/partner/profile/:id" component={PartnerProfile} />
  • Partner card url 파라미터를 통해 각 상담사 정보로 갈 수 있도록 link 태그 사용
        <Link
          to={`/partner/profile/${this.props.id}`}
          style={{ textDecoration: "none", color: "var(--black)" }}
        >

  • Partner Profile 에서 fetch , this.props.match.params 를 통해 params 정보를 가져와 활용
  componentDidMount() {
    fetch(`http://10.58.7.28:8000/partner/${this.props.match.params.id}`)
      .then((res) => res.json())
      .then((res) => {
        this.setState({ partnerProfileData: res.information });
      });
    //http://localhost:3000/data/partnerProfileData.json
    //http://10.58.7.28:8000/partner/10
  }
  • Partner Profile 에서 각 컴포넌트에 데이터를 전달할 때
    constructor 실행 -> 최초 render -> componentDidMount 실행 -> render 순으로 진행되니
    맨처음 빈 데이터을 전달해 자식 component에서 데이터가 나오지 않는 문제가 생겨
    자식 component 에 데이터 전달 시 && 연산자를 사용하여 재 렌더된 후 값이 있을때 전달해야 한다는 것을 배웠다
    2-3-3 을 통해 확인 가능하다

2-3-3. active tab , props 로 데이터 전달

  • 보여주는 tab, state 로 관리
  constructor() {
    super();

    this.state = {
      showTab: "tabInfo",
      partnerProfileData: [],
    };
  }
  • tab list의 onClick 이벤트에 전달할 함수
  clickShowTab = (tabName) => {
    this.setState({ showTab: tabName });
  };
                <ul className="content-list">
                  <li
                    className={
                      this.state.showTab === "tabInfo"
                        ? "content-el is-click"
                        : "content-el"
                    }
                    onClick={() => this.clickShowTab("tabInfo")}
                  >
                    상담사 정보
                  </li>
                  <li
                    className={
                      this.state.showTab === "tabReservation"
                        ? "content-el is-click"
                        : "content-el"
                    }
                    onClick={() => this.clickShowTab("tabReservation")}
                  >
                    예약 방법
                  </li>
                  <li
                    className={
                      this.state.showTab === "tabReview"
                        ? "content-el is-click"
                        : "content-el"
                    }
                    onClick={() => this.clickShowTab("tabReview")}
                  >
                    상담 후기
                  </li>
                </ul>
  • 클릭한 tab 이름 화면에 보일 함수 (switch case 사용, 화면에 보여줄 데이터 전달)
activeTabShow = (activeTab) => {
    switch (activeTab) {
      case "tabInfo":
        return (
          <PartnerProfileInfo
            ptProInfoData={
              this.state.partnerProfileData.length > 0 &&
              this.state.partnerProfileData[0]
            }
            ptProConData={
              this.state.partnerProfileData.length > 0 &&
              this.state.partnerProfileData[1].patner_content
            }
          />
        );
      case "tabReservation":
        return <PartnerProfileReservation />;
      case "tabReview":
        return (
          <PartnerProfileReview
            ptProInfoData={
              this.state.partnerProfileData.length > 0 &&
              this.state.partnerProfileData[0]
            }
            ptProReviewData={
              this.state.partnerProfileData.length > 0 &&
              this.state.partnerProfileData[2].reviews
            }
          />
        );
      default:
        break;
    }
  };

2-3-4. 백엔드로 부터 전달 받은 데이터 가공

  • 상담사 경력을 하나의 문장으로 전달받아 \n 문자를 기준으로 배열을 생성하여 map 함수로 데이터를 return
export default class PartnerProfileInfo extends Component {
  render() {
    const { ptProInfoData, ptProConData } = this.props;
    const ptQualiArr = ptProConData && ptProConData.history_body.split("\n");
  • 상담심리사 1급을 포함하는 경우 다른 css 속성 부여
            <ul className="partner-qualification">
              {ptProConData &&
                ptQualiArr.map((data, idx) => (
                  <li
                    className={
                      data.includes("상담심리사 1급")
                        ? "list-el medal"
                        : "list-el"
                    }
                    key={idx}
                  >
                    {data}
                  </li>
                ))}
            </ul>

2-3-5. 리뷰 데이터 map으로 return

  • 백엔드로 부터 전달받은 review 데이터 map 함수로 return
          <ul className="review-list">
            {/* 리뷰데이터 컴포넌트, map 구현 */}
            {ptProReviewData.map((data, idx) => {
              return (
                <PartnerProfileReviewData
                  key={idx}
                  review_created={data.review_created}
                  review_score={data.review_score}
                  review_comment={data.review_comment}
                />
              );
            })}
          </ul>
  • random 함수 사용하여 데이터 랜덤하게 화면에 표시
const keyword = [
  "#우울",
  "#불안",
  "#분노",
  "#강박",
  "#무기력",
  "#자살",
  "#자존감상실",
  "#자해",
  "#스트레스",
  "#트라우마",
  "#공황",
  "#콤플렉스",
  "#상실",
  "#대인관계",
  "#친구",
  "#부부",
  "#연인",
  "#가족",
  "#직장",
  "#진로",
  "#취업",
  "#육아",
  "#해외생활",
  "#중독",
  "#섭식장애",
  "#성생활",
  "#성소수자",
  "#환청",
  "#기타",
];

const program = [
  "텍스트테라피 50분 상담권 상담후기",
  "텍스트테라피 50분 2회 상담권 상담후기",
  "텍스트테라피 50분 4회 상담권 상담후기",
  "전화상담 30분 상담권 상담후기",
  "전화상담 60분 상담권 상담후기",
  "전화상담 60분 상담권 2회 상담후기",
  "대면상담 30분 상담권 상담후기",
  "대면상담 50분 상담권 상담후기",
  "대면상담 50분 상담권 2회 상담후기",
];

export default class PartnerProfileReviewData extends Component {
  changePointClassName = (num) => {
    let starNum = this.props.review_score - num;
    if (starNum >= 0) {
      return "star-point full";
    } else if (-1 < starNum && starNum < -0.5) {
      return "star-point";
    } else if (-0.5 <= starNum && starNum < 0) {
      return "star-point half";
    } else if (starNum - num <= -1) {
      return "star-point";
    }
  };

  render() {
    const { review_created, review_score, review_comment } = this.props;
    // console.log(review_created);
    return (
      <li className="review-content-wrap">
        <div className="review-content-top">
          <div className="review-star-point">
            <ol>
              <li className={this.changePointClassName(1)}></li>
              <li className={this.changePointClassName(2)}></li>
              <li className={this.changePointClassName(3)}></li>
              <li className={this.changePointClassName(4)}></li>
              <li className={this.changePointClassName(5)}></li>
            </ol>
          </div>
          <div className="review-info">
            <span>{Math.floor(Math.random() * 8999) + 1000}....</span> 님 /{" "}
            <span>{review_created.split("").splice(0, 10).join("")}</span>
          </div>
        </div>
        <div className="review-content-main">
          <div className="review-text">{review_comment}</div>
          <div className="review-cate">
            <h5>{program[Math.floor(Math.random() * program.length)]}</h5>
            <ul>
              <li>{keyword[Math.floor(Math.random() * 6) + 0]}</li>
              <li>{keyword[Math.floor(Math.random() * 6) + 6]}</li>
              <li>{keyword[Math.floor(Math.random() * 6) + 12]}</li>
              <li>{keyword[Math.floor(Math.random() * 6) + 18]}</li>
              <li>{keyword[Math.floor(Math.random() * 5) + 24]}</li>
            </ul>
          </div>
        </div>
      </li>
    );
  }
}

1차 프로젝트 소감

맨 처음 프로젝트를 시작할 때 잘할 수 있을까 걱정이 많이 되었고,
협업을 통해 개발하는 것이 처음이다 보니 나 혼자 개발할 때와는 느낌이 많이 달랐다
프로젝트를 하는 동안 드는 생각은 내가 과연 잘하고 있는걸까? 라는 생각이 가장 많이 들었던 것 같다
지금 프로젝트가 끝난 후 돌이켜 생각해보니 그래도 내가 맡은 분량은 끝냈고, 지금까지 배워왔던 기술은 웬만하면 다 구현을 한 것 같아 많이 힘들었지만 뿌듯한 느낌도 든다
또한 좋은 팀원을 통해 내가 몰랐던 것들을 배운것도 많았다
백엔드와의 소통을 해보는 것도 처음이라 데이터를 전달받아 잘 편집하여 데이터가 잘 나올때 뿌듯했었다
개발이라는 직업이 하면 할수록 배워야 할것도 너무 많고, 해보고 싶은 기술도 너무 많다
다시한번 코드를 점검하고 적용하지 못했던 기술도 적용 시켜 봐야겠다
힘들지만 재미있었다

0개의 댓글