졸업작품상황보고

학교 팀프로젝트 상황보고1

우주전사버즈 2024. 3. 31. 16:44

팀프로젝트 : 맛집정보제공웹앱 

 

프로젝트개요: 수많은 맛집정보제공 서비스가 존재하지만(블루리본,식신,다이닝코드 등) 정말 개인의 입맛취향(ooo는 매운맛을싫어하고 ooo는 짠맛을좋아한다)까지 고려해서 맛집정보를 제공하는 서비스를 만들어보면 좋지않을까? 라는 취지에서 시작되었고.

컴공 졸업작품으로 지난 3학년2학기부터(2023-09~Now) 진행중인 프로젝트이다. 


맡은역할: Full stack (팀원 역할을 분리할때 프론트/백엔드를 나누지않았고 필요한기능을 하나찾으면 그냥 무작정 시도해보는방식으로 개발 진행중이다 - 코리안에자일방법)

프로젝트에서 스스로 구현완료/구현중인 아이디어 기능 : 1) 맛조절기능모드 2)로그인기능 구현

 

1) 맛 조절 기능모드 

사용자가 좋아하는 입맛을 고려한 식당을 추천하기위해 어떤방식을 고려해야할까를 생각하다가

대표적인 맛의종류인 5가지기본맛(신맛,쓴맛,짠맛,매운맛,단맛)을 5개의 단계로 지정(매우싫어요,싫어요,무난해요,좋아해요,매우좋아해요)로 맛 게이지를 나누어서 

사용자는 각각의 맛게이지를 조정해서 설정해둔다음 식당찾기를 누르면 각각조정해둔 5가지맛의 단계를 조합해서 해당 조합과 가장 가까운 식당을 조회(이런컨셉을 위해 식당 데이터를 따로 db에 하나하나 담아야겠다고 생각이 들어서 DB에 식당 테이블을 구성해둠)
한다는 컨셉으로 만들어보자라고 생각했다.

단계별로 지정해두고 그에맞는 식당을 조회한다는 아이디어는 리커드 척도기법에서 영감을얻었다.(설문조사에서 쓰이는기법)

출처 : https://www.whtm.space/likertscale/

 

코드

import React, { useState, useEffect } from "react";
import styled from "styled-components";
import RangeSlider from "../components/Services/RangeSlider";

// 모달 컴포넌트
const Modal = ({ children, onClose }) => {
  return (
    <ModalBackground onClick={onClose}>
      <ModalContent onClick={(e) => e.stopPropagation()}>
        {children}
        <button onClick={onClose}>닫기</button>
      </ModalContent>
    </ModalBackground>
  );
};

// Service 컴포넌트
const Service = () => {
  const [restaurants, setRestaurants] = useState([]);
  const [taste, setTaste] = useState({
    sweet: 0,
    salty: 0,
    sour: 0,
    bitter: 0,
  });

  const [selectedRestaurant, setSelectedRestaurant] = useState(null);
  const [isModalOpen, setIsModalOpen] = useState(false);

  useEffect(() => {
    fetch("http://localhost:3000/api/v1/restaurants")
      .then((response) => response.json())
      .then((data) => {
        setRestaurants(Array.isArray(data.data) ? data.data : [data.data]);
      });
  }, []);

  const findRestaurant = () => {
    // 사이트에 등록된 맛집이 하나도 없다면 null처리
    if (restaurants.length === 0) {
      return null;
    }
    // 가장 가까운맛집과 그 맛집까지의차이를 나타내는 최소값을 저장할 변수
    let closestRestaurant = restaurants[0];
    let minDiff = Infinity;


    // 각 맛집의 맛과 사용자가 선택한 맛 레벨간의 차이 계산하여 diff 변수에 누적
    for (let restaurant of restaurants) {
      let diff = 0;
      for (let tasteKey in taste) {
        diff += Math.abs(taste[tasteKey] - restaurant.taste_level);
      }

      // 맛집을 순회하면서 계산한 차이가 현재까지의 최소값보다 작을경우 최소값을 업데이트 가장 가까운 맛집을 찾음
      if (diff < minDiff) {
        minDiff = diff;
        closestRestaurant = restaurant;
      }
    }
    // 해당 맛집반환
    return closestRestaurant;
  };

  const handleSearch = () => {
    const closestRestaurant = findRestaurant();
    setSelectedRestaurant(closestRestaurant);
    setIsModalOpen(true);
  };

  const handleSave = (tasteKey, value) => {
    setTaste((taste) => ({ ...taste, [tasteKey]: value }));
  };

  const handleCloseModal = () => {
    setIsModalOpen(false);
  };

  return (
    <Container>
      <Circle className="sweet">단맛</Circle>
      <StyledRangeSlider onSave={(value) => handleSave("sweet", value)} />
      <Circle className="salty">짠맛</Circle>
      <StyledRangeSlider onSave={(value) => handleSave("salty", value)} />
      <Circle className="sour">신맛</Circle>
      <StyledRangeSlider onSave={(value) => handleSave("sour", value)} />
      <Circle className="bitter">쓴맛</Circle>
      <StyledRangeSlider onSave={(value) => handleSave("bitter", value)} />
      <SearchBtn className="search" onClick={handleSearch}>
        검색
      </SearchBtn>
      {isModalOpen && selectedRestaurant && (
        <Modal onClose={handleCloseModal}>
          <h1>{selectedRestaurant.restaurants_name}</h1>
          <h2>주소: {selectedRestaurant.address}</h2>
          <h2>전화번호: {selectedRestaurant.phone}</h2>
          <h2>영업 시간: {selectedRestaurant.opening_hours}</h2>
          <h2>별 점: {selectedRestaurant.rating}</h2>
          <ModalImage
            src={selectedRestaurant.image}
            alt={selectedRestaurant.restaurants_name}
          />
        </Modal>
      )}
    </Container>
  );
};

// 나머지 스타일 컴포넌트...

export default Service;

const Circle = styled.h1`
  background-color: ${(props) => {
    if (props.className === "sweet") {
      return "#ffb6c1";
    } else if (props.className === "salty") {
      return "#87CEFA";
    } else if (props.className === "sour") {
      return "#90EE90";
    } else if (props.className === "bitter") {
      return "#CD853F";
    }
  }};

  color: #fff;
  border-radius: 50%;
  padding: 10%;
  margin-bottom: 10px;
  margin-left: 100px;
  text-align: center;
  font-size: 18px;
  font-weight: bold;
  text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
  padding: 10%;
`;

const Container = styled.div`
  width: 18%;
  height: 100%;
  padding: 50px;
  margin-left: 30px;
  background-color: white;
  border-radius: 50px;
  box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
  display: flex;
  flex-direction: column;
  align-items: flex-start; /* 왼쪽 정렬 추가 */
  justify-content: center;
  gap: 10px; /* 컴포넌트 간 간격 추가 */
`;

// RangeSlider 스타일 조절
const StyledRangeSlider = styled(RangeSlider)`
  width: 80%;
  margin-bottom: 20px;
`;

// Button 스타일 만들어주기
const SearchBtn = styled.button`
  padding: 20px;
  background-color: #f7df1e; /* 배경색 변경 */
  color: black;
  border: none; /* 테두리 없애기 */
  border-radius: 10px;
  cursor: pointer;

  &:hover {
    background-color: #ffd700; /* hover 시 배경색 변경 */
    color: white; /* hover 시 텍스트 색상 변경 */
  }

  &:active {
    transform: translateY(2px); /* 클릭 시 버튼 아래로 약간 이동 */
  }
`;
// Service 컴포넌트를 내보냄
const ModalBackground = styled.div`
  position: fixed;
  top: 20px;
  left: 50px;
  width: 30%;
  height: 30%;

  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
`;

const ModalContent = styled.div`
  background-color: white;
  padding: 20px;
  border-radius: 10px;
`;
const ModalImage = styled.img`
  width: 50%; // 이미지 크기를 조절합니다. 원하는 크기로 변경 가능합니다.
  height: 50%; // 높이를 자동으로 설정하여 이미지 비율을 유지합니다.
`;

내가생각하는 맛게이지조절 기능의 방향성?

현재는 리커드척도 기법에서 영감을 얻어 게이지바로 직관적으로 사용자에게 각각의맛(단쓴신짠매)을 게이지로

직접 조정해서 뽑기를 뽑듯 식당의 정보를 뽑아내고있는데 

사실 사용자들이 진짜 자기가 어떤맛을 좋아하는지 모를수도있고 직관적으로 좋아하는 맛을 세부적으로 설정하라고하는것이

사용자에게 좋은 경험을 주기는 힘들다는 생각이 들었다. 

MBTI를 패러디한 맛 유형별 선호도 조사기능으로 바꿔보는게 좋지않을까?

그래서 좀더 쉽고 재미있게 사용자들로부터 좋아하는 맛의 음식을 조사하고자 MBTI처럼 
각각 음식을 소비하는 보고서 문항(ex : 질문 - 나는 더운날에 매운음식을먹고싶다 답 - 전혀 그렇지않다, 그렇지않다 , 보통이다, 그렇다, 매우그렇다) 처럼 보고서문항을통해 맛소비유형을분석하고 MBTI처럼 여러가지 단어키워드마다 성향을 지정해놓고 조합함으로써

각각 유저마다 맛선호도 mbti 호칭을 부여받게할수있도록 하고 이러한 부여받은호칭을 달고 서비스를 이용할수 있도록 하는것이

재미있고 더 사용자 경험을 증진시키지않을까라는 생각이 들었다

그러나 현재는 개인프로젝트가아닌 팀프로젝트이고 기본적으로 현재프로젝트의 기본적인기능들을 모두 구현한 상태는 아니며

전체적인 개발실력을 고려해서 내가 생각하는 아이디어를 프로젝트에 반영하는데 있어서 커뮤니케이션에서의 약간의 어려움을 느꼇다.
팀원들의 의견은 현재 할 수 있는 기능을 먼저 기초적인것부터 구현하자는 의견이기때문에 그에맞는 방향으로

현재 전체적인 프로젝트의 기능들을 모두 구현한뒤에 그 다음에 해당아이디어를 고려해봐야할것같다.

 

 

2) 로그인 기능 구현

해당 기능을 구현하는데 있어서 엄청난 어려움을느꼇다. 해보지않은 기능이였고

특히나 프론트엔드의 기초적인 기능들을 지난 학기에 구현해둔 상태였는데 로그인기능(인증)을 구현하는거 자체를 공부하는데 있어서 1차적인 어려움 2차적인 어려움으로는 로그인 이후 서비스를 이용하는 (인가)기능을 구현하는데 있어서 현재 엄청 머리를 굴리고있는중이다. 

먼저 현재 팀 프로젝트의 기술스택을 소개하자면 프론트엔드는 React로 백엔드 개발은nodejs-express프레임워크로 RDBMS로는 PostgreSql를 선택하였는데.

이전에 방학동안 진행해보았던 기술을 그대로 응용하고자하여 택하였고 

 

로그인 기능을 어떻게 구현해야할까에대해 먼저 해당 기능을 구현하기위한 기술들을 서칭해보았었다.

 

3월14일~3월21일 cookie - session

생활코딩의 쿠키-세션 영상으로 쿠키-세션에 대한 내용을 공부했다.

공부한내용들을 notion 문서에 정리해두려고 노력했다. (session은 영상만보고 정리를 끝까지 못했다..)

https://www.notion.so/Js-Javascript-b3209eea3435407ea933be2e4049c542

 

Js(Javascript) 관련 위키 | Notion

✏️ 공부내용을 기록합니다.

glimmer-honesty-f60.notion.site

3월21일~3월31일 JWT

쿠키와 세션으로 로그인기능을구현하기위해서 공부는 했는데 문제는 해당 공부내용을 실전인 현재 프로젝트에 도입하기위해

어떻게해야할지? 전혀 감이안오는 큰 문제가 존재했다.
팀프로젝트의 기술(PERN stack)에 맞게 직접적으로 클론코딩처럼 전체적으로 개발을하면서 로그인기능을 도입해보는 프로젝트영상을 참조하면서 우리프로젝트에 도입시켜야할것 같았다.
그러다가 해당 영상을 찾았고 영상을 보면서 어거지로 기능을 구현해보았다.

문제점

어떻게 기능을 구현해보긴했는데 아직 논리적으로 설명을 하지못한다.. 

해당영상을 참조하면서 다시 공부를 해야할것같다. 

또한 인증기능을구현하는것까지는 성공하였는데 인가(로그인후 서비스를이용할때 접근제어)를 어떻게 반영해야할지

생각해봐야할것같다.. 

원래는 로그인을 하지않더라도 메인홈화면에 접속할수있게하려고하다가 좀더 직관적으로 인증기능을 구현해보고자

첫화면을 로그인을 시도하는 화면으로 렌더링하도록 따로 빼놓은상태인데 첫화면을 디자인적으로

우리 프로젝트가 어떤 서비스인지 간단하게 소개하는내용과함께 로그인창을 두면 좋을것같은데

이 또한 고려해봐야할 문제이다..