ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,66 @@
+import { useQuery } from "@tanstack/react-query";
+import { getCountries } from "../api/countryAPI";
+import CountryCard from "./CountryCard";
+import { useEffect } from "react";
+import { Country } from "../types/country";
+import useCountryStore from "../zustand/countryStore";
+
+const CountryList = ({ isDone }: { isDone: boolean }) => {
+  const { countries, setCountries, favoriteCountries, setFavoriteCountries } =
+    useCountryStore();
+
+  const { data, error, isLoading } = useQuery({
+    queryKey: ["countries"],
+    queryFn: getCountries,
+  });
+
+  const handleToggleCountry = (country: Country) => {
+    if (isDone) {
+      setFavoriteCountries(
+        favoriteCountries.filter((toggle) => toggle.cca3 !== country.cca3)
+      );
+      setCountries([country, ...countries]);
+    } else {
+      setCountries(countries.filter((toggle) => toggle.cca3 !== country.cca3));
+      setFavoriteCountries([country, ...favoriteCountries]);
+    }
+  };
+
+  useEffect(() => {
+    if (data) {
+      setCountries(data);
+    }
+  }, [data, setCountries]);
+
+  if (isLoading) {
+    return (
+      <div className="flex justify-center items-center">
+        <h1 className="text-4xl">로딩중입니다!!</h1>
+      </div>
+    );
+  }
+
+  if (error) {
+    return (
+      <div className="flex justify-center items-center">
+        <h1 className="text-4xl">에러가 발생했습니다!</h1>
+      </div>
+    );
+  }
+
+  const isDoneCountries = isDone ? favoriteCountries : countries;
+
+  return (
+    <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8 px-8">
+      {isDoneCountries.map((country) => (
+        <CountryCard
+          key={country.cca3}
+          country={country}
+          handleToggleCountry={handleToggleCountry}
+        />
+      ))}
+    </div>
+  );
+};
+
+export default CountryList;

리뷰: 이 부분은 home.tsx에서는 전체적인 UI 및 페이지 관련만 나타내고 List에서 Card를 핸들링 할 수 있도록 생각을 해서 로직을 구현했는데 이런 핸들링 로직은 home.tsx가 더 적절하단 말씀이실까여??
@@ -0,0 +1,15 @@
+import axios from "axios";
+import { Country } from "../types/country";
+
+export const countryApi = axios.create({
+  baseURL: "https://restcountries.com/v3.1",
+});
+
+export const getCountries = async (): Promise<Country[]> => {
+  try {
+    const response = await countryApi.get("/all");
+    return response.data;
+  } catch (error) {
+    throw new Error();
+  }
+};

리뷰: 아 그렇군여! 저는 일단 js하듯이 작성 한 후에 strict모드가 설정 되어있으니 ts에서 새로 발생한 오류에 맞춰 수정했어가지고 반환값만 명시하고 오류가 사라졌길래 그 부분은 미처 생각을 못했습니다.
아무래도 모든 값에 정확한 타입을 명시하는건 저도 해야겠다 생각했는데 감사합니다!
@@ -0,0 +1,66 @@
+import { useQuery } from "@tanstack/react-query";
+import { getCountries } from "../api/countryAPI";
+import CountryCard from "./CountryCard";
+import { useEffect } from "react";
+import { Country } from "../types/country";
+import useCountryStore from "../zustand/countryStore";
+
+const CountryList = ({ isDone }: { isDone: boolean }) => {
+  const { countries, setCountries, favoriteCountries, setFavoriteCountries } =
+    useCountryStore();
+
+  const { data, error, isLoading } = useQuery({
+    queryKey: ["countries"],
+    queryFn: getCountries,
+  });
+
+  const handleToggleCountry = (country: Country) => {
+    if (isDone) {
+      setFavoriteCountries(
+        favoriteCountries.filter((toggle) => toggle.cca3 !== country.cca3)
+      );
+      setCountries([country, ...countries]);
+    } else {
+      setCountries(countries.filter((toggle) => toggle.cca3 !== country.cca3));
+      setFavoriteCountries([country, ...favoriteCountries]);
+    }
+  };
+
+  useEffect(() => {
+    if (data) {
+      setCountries(data);
+    }
+  }, [data, setCountries]);
+
+  if (isLoading) {
+    return (
+      <div className="flex justify-center items-center">
+        <h1 className="text-4xl">로딩중입니다!!</h1>
+      </div>
+    );
+  }
+
+  if (error) {
+    return (
+      <div className="flex justify-center items-center">
+        <h1 className="text-4xl">에러가 발생했습니다!</h1>
+      </div>
+    );
+  }
+
+  const isDoneCountries = isDone ? favoriteCountries : countries;
+
+  return (
+    <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8 px-8">
+      {isDoneCountries.map((country) => (
+        <CountryCard
+          key={country.cca3}
+          country={country}
+          handleToggleCountry={handleToggleCountry}
+        />
+      ))}
+    </div>
+  );
+};
+
+export default CountryList;

리뷰: prop 타입을 인라인으로 정의한 이유가 있는지 궁금합니다!
@@ -0,0 +1,66 @@
+import { useQuery } from "@tanstack/react-query";
+import { getCountries } from "../api/countryAPI";
+import CountryCard from "./CountryCard";
+import { useEffect } from "react";
+import { Country } from "../types/country";
+import useCountryStore from "../zustand/countryStore";
+
+const CountryList = ({ isDone }: { isDone: boolean }) => {
+  const { countries, setCountries, favoriteCountries, setFavoriteCountries } =
+    useCountryStore();
+
+  const { data, error, isLoading } = useQuery({
+    queryKey: ["countries"],
+    queryFn: getCountries,
+  });
+
+  const handleToggleCountry = (country: Country) => {
+    if (isDone) {
+      setFavoriteCountries(
+        favoriteCountries.filter((toggle) => toggle.cca3 !== country.cca3)
+      );
+      setCountries([country, ...countries]);
+    } else {
+      setCountries(countries.filter((toggle) => toggle.cca3 !== country.cca3));
+      setFavoriteCountries([country, ...favoriteCountries]);
+    }
+  };
+
+  useEffect(() => {
+    if (data) {
+      setCountries(data);
+    }
+  }, [data, setCountries]);
+
+  if (isLoading) {
+    return (
+      <div className="flex justify-center items-center">
+        <h1 className="text-4xl">로딩중입니다!!</h1>
+      </div>
+    );
+  }
+
+  if (error) {
+    return (
+      <div className="flex justify-center items-center">
+        <h1 className="text-4xl">에러가 발생했습니다!</h1>
+      </div>
+    );
+  }
+
+  const isDoneCountries = isDone ? favoriteCountries : countries;
+
+  return (
+    <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8 px-8">
+      {isDoneCountries.map((country) => (
+        <CountryCard
+          key={country.cca3}
+          country={country}
+          handleToggleCountry={handleToggleCountry}
+        />
+      ))}
+    </div>
+  );
+};
+
+export default CountryList;

리뷰: 아...원래 data로 mpa을 돌렸었는데...;; 이게 isDone값을 꼭 써보려고 노력하다보니 어쩌다 그렇게 되었습니다 ㅠㅠ... 말씀대로 지금 쿼리가 초기값 넣어주는거말고 딱히 안쓰이긴합니다..
@@ -0,0 +1,66 @@
+import { useQuery } from "@tanstack/react-query";
+import { getCountries } from "../api/countryAPI";
+import CountryCard from "./CountryCard";
+import { useEffect } from "react";
+import { Country } from "../types/country";
+import useCountryStore from "../zustand/countryStore";
+
+const CountryList = ({ isDone }: { isDone: boolean }) => {
+  const { countries, setCountries, favoriteCountries, setFavoriteCountries } =
+    useCountryStore();
+
+  const { data, error, isLoading } = useQuery({
+    queryKey: ["countries"],
+    queryFn: getCountries,
+  });
+
+  const handleToggleCountry = (country: Country) => {
+    if (isDone) {
+      setFavoriteCountries(
+        favoriteCountries.filter((toggle) => toggle.cca3 !== country.cca3)
+      );
+      setCountries([country, ...countries]);
+    } else {
+      setCountries(countries.filter((toggle) => toggle.cca3 !== country.cca3));
+      setFavoriteCountries([country, ...favoriteCountries]);
+    }
+  };
+
+  useEffect(() => {
+    if (data) {
+      setCountries(data);
+    }
+  }, [data, setCountries]);
+
+  if (isLoading) {
+    return (
+      <div className="flex justify-center items-center">
+        <h1 className="text-4xl">로딩중입니다!!</h1>
+      </div>
+    );
+  }
+
+  if (error) {
+    return (
+      <div className="flex justify-center items-center">
+        <h1 className="text-4xl">에러가 발생했습니다!</h1>
+      </div>
+    );
+  }
+
+  const isDoneCountries = isDone ? favoriteCountries : countries;
+
+  return (
+    <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8 px-8">
+      {isDoneCountries.map((country) => (
+        <CountryCard
+          key={country.cca3}
+          country={country}
+          handleToggleCountry={handleToggleCountry}
+        />
+      ))}
+    </div>
+  );
+};
+
+export default CountryList;

리뷰: 저도 궁금합니다..!!!
@@ -0,0 +1,84 @@
+export interface Country {
+  name: {
+    common: string;
+    official: string;
+    nativeName: {
+      [key: string]: {
+        official: string;
+        common: string;
+      };
+    };
+  };
+  tld: string[];
+  cca2: string;
+  ccn3: string;
+  cca3: string;
+  cioc: string;
+  independent: boolean;
+  status: string;
+  unMember: boolean;
+  currencies: {
+    [key: string]: {
+      name: string;
+      symbol: string;
+    };
+  };
+  idd: {
+    root: string;
+    suffixes: string[];
+  };
+  capital: string[];
+  altSpellings: string[];
+  region: string;
+  subregion: string;
+  languages: {
+    [key: string]: string;
+  };
+  translations: {
+    [key: string]: {
+      official: string;
+      common: string;
+    };
+  };
+  latlng: number[];
+  landlocked: boolean;
+  area: number;
+  demonyms: {
+    eng: {
+      f: string;
+      m: string;
+    };
+  };
+  flag: string;
+  maps: {
+    googleMaps: string;
+    openStreetMaps: string;
+  };
+  population: number;
+  gini: {
+    [key: string]: number;
+  };
+  fifa: string;
+  car: {
+    signs: string[];
+    side: string;
+  };
+  timezones: string[];
+  continents: string[];
+  flags: {
+    png: string;
+    svg: string;
+  };
+  coatOfArms: {
+    png?: string;
+    svg?: string;
+  };
+  startOfWeek: string;
+  capitalInfo: {
+    latlng: number[];
+  };
+  postalCode: {
+    format: string;
+    regex: string;
+  };
+}

리뷰: 저도 사실 모든 데이터를 다 보지 않았지만, 몇몇 데이터는 선택적으로 들어오는 것 같더라구요..!! 그래서 모든 데이터에 다 들어오는 것이 아니라면 옵셔널을 붙여주면 좋을 것 같습니다..!!
@@ -0,0 +1,89 @@
+package store.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+import store.model.Order;
+import store.model.Promotion;
+import store.model.Receipt;
+import store.model.Store;
+import store.service.StoreService;
+import store.view.InputView;
+import store.view.OutputView;
+
+public class StoreController {
+    private final InputView inputView;
+    private final OutputView outputView;
+    private final StoreService storeService;
+
+    public StoreController(InputView inputView, OutputView outputView, StoreService storeService) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+        this.storeService = storeService;
+    }
+
+    public void run() {
+        List<Store> store = printProductList();
+        List<Promotion> promotions = loadPromotions();
+        Order order;
+
+        do {
+            order = purchaseProduct(store);
+            priceCalculator(store, order, promotions);
+
+        } while (!checkEnd(store, order));
+    }
+
+    private boolean checkEnd(List<Store> store, Order order) {
+        while (true) {
+            try {
+                if (!storeService.isAnswer(inputView.inputEndMessage())) {
+                    return true;
+                }
+                updateStore(store, order);
+                return false;
+            } catch (IllegalArgumentException e) {
+                outputView.printError(e.getMessage());
+            }
+        }
+    }
+
+    private void updateStore(List<Store> store, Order order) {
+        storeService.checkStock(store, order);
+        outputView.printProductList(store);
+    }
+
+    private void priceCalculator(List<Store> store, Order order, List<Promotion> promotions) {
+        List<Receipt> receipts = storeService.parseReceipt(store, order, promotions);
+        storeService.checkTribeQuantity(receipts, store);
+        storeService.checkTribePromotion(receipts, store);
+        int discount = storeService.membershipDiscount(receipts);
+
+        outputView.printReceipt(receipts, discount);
+    }
+
+    private List<Store> printProductList() {
+        List<Store> store = new ArrayList<>();
+        storeService.loadProductsForm(store);
+        outputView.printProductList(store);
+        return store;
+    }
+
+    private List<Promotion> loadPromotions() {
+        List<Promotion> promotions = new ArrayList<>();
+        storeService.loadPromotionsForm(promotions);
+        return promotions;
+    }
+
+    private Order purchaseProduct(List<Store> store) {
+        while (true) {
+            try {
+                String inputOrder = inputView.inputPurchaseProduct();
+                Order order = new Order(inputOrder);
+                storeService.checkProduct(store, order);
+                return order;
+            } catch (IllegalArgumentException e) {
+                outputView.printError(e.getMessage());
+            }
+        }
+    }
+}

리뷰: order를 do 내부에서 선언하는 것이 더 깔끔하지 않을까 생각이 드네요..!
checkEnd() 내부의 updateStore() 를 do 구문 내부로 이동시킨다면 가능할 것 같습니다..!
@@ -0,0 +1,59 @@
+package store.model;
+
+import static store.utils.ErrorMessage.INVALID_FORMAT;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Order {
+    private List<Product> order;
+
+    public Order(String inputOrder) {
+        validateProductFormat(inputOrder);
+        this.order = generateProducts(inputOrder);
+    }
+
+    public List<Product> generateProducts(String inputOrder) {
+        List<Product> products = new ArrayList<>();
+        String[] orders = inputOrder.split("],\\[");
+
+        for (String order : orders) {
+            String formatOrder = order.replace("[", "").replace("]", "");
+            int quantityIndex = formatOrder.lastIndexOf('-') + 1;
+
+            String name = formatOrder.substring(0, quantityIndex - 1).trim();
+            String quantity = formatOrder.substring(quantityIndex).trim();
+
+            products.add(new Product(name, quantity));
+        }
+        return products;
+    }
+
+    private void validateProductFormat(String inputOrder) {
+        String[] orderList = inputOrder.split(",");
+
+        for (String order : orderList) {
+            if (!isValidProductEntry(order.trim())) {
+                throw new IllegalArgumentException(INVALID_FORMAT);
+            }
+        }
+    }
+
+    private boolean isValidProductEntry(String order) {
+        if (!order.startsWith("[") || !order.endsWith("]")) {
+            return false;
+        }
+
+        String part = order.substring(1, order.length() - 1);
+        String[] parts = part.split("-");
+        if (parts[0].isEmpty()) {
+            return false;
+        }
+
+        return parts.length == 2;
+    }
+
+    public List<Product> getOrder() {
+        return order;
+    }
+}

리뷰: replaceAll() 이라는 메서드로도 같은 기능을 구현할 수 있습니다!
이미 아실수도 있지만 혹시나 해서 말씀드립니다..!
@@ -0,0 +1,73 @@
+package store.model;
+
+import camp.nextstep.edu.missionutils.DateTimes;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+public class Promotion {
+    private String name;
+    private int buy;
+    private int get;
+    private String startDateStr;
+    private String endDateStr;
+
+    public Promotion(String name, int buy, int get, String startdateStr, String enddateStr) {
+        this.name = name;
+        this.buy = buy;
+        this.get = get;
+        this.startDateStr = startdateStr;
+        this.endDateStr = enddateStr;
+    }
+
+    public static Promotion parsePromotions(String line) {
+        List<String> promotion = Arrays.asList(line.split(","));
+
+        String name = promotion.get(0).trim();
+        int buy = Integer.parseInt(promotion.get(1).trim());
+        int get = Integer.parseInt(promotion.get(2).trim());
+        String start_date = promotion.get(3).trim();
+        String end_date = promotion.get(4).trim();
+
+        return new Promotion(name, buy, get, start_date, end_date);
+    }
+
+    public int checkPromotionDate() {
+        final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
+
+        try {
+            Date startDate = DATE_FORMAT.parse(startDateStr);
+            Date endDate = DATE_FORMAT.parse(endDateStr);
+            Date now = DATE_FORMAT.parse(String.valueOf(DateTimes.now()));
+
+            if (!now.before(startDate) && !now.after(endDate)) {
+                return this.buy;
+            }
+        } catch (ParseException e) {
+            return 0;
+        }
+        return 0;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    public String getStartDateStr() {
+        return startDateStr;
+    }
+
+    public String getEndDateStr() {
+        return endDateStr;
+    }
+}

리뷰: `now.after(startDate) && now.before(endDate)` 도 같은 의미인데
현재 코드처럼 작성하신 이유가 있으실까요?
궁금해서 질문드립니다..!
@@ -0,0 +1,14 @@
+package store.utils;
+
+public class ErrorMessage {
+    public static final String ERROR = "[ERROR] ";
+    public static final String RETRY = " 다시 입력해 주세요.";
+    public static final String INVALID_FORMAT = " 올바르지 않은 형식으로 입력했습니다. ";
+    public static final String INVALID_NAME = " 존재하지 않는 상품입니다.";
+    public static final String INVALID_QUANTITY = "재고 수량을 초과하여 구매할 수 없습니다.";
+    public static final String INVALID_INPUT = "잘못된 입력입니다.";
+
+    private ErrorMessage() {
+
+    }
+}

리뷰: enum을 사용하지 않고 class로 분리한 이유가 궁금합니다
@@ -0,0 +1,89 @@
+package store.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+import store.model.Order;
+import store.model.Promotion;
+import store.model.Receipt;
+import store.model.Store;
+import store.service.StoreService;
+import store.view.InputView;
+import store.view.OutputView;
+
+public class StoreController {
+    private final InputView inputView;
+    private final OutputView outputView;
+    private final StoreService storeService;
+
+    public StoreController(InputView inputView, OutputView outputView, StoreService storeService) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+        this.storeService = storeService;
+    }
+
+    public void run() {
+        List<Store> store = printProductList();
+        List<Promotion> promotions = loadPromotions();
+        Order order;
+
+        do {
+            order = purchaseProduct(store);
+            priceCalculator(store, order, promotions);
+
+        } while (!checkEnd(store, order));
+    }
+
+    private boolean checkEnd(List<Store> store, Order order) {
+        while (true) {
+            try {
+                if (!storeService.isAnswer(inputView.inputEndMessage())) {
+                    return true;
+                }
+                updateStore(store, order);
+                return false;
+            } catch (IllegalArgumentException e) {
+                outputView.printError(e.getMessage());
+            }
+        }
+    }
+
+    private void updateStore(List<Store> store, Order order) {
+        storeService.checkStock(store, order);
+        outputView.printProductList(store);
+    }
+
+    private void priceCalculator(List<Store> store, Order order, List<Promotion> promotions) {
+        List<Receipt> receipts = storeService.parseReceipt(store, order, promotions);
+        storeService.checkTribeQuantity(receipts, store);
+        storeService.checkTribePromotion(receipts, store);
+        int discount = storeService.membershipDiscount(receipts);
+
+        outputView.printReceipt(receipts, discount);
+    }
+
+    private List<Store> printProductList() {
+        List<Store> store = new ArrayList<>();
+        storeService.loadProductsForm(store);
+        outputView.printProductList(store);
+        return store;
+    }
+
+    private List<Promotion> loadPromotions() {
+        List<Promotion> promotions = new ArrayList<>();
+        storeService.loadPromotionsForm(promotions);
+        return promotions;
+    }
+
+    private Order purchaseProduct(List<Store> store) {
+        while (true) {
+            try {
+                String inputOrder = inputView.inputPurchaseProduct();
+                Order order = new Order(inputOrder);
+                storeService.checkProduct(store, order);
+                return order;
+            } catch (IllegalArgumentException e) {
+                outputView.printError(e.getMessage());
+            }
+        }
+    }
+}

리뷰: 함수명은 동사로 하는 건 어떨까요?? 예를 들어 `calculatePrice` 처럼 말이죠!!
@@ -0,0 +1,91 @@
+package store.model;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class Store {
+    private final String name;
+    private int price;
+    private int quantity;
+    private String promotion;
+
+    public Store(String name, int price, int quantity, String promotion) {
+        this.name = name;
+        this.price = price;
+        this.quantity = quantity;
+        this.promotion = promotion;
+    }
+
+    public static Store parseStore(String line) {
+        List<String> product = Arrays.asList(line.split(","));
+
+        String name = product.get(0).trim();
+        int price = Integer.parseInt(product.get(1).trim());
+        int quantity = Integer.parseInt(product.get(2).trim());
+        String promotion = checkPromotion(product.get(3));
+
+        return new Store(name, price, quantity, promotion);
+    }
+
+    private static String checkPromotion(String promotion) {
+        if (promotion.equals("null")) {
+            return "";
+        }
+        return promotion.trim();
+    }
+
+    public static void checkNotPromotionProduct(List<Store> store, String line) {
+        if (store.isEmpty()) {
+            return;
+        }
+
+        Store product = store.getLast();
+        if (!line.contains(product.getName()) && !product.getPromotion().isEmpty()) {
+            store.add(new Store(product.getName(), product.getPrice(), 0, ""));
+        }
+    }
+
+    public int deduction(int orderQuantity) {
+        int value = 0;
+        quantity -= orderQuantity;
+
+        if (quantity < 0) {
+            value = Math.abs(quantity);
+            quantity = 0;
+        }
+        return value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getPrice() {
+        return price;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public String getPromotion() {
+        return promotion;
+    }
+
+    @Override
+    public String toString() {
+        String formatPrice = String.format("%,d", price);
+        if (quantity == 0) {
+            return "- " +
+                    name + " " +
+                    formatPrice + "원 " +
+                    "재고 없음" +
+                    promotion;
+        }
+        return "- " +
+                name + " " +
+                formatPrice + "원 " +
+                quantity + "개 " +
+                promotion;
+    }
+}

리뷰: 각각의 인덱스가 어떤 것을 의미하는지 상수화 했으면 더 좋았을 것 같습니다!!!