[WIL 6週間]航海99組プロジェクト愛犬動物病院予約プラットフォームwith reaction

14484 ワード

今回の航行996週目は初めて反応器を使ったチームミニプロジェクトを試みました!
ついに、私の好きなフィードバックを利用して、バックグラウンドの開発者と初めてチームで協力して、とても楽しかったです.

プロジェクトの説明


プロジェクト期間:2021-07-09~2012-07-15
項目名称:アプギ盟(愛犬動物病院予約プラットフォーム)
チームメンバー:反応開発者3名、スプリングガイド開発者2名
使用テクニック:反応器、火ベース、スプリングガイド
IDE:VSコードの使用
項目説明:受診する症状の種類を選んだ後、専門病院のために予約システムを構築する
機能:会員登録と登録、個人情報画像アップロード、病院予約と評論採点システム、私の個人ホームページ上の予約履歴を表示し、受診したい症状病種別をスクリーニングする
プロジェクト進捗:プロジェクトテーマの選択->ワイヤフレーム->必要な機能の選択->必要な機能のオンデマンドapi整理(URLとパラメータ名)->フロントエンドとバックエンド開発者が分担->機能実装->機能テスト
ワイヤフレームurl-https://ovenapp.io/view/gMfUj8EvR4DBv1FG6g5BwpVkd6ur0LrA#sNWSj
notion url - https://www.notion.so/26-ac379cabede94755ae8303bba6f9361c

プロジェクト成果物


ログインと登録のコスト




1.ネットワークにログインしたときにサーバからランダムトークン値を取得し、axiosヘッダファイルとWebサービスクッキーに格納し、jwt方式でサーバと通信する
const loginDB = (userName, password) => {
  return function (dispatch, getState, { history }) {
    let login_info = {
      userName,
      password,
    };
    instance
      .post("/user", login_info)
      .then((response) => {
        console.log(response);
        const accessToken = response.data;

        // API 요청하는 콜마다 해더에 accessTocken 담아 보내도록 설정
        instance.defaults.headers.common["Authorization"] = `${accessToken}`;

        //받은 token 쿠키에 저장
        setCookie("token", accessToken, 1, "/");
        // const token = getCookie("token");
        dispatch(setUser(login_info));
        history.push("/pages/mainpage");
      })
      .catch((error) => console.log("로그인 중 에러가 발생했어요!", error));
  };
};

コードの説明

  • ログイン時にユーザのアイデンティティとパスワードをパラメータとしてサーバに送信し、サーバはランダムなトークン値(ユーザがDBに存在する場合)に応答する.応答値としてデータを送信->クライアントがトークン値を受信し、axiosのヘッダファイルにデフォルトの許可転送値を設定->tokenというクッキーを実行してトークン値を格納->通常処理後、ホームページ
  • に移動する

    ホームページ(病院のリストを表示)



    1.データベースに格納されている病院ディレクトリの出力
    const getHospitalsDB = () => {
      return function (dispatch, getState, { history }) {
        instance.get("/hospitals").then((result) => {
          dispatch(getHospitals(result.data));
        });
      };
    };
    
    const getHospitals = createAction(GET_HOSPITALS, (hospitals) => ({
      hospitals,
    }));
    
    export default handleActions(
      {
        [GET_HOSPITALS]: (state, action) => {
          return produce(state, (draft) => {
            draft.hospital_list = action.payload.hospitals;
          });
        },
      },
      initialState
    );

    コードの説明

  • aixosをインスタンス化し、通信後getHospitalsというレプリケーション動作関数を使用して病院リストをレプリケーション状態値に保存し、map関数を使用してクライアント出力
  • を行う.

    病院の詳細ページ





    1.当該病院のid値を調べた後、該当病院の詳細情報を出力する
      const { id } = useParams();
    
    
      useEffect(() => {
        dispatch(userActions.loginCheckDB());
        dispatch(getHospitalDB(id));
        
        if (location.state !== undefined) {
          setTabIndex(location.state.tabIndex);
        }
      }, []);
    
    export const getHospitalDB = (id) => {
      return function (dispatch, getState, { history }) {
        const token = getCookie("token");
        instance.defaults.headers.common["Authorization"] = `${token}`;
        instance.get(`/hospitals/${id}`).then((result) => {
          dispatch(getHospital(result.data));
        });
      };
    };
    
    const getHospital = createAction(GET_HOSPITAL, (hospital) => ({ hospital }));
    
    export default handleActions(
      {
        [GET_HOSPITAL]: (state, action) =>
          produce(state, (draft) => {
            draft.hospital = action.payload.hospital;
          }),
      },
      initialState
    );
    
    const hospital = useSelector((state) => state.hospital.hospital);
    
      
      

    コードの説明

  • useParams()を使用してurlパラメータ値を取得->useEffectを使用して初期レンダリング時のgetHospitaldB関数に対応するid値を入れてreduce action関数を呼び出す->reduce状態値に病院詳細データを格納する
  • 2.病院のコメントリスト
    const { id } = useParams();
    
      useEffect(() => {
        dispatch(actionCreators.getReviewDB(id));
      }, []);
      
      const getReviewDB = (id) => {
      return function (dispatch, getState, { history }) {
        const token = getCookie("token");
        instance.defaults.headers.common["Authorization"] = `${token}`;
        instance.get(`/hospitals/${id}/reviews`).then((result) => {
          dispatch(getReview(result.data));
        });
      };
    };
    
    const getReview = createAction(GET_REVIEW, (review) => ({ review }));
    
    export default handleActions(
      {
        [GET_REVIEW]: (state, action) => {
          return produce(state, (draft) => {
            draft.review_list = action.payload.review;
          });
        },
      },
      initialState
    );

    コードの説明

  • UseParams()を使用してurlパラメータ値を取得する->Effectを使用して初期レンダリング時のgetReviewDB関数に対応するid値を入れてReduct action関数を呼び出す->Reduct状態値に対応する病院レポートデータ
  • を格納する
    3.コメントの作成
      const handleAddReview = (review) => {
        dispatch(actionCreators.addReviewDB(id, review));
      };
    
    
    const addReviewDB = (id, review) => {
      return function (dispatch, getState, { history }) {
        const token = getCookie("token");
        instance.defaults.headers.common["Authorization"] = `${token}`;
        const { reviewContent, hospitalRate } = review;
        const new_review = {
          reviewContent,
          hospitalRate,
        };
        instance.post(`/hospitals/${id}/reviews`, new_review).then((result) => {
          dispatch(getReviewDB(id));
        });
      };
    };
    

    コードの説明

  • handleAddReview関数では、コメントスコアとコンテンツをパラメータ->axios postメソッドにコメントID、スコアとコンテンツ->getReviewDB実行後にステータス値反映を再適用する
  • に送信します.
  • 地図を使用して病院の位置を検索する
  • const { kakao } = window;
    
    const hospital = useSelector((state) => state.hospital.hospital);
      const { hospitalName, hospitalLocation, hospitalNumber } = hospital;
      useEffect(() => {
        const mapContainer = document.getElementById("myMap"), // 지도를 표시할 div
          mapOption = {
            center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
            level: 3, // 지도의 확대 레벨
          };
    
        // 지도를 생성합니다
        const map = new kakao.maps.Map(mapContainer, mapOption);
    
        // 주소-좌표 변환 객체를 생성합니다
        const geocoder = new kakao.maps.services.Geocoder();
    
        // 주소로 좌표를 검색합니다
        geocoder.addressSearch(hospitalLocation, function (result, status) {
          // 정상적으로 검색이 완료됐으면
          if (status === kakao.maps.services.Status.OK) {
            const coords = new kakao.maps.LatLng(result[0].y, result[0].x);
    
            // 결과값으로 받은 위치를 마커로 표시합니다
            const marker = new kakao.maps.Marker({
              map: map,
              position: coords,
            });
    
            // 인포윈도우로 장소에 대한 설명을 표시합니다
            const infowindow = new kakao.maps.InfoWindow({
              content: `<div style="width:150px;text-align:center;padding:6px 0;">${hospitalName}</div>`,
            });
            infowindow.open(map, marker);
    
            // 지도의 중심을 결과값으로 받은 위치로 이동시킵니다
            map.setCenter(coords);
          }
        });
      }, []);
      

    コードの説明

  • リッドス状態値から病院の住所を読み込み、KACA APIを使用して地図にマークする住所
  • を挿入する.

    診察別フィルタページ



    1.カテゴリをクリックすると、そのカテゴリの専門知識を持つ病院のリストが表示されます
    const search = (e, idx) => {
        changeColor(e, idx);
        const keyword = e.target.textContent;
        // 한글로 보내면 에러나기때문에 인코딩 후 서버에서 디코딩 진행
        const encode = encodeURIComponent(keyword);
        instance.get(`/hospitals/search?subject=${encode}`).then((response) => {
          setData(response.data);
          console.log(response.data);
        });
      };

    コードの説明

  • カテゴリをクリックすると、クリックしたカテゴリがChangeColor関数を使用してシェーディングされます.
  • 該当カテゴリキーワードを符号化し、サーバに送信後にデータを受信し、Dateを設定後に病院リスト
  • を出力する.

    予約ページ




    1.ペットの犬の名前、予約日程と要求事項を記入した後、予約をクリックして病院の予約を完了する
     const reservate = () => {
        dispatch(
          reservationActions.addReservationDB(id, dogName, schedule, request)
        );
      };
      
    const addReservationDB = (
      hospitalId,
      dogName,
      reservationDate,
      reservationDetail
    ) => {
      return function (dispatch, getState, { history }) {
        let new_reservation = {
          hospitalId,
          dogName,
          reservationDate,
          reservationDetail,
        };
        const token = getCookie("token");
        instance.defaults.headers.common["Authorization"] = `${token}`;
    
        instance
          .post(
            "/reservations",
            new_reservation
            // ... add other header lines like: 'Content-Type': 'application/json'
          )
          .then((response) => {
            console.log(response);
            switch (response.data.msg) {
              case "success":
                dispatch(addReservation(new_reservation));
                window.alert(
                  "예약이 완료되었습니다. 마이페이지에서 예약 목록을 확인해 보세요 :)"
                );
                history.push("/pages/mypage");
                break;
              case "not_login":
                window.alert("로그인이 필요합니다!");
                history.replace("/login");
                break;
              default:
                // window.alert("예약 신청 중 오류가 생겼네요! 다시 부탁드려요!");
                dispatch(addReservation(new_reservation));
                window.alert("예약이 완료되었습니다.");
                history.push("/pages/mypage");
                break;
            }
          })
          .catch((error) => {
            console.log("예약 저장 중 오류 발생!", error);
          });
      };
    };
    

    コードの説明


    「予定
  • 」をクリックしてreserve関数を実行する->予定情報を変数として、addReservationDB reduce関数を実行する->データベースに予定履歴を保存する
  • マイページ



    1.プロファイル画像の変更
      const selectFile = (e) => {
        const reader = new FileReader();
        const file = imageInput.current.files[0];
    
        reader.readAsDataURL(file);
    
        reader.onloadend = () => {
          setPreview(reader.result);
        };
      };
      
      
      const uploadFB = () => {
        let image = imageInput.current.files[0];
        dispatch(imageActions.uploadImageFB(image));
        setTimeout(() => setDone(true), 500);
        setTimeout(() => setDone(false), 3000);
      };
      
      const uploadImageFB = (image) => {
      return function (dispatch, getState, { history }) {
        const _upload = storage.ref(`images/${image.name}`).put(image);
    
        _upload.then((snapshot) => {
          console.log(snapshot);
    
          snapshot.ref.getDownloadURL().then((url) => {
            dispatch(uploadImage(url));
            instance.post("/userinfo/image", { dogImage: url }).then((response) => {
              dispatch(userActions.editUser(url));
            });
          });
        });
      };
    };
    

    コードの説明

  • カメラボタンをクリックしてSelectFile関数を実行して写真をアップロードすると、画像がプレビュー形式で出力されます->右クリックアップロードFB実行->画像ファイルが火庫に保存されます->保存が完了したら、画像URLを受信してそのURLをステータス値->/userinfo/imageapiに保存します.Devieのプロファイルイメージの更新->プロファイルイメージのurlをredususer状態値
  • に更新する
    2.ログアウト
    const logoutDB = () => {
      return function (dispatch, getState, { history }) {
        deleteCookie("token");
        instance.defaults.headers.common["Authorization"] = null;
        delete instance.defaults.headers.common["Authorization"];
        dispatch(logout());
        history.push("/pages/mainpage");
      };
    };

    コードの説明

  • ログアウトボタンをクリックしてlogoutDB関数を実行する->jwtトークン値を保存するトークンcookieを削除->axiosヘッダ値nullに変更->ホームページ
  • に移動

    に感銘を与える


    初めて反応器を使ってバックエンドと協力したのは、有意義な時間だったようだ.協力の過程で一般個人のプロジェクトとは違ってコミュニケーションが重要だと感じました.お互いが望む計画の方向性に合意することも重要であり、api設計など多くの面で合意する必要がある.この過程で、誰かが利己的になったり、背いたりすると、プロジェクトは山になるかもしれません.ある部分に困難があれば、なぜ困難があるのかを盲目的に強要するのではなく、理解して解決する能力を持つべきだ.
    いったん会社に入ったら、開発は絶対に一人でやるものではありません.チームパートナーがいて、コミュニケーションは開発者が避けられない重要な宿命です.個人の開発力を増やすことも重要だが、残りの99の航海隊プロジェクトの中で、このコミュニケーション能力を育成する時間が増えることを望んでいる.

    URL


    ワイヤフレーム-https://ovenapp.io/view/gMfUj8EvR4DBv1FG6g5BwpVkd6ur0LrA#sNWSj
    コンセプト企画-https://www.notion.so/26-ac379cabede94755ae8303bba6f9361c
    github - https://github.com/Sparta-MungMung/mungmung_client
    インプリメンテーションドメイン-http://munghospital.shop/
    プロジェクト概要YouTube url-https://www.youtube.com/watch?v=Sd98UjrPmB4