벌써 3주차다. 이번 주에는 공용 모달이나 사이드바 숨김 기능 등등 여러가지 구현하였다.

 

지금까지는 무작정 코딩부터 했는데, 계획이 필요할 것 같아 디자인을 해보고자 피그마를 찾아보니 디자인 예시 파일들이 아주 많아서 참고하기 좋았다.

찾아본 피그마 예시 중 일부

 

저번 주차에 만든 공용 카드 컴포넌트를 컴파운드컴포넌트로 만들고싶어서 수정도 해보았는데 이해가 부족했던 것 같다.

앞으로 계속 더 수정해야할 것 같다. 공용 컴포넌트는 말그대로 공용이므로 활용하기 좋게 만들어야 하는데 어렵다. 

 

우리 팀 프로젝트는 전력 거래 관련하여 관리하는 웹 페이지인데 이 전력 거래 시스템이라는 것이 이해하기 어려워서 헤매고 있다.

smp나 rec에 대해서 좀 더 찾아보려고 한다.

이미 있는 다른 서비스들을 찾아보니까 조금 감이 잡혔다.

 

팀장님이 유경험자여서 많이 도움을 받고 있다. 다음 주에는 여유 되는 시간에 자바스크립트와 리액트 공부를 해야겠다.

 

감사하게도 팀수당이 넉넉히 주어졌기 때문에 이번 주부터는 주 2회 서울에 가서 회의에 참석하게 되었다.

아침 일찍 일어나서 가는 건 힘들지만, 확실히 만나서 하면 더 많이 하게 되긴 하는 것 같다.

단점은 진짜 일찍 일어나야 해서 피곤하다...

저번보다 팀원들과 친해진 것 같아서 좋다.

 

——————————————————————————

본 후기는 [유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 과정(B-log) 리뷰로 작성 되었습니다. #유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #Next.js #프론트엔드개발자양성과정 #개발자교육과정

 

 

2주차에는 슬슬 ui를 마무리하기로 했다.

로그인은 어떻게 구현할지 등등에 대해서 회의를 진행했다.

우리 조의 제일 큰 어려움은 백엔드 부분에 대한 지식이 없는 것이었다.

멘토님에게 질문 릴레이를 했다.

간단한 api도 구현해보았다.

 

 

 

 

 

에러 1. use client 붙여서 에러

콘솔에 반복출력 되어서 검색해보니 아래 방법으로 해결 가능했음

에러메시지: async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding 'use client' to a module that was originally written for the server.

에러를 잡는 마음가짐..

 

에러를 잡는 마음가짐..

supabase를 활용해 소셜로그인을 구현하던 중 client 컴포넌트에서 async/await을 사용할 수 없다는 경고가 발생했다 async/await is not yet supported in Client Components, only Server Components. This error is of

nninyeong.tistory.com

 

 

콘솔에 제대로 출력되었으므로 페이지에 띄워보자

[React] json-server를 이용한 json데이터 가져와서 화면에 보여주기

 

[React] json-server를 이용한 json데이터 가져와서 화면에 보여주기

보통 웹 애플리케이션을 만들 때는 백엔드에서 설계된 데이터베이스에서 데이터를 가져와서 화면에 보여주고 수정하는 등 CRUD를 통해서 동적으로 화면에 보여주는 것이 프론트엔드 개발자 역

velog.io

 

여기서 맵함수 가져옴,,

import getChartData from "./fetchData.mjs"

export default async function Profile() {
	const data = await getChartData()
	return (
		<div>
			Profile
			{data.map((data) => (
				<div>
					<h2>{data.Date}</h2>
					<p>{data.Value}</p>
				</div>
			))}
		</div>
	)
}

에러 2. map은 key가 필요하다

Rendering Lists – React

키는 각 구성 요소가 어떤 배열 항목에 해당하는지 React에게 알려주어 나중에 일치시킬 수 있도록 합니다. 배열 항목이 이동(예: 정렬로 인해), 삽입 또는 삭제될 수 있는 경우 이는 중요합니다. 잘 선택된 key 정확히 무슨 일이 일어났는지 추론하고 DOM 트리를 올바르게 업데이트하는 데 도움이 됩니다.

즉석에서 키를 생성하기보다는 데이터에 키를 포함시켜야 합니다

그리고 'undefined'일 수 있습니다 에러

 

에러

[typescript] 'undefined'일 수 있습니다.(error)

 

[typescript] 'undefined'일 수 있습니다.(error)

답도 없었다..데이터를 가져와야 그 속에 값이 있을텐데.. typescript 가 null, undefined에 너무 빡빡하다.if 를 이용하여 data가 있을 경우 에만 동작하도록 설정해주었다.

velog.io

 

import getChartData from "./fetchData.mjs"

export default async function Profile() {
	const data = await getChartData()
	return (
		<div>
			Profile
			{data
				? data.map((data) => (
						<div key={data.id}>
							<p>
								{data.Date} {data.Value}
							</p>
						</div>
					))
				: "no data"}
		</div>
	)
}

이렇게 수정

{data.id}는 아무 값 없다 ..

좋은 방법인진모름

임시방편일뿐

 

 

 

한글을 빼고싶어서 아래 코드를 추가

		// 현재 연도와 월 가져오기
		const currentYear = new Date().getFullYear()
		const currentMonth = new Date().getMonth() + 1 // getMonth()는 0부터 시작하므로 1을 더합니다.

		// JSON 변환 부분 수정
		const jsonNewData = todayData.map((line) => {
			const data = JSON.parse(
				line.replace("chartData.push(", "").replace(");", ""),
			)

			// 날짜 문자열 파싱
			const [day, hour] = data.Date.split(" ")
			const dayNumber = parseInt(day)

			// 새로운 Date 객체 생성
			const date = new Date(
				currentYear,
				currentMonth - 1,
				dayNumber,
				parseInt(hour),
			)

			// 날짜를 원하는 형식으로 포맷팅
			const formattedDate = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:00`

			return { ...data, Date: formattedDate }
		})

 

 

 

 


rec 정보 데이터 크롤링도 해보았다.

브라우저에 따라서 trim() 함수가 동작하지 않는 경우가 있습니다.이런 경우에는 replace() 함수와 정규식을 사용하여 앞뒤의 공백을 제거할 수 있습니다.

출처:https://hianna.tistory.com/337 [어제 오늘 내일:티스토리]

 

 

잘 출력된 모습

https://myung-ho.tistory.com/109

 

[node.js] nodejs로 크롤링(crawling) 시작하기

1. 크롤링이란? 크롤링(crawling) 혹은 스크레이핑(scraping)은 웹 페이지를 그대로 가져와서 거기서 데이터를 추출해 내는 행위다. 크롤링하는 소프트웨어는 크롤러(crawler)라고 부른다. 출처: 나무위

myung-ho.tistory.com

https://arikong.tistory.com/12

 

웹 크롤링 해보기 #1 - NodeJS, cheerio (feat. VueJS)

모두가 필수 코스로 해본다는 크롤링, 제가 해보겠습니다. 링크 ① 웹 크롤링 해보기 #1 - NodeJS, cheerio (feat. VueJS) ② 웹 크롤링 해보기 #2 - NodeJS, puppeteer 오늘은 웹 크롤링(스크래핑)에 대한 내용입

arikong.tistory.com

위 문서들을 참고했다.

 

처음하다보니 뭔가 바로바로 구현되지 않아서 힘들다.

각 페이지에 무슨 기능을 넣어야하는지, 배치는 어떻게 해야하는지 아직 감이 잘 오지 않는다.

어떻게든 완성만 하면 좋겠다.

 

——————————————————————————

본 후기는 [유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 과정(B-log) 리뷰로 작성 되었습니다. #유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #Next.js #프론트엔드개발자양성과정 #개발자교육과정

 

 

 


이번 주는 github 협업에 적응하고, 개발을 슬슬 시작해보는 시간이었다.


prettier 설정도 잘못돼있었고, tailwind css 설정 파일의 css 적용 범위가 잘못되어있는 바람에 헤더 만들때 간단한 작업임에도 시간이 많이 걸렸다.

 

https://stackoverflow.com/questions/74584091/how-to-get-the-current-pathname-in-the-app-directory-of-next-js

 

How to get the current pathname in the app directory of Next.js?

I'm using the experimental app folder in Next.js 13, where they have replaced next/router with next/navigation, so I imported the useRouter hook accordingly. I do not see the property pathname in ...

stackoverflow.com

 

 

https://uiverse.io/vinodjangid07/good-donkey-28

 

Input by vinodjangid07 made with CSS | Uiverse.io

This Input was posted by vinodjangid07. Tagged with: input, message, send, image, upload, file. You can create your own elements by signing up.

uiverse.io

 

 

React input태그에서 Enter키로 이벤트 활성화하기

 

[JavaScript] 값 입력 후 엔터(Enter)키 눌렀을 때 이벤트 실행

 

React input태그에서 Enter키로 이벤트 활성화하기

 

위 문서들을 참고하여

서치데이터 콘솔에 띄워보기까지 완료

useParams 쓰려고 시도해보다가 서치 다시 해보니 id 말고 문자열로 된 링크는 usePathName 쓰면 되는 것을 알게 되었다.

함수도 onKeyPress로 적었는데 onKeyDown으로 수정해야한다.

이건 다음에 새로운 브랜치에서 작업하기로 한다.

지금에서야 드는 생각인데 useEffect로 바꿔도 될거같다.


이후 props활용해 거래 페이지를 위한 카드 컴포넌트도 직접 작성해봤는데 css가 어려웠고, css적용을 위해 컴포넌트를 어떻게 나눌지, props로 어떻게 css 설정을 가져올지 찾아보고 하느라 어려웠지만 일단 레이아웃은 의도대로 나왔고, 나쁘지 않아 보였다.

css는 다음에 한번 갈아 엎긴 해야할 것 같다..


테이블 작업은 따로 문서 작성하였다.

https://developmiran.tistory.com/11

 

[Next.js] shadcn/ui 로 데이터테이블 만들기

시작하기에 앞서, shadcn/ui로 진행하게 된 이유shadcn/ui 가 mui보다 기능이 적어보여 고민하던 중 아래 글을 보게 되었다. 테이블을 편하게, Tanstack-table 사용하기 테이블을 편하게, Tanstack-table 사용

developmiran.tistory.com

 




강의 한번 더 보고 리팩토링 해봐야지.



풀리퀘스트 잘못보내고 그랬는데 경험자이신 팀장님께서 열심히 알려주셔서 감사했다.

우리 조는 제대로 된 협업 경험이 많이 없어서 시작이 좀 느리지만 열심히 해서 잘 마무리하고싶다.

나만 느린 것일수도 ...

 

 

 

 

——————————————————————————

본 후기는 [유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 과정(B-log) 리뷰로 작성 되었습니다. #유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #Next.js #프론트엔드개발자양성과정 #개발자교육과정

 

이번 주 수업 내용

 

이번 주는 supabase 코드를 hook으로 빼고 로그인, 로그아웃 등의 기능을 구현하였다.

 

npm install jotai

 

 

import { atom } from "jotai";
import { Task } from "@/types";

/** Supabase에 저장되어 있는 'todos' (tasks) 테이블 내에 있는 모든 데이터 조회 */
/** 전체 tasks 목록 조회 */
export const tasksAtom = atom<Task[]>([]);

/** 단일(개별) task 상태 */
export const taskAtom = atom<Task | null>(null);

 


jotai라는 상태 관리 라이브러리를 이용해 task data를 tasksAtom이라는 전역상태에 할당하게 만들었다.

useState와 비슷한 구조를 가지고 있다.

 

https://velog.io/@ggong/%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-jotai

 

상태 관리를 위한 라이브러리 jotai 훑어보기

이번에 신규 프로젝트 개발을 하면서 React-query와 함께 jotai를 도입하기로 했다.전에 작업하면서는 거의 리덕스로 모든 데이터를 전역에 저장하고 썼었는데, 전역 상태를 관리하고 업데이트 하기

velog.io

 

더보기

1. atom

const priceAtom = atom(10000);

atom 을 생성하고 괄호 안에 초기값을 넣을 수 있다.

 

2. useAtom(read/write)

const [price, setPrice] = useAtom(priceAtom);

생성된 atom 을 불러와서 useState 와 동일하게 사용 가능하다.

 

3. useSetAtom(write)

const setPrice = useSetAtom(priceAtom);

생성된 atom 의 값을 update 만 할 때 사용한다.

 

4. useAtomValue(read)

const price = useAtomValue(priceAtom);

생성된 atom 의 값을 read 만 할 때 사용한다.

 

useSetAtom, useAtomValue 는 각각 읽기, 쓰기만 가져와서 사용하기 때문에 useAtom 과 다르게 재랜더링 하지 않는 장점이 있다.

 

출처 https://liebe97.tistory.com/49

 

React Jotai 사용법 총 정리(1) - Jotai는 무엇이고 어떻게 사용하나요?

React Jotai 사용법 총 정리(2) - atom 이해 및 기본 유용한 기능 ( https://liebe97.tistory.com/51 ) React Jotai 사용법 총 정리(3) - useAtom & provider 사용법 ( https://liebe97.tistory.com/52 ) 앞으로 프로젝트 리팩토링을

liebe97.tistory.com

 

 

use~로 시작하는 파일은 리액트가 hook으로 자동 인식한다.

 

 

"use client";
import { useRouter } from "next/navigation";
import { useToast } from "@/hooks/use-toast";
import { supabase } from "@/lib/supabase";
import { useAtom } from "jotai";
import { tasksAtom } from "@/stores/atoms";

function useCreateTask() {
    const router = useRouter();
    const { toast } = useToast();
    const [  , setTasks] = useAtom(tasksAtom); // task 안쓰니까 없앰

    const createTask = async () => {
        try {
            const { data, status, error } = await supabase
                .from("todos")
                .insert([
                    {
                        title: null,
                        start_date: null,
                        end_date: null,
                        boards: [],
                    },
                ])
                .select();
            if (data && status === 201) {
                /** 올바르게 tasks 테이블에 ROW 데이터 한 줄이 올바르게 생성되면 tasksAtom에 할당한다. */
                setTasks((prevTasks) => [...prevTasks, data[0]]); // Jotai의 tasksAtom 상태 업데이트
                toast({
                    title: "새로운 TASK가 생성되었습니다.",
                    description: "나만의 TODO-BOARD를 생성해보세요!",
                });
                router.push(`/board/${data[0].id}`);
            }

            if (error) {
                toast({
                    variant: "destructive",
                    title: "에러가 발생했습니다.",
                    description: `Supabase 오류: ${error.message || "알 수 없는 오류"}`,
                });
            }

        } catch (error) {
            /** 네트워크 오류나 예기치 않은 에러를 잡기 위해 catch 구문 사용 */
            console.error(error);
            toast({
                variant: "destructive",
                title: "네트워크 오류",
                description: "서버와 연결할 수 없습니다. 다시 시도해주세요!",
            });
        }
    };

    return createTask;
}

export {useCreateTask};

 

 

"use client";

import { useToast } from "@/hooks/use-toast";
import { supabase } from "@/lib/supabase";
import { useAtom } from "jotai";
import { tasksAtom } from "@/stores/atoms";

function useGetTasks() {
    const { toast } = useToast();
    const [tasks, setTasks] = useAtom(tasksAtom);

    /** 하단의 코드에서 Supabase에서 error를 반환함에도 불구하고 try-catch 구문을 사용하는 이유
     * async-await 구문에서 비동기 로직을 처리할 경우, try-catch는 주로 비동기 함수에서 발생할 수 있는 예외를 처리하기 위해 사용됩니다.
     * 만약, getTasks 함수 내에서 await한 API 호출이나 네트워크 요청에서 에러가 발생한다면, 그 오류는 자동으로 예외를 발생할 수 있습니다.
     * 그럴 경우, 예외를 잡아내지 않으면 프로그램이 중단되거나 예상치 못한 오류가 발생할 수 있습니다.
     */

    const getTasks = async () => {
        try {
            const { data, status, error } = await supabase
                .from("todos")
                .select("*");

            if (data && status === 200) {
                setTasks(data);
                toast({
                    title: "task loaded",
                    description: "새로운 TASK가 생기시면 언제든 추가해주세요!",
                });
            }

            if (error) {
                toast({
                    variant: "destructive",
                    title: "에러가 발생했습니다.",
                    description: `Supabase 오류: ${error.message || "알 수 없는 오류"}`,
                });
            }
        } catch (error) {
            console.error(error);
            toast({
                variant: "destructive",
                title: "에러",
                description: "에러발생",
            });
        }
    };

    return { tasks, getTasks };
}

export { useGetTasks };

 

"use client";

import { useRouter } from "next/navigation";
import { supabase } from "@/lib/supabase";
import { useToast } from "@/hooks/use-toast";

function useDeleteTask() {
    const router = useRouter();
    const { toast } = useToast();

    const deleteTask = async (taskId: number) => {
        try {
            const { status, error } = await supabase
                .from("todos")
                .delete()
                .eq("id", taskId);

            if (status === 204) {
                toast({
                    title: "해당 task를 삭제했습니다",
                    description: "새로운 task를 생성해보세요",
                });
                router.push("/");
            }

            if (error) {
                /** 네트워크 오류나 예기치 않은 에러를 잡기 위해 catch 구문 사용 */
                toast({
                    variant: "destructive",
                    title: "에러가 발생했습니다.",
                    description: `Supabase 오류: ${error.message || "알 수 없는 오류"}`,
                });
            }
        } catch (error) {
            console.error(error);
            toast({
                variant: "destructive",
                title: "에러",
                description: "에러발생",
            });
        }
    };

    return deleteTask;
}

export { useDeleteTask };

 

"use client";

import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui";
import { useDeleteTask } from "@/hooks/api";
import { useParams } from "next/navigation";

interface Props {
    children: React.ReactNode;
}

function AlertPopup({ children }: Props) {
    const { id } = useParams();

    const handleDeleteTask = useDeleteTask();

    return (
        <AlertDialog>
            <AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
            <AlertDialogContent>
                <AlertDialogHeader>
                    <AlertDialogTitle>해당 TASK를 정말로 삭제하시겠습니까?</AlertDialogTitle>
                    <AlertDialogDescription>
                        이 작업이 실행되면 다시 취소할 수 없습니다.
                        <br /> 삭제가 진행되면 귀하의 게시물은 영구적으로 삭제됩니다.
                    </AlertDialogDescription>
                </AlertDialogHeader>
                <AlertDialogFooter>
                    <AlertDialogCancel>취소</AlertDialogCancel>
                    <AlertDialogAction onClick={() => handleDeleteTask(Number(id))}  className="bg-red-600 hover:bg-rose-600">삭제</AlertDialogAction>
                </AlertDialogFooter>
            </AlertDialogContent>
        </AlertDialog>
    );
}

export { AlertPopup };

 

onClick={() => handleDeleteTask(Number(id))}

 

https://ej-development-note.tistory.com/80

 

[JavaScript] 이벤트 - 이벤트 객체, this 키워드

이벤트 이벤트(event)는 웹 브라우저와 사용자 사이에 상호작용이 발생하는 특정 시점을 의미한다. 자바스크립트로 이벤트 종류에 따른 제어 작업을 할 수 있다. 1. 이벤트 종류 구분 이벤트 설명

ej-development-note.tistory.com

 

상태코드

200 ok

204 no content

 

REST API 관점에서 바라보는 HTTP 상태 코드(HTTP status code)

 

REST API 관점에서 바라보는 HTTP 상태 코드(HTTP status code)

REST API 관점에서 바라보는 HTTP 상태 코드(HTTP status code) TOC Introduction HTTP 와 REST HTTP Status Code 2XX Success 4.1. 200 OK 4.2. 201 Created 4.3. 202 Accepted 4.4. 204 No Content 4XX Client errors 5.1. 400 Bad Request 5.2. 401 Unauthori

sanghaklee.tistory.com

 

hook 분리하고 데이터 저장 기능이나 로그인 기능도 만들었다.

로그인 기능은 단순히 로그인 기능을 만드는 것 뿐만 아니라 페이지 접근 권한 또한 관리해주어야 해서 꽤나 복잡한 기능이었다.

아직 정리가 덜되어 자료 링크만 첨부하고 다음주 내로 다시 정리해야겠다.

 

 

https://lopunko.notion.site/Part-3-Supabase-Auth-1ba2621a1b5345f3b02d2059d47ac7a3

 

Part 3. Supabase Auth 소개 및 인증방식 기획 | Notion

Supabase Auth

lopunko.notion.site

 

 

https://velog.io/@sanghyeon/Supabase%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%9D%B8%EC%A6%9D-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

 

Supabase를 이용하여 로그인 인증 구현하기

[풀스택 완성] Supabase로 웹사이트 3개 클론하기 (Next.js 14)

velog.io

 

 

https://www.youtube.com/watch?v=7FemRsuDdGg

 

https://supabase.com/docs/reference/javascript/auth-admin-deleteuser

 

JavaScript: Delete a user | Supabase Docs

shouldSoftDelete (Required) If true, then the user will be soft-deleted (setting `deleted_at` to the current timestamp and disabling their account while preserving their data) from the auth schema. Defaults to false for backward compatibility. This functio

supabase.com

 

 

 

프로젝트 조 배정 & 활동 준비

 

팀 배정이 발표되어서 팀 서류 작성 등등 팀활동을 했다!

어쩌다보니 내가 총무를 하게 되어서 카뱅 계좌도 하나 팠다..

잘 할수 있을지 자신은 없지만 다음주부터 활동 시작이니 화이팅.

 

 

——————————————————————————

본 후기는 [유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 과정(B-log) 리뷰로 작성 되었습니다.

#유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #Next.js #프론트엔드개발자양성과정 #개발자교육과정

이번 주 진행한 프로젝트

1. 리액트 & api 이용해 이미지검색하는 사이트 만들기

2. 넥스트js 이용해 supabase 데이터베이스 연동하여 todo리스트 만들기

 

1번 프로젝트는 많이 놓쳐서 다음 주 내에 정리해야할거같다.. 🥹

2번 프로젝트에서 css랑 ui는 다음에 해도 될 것 같고 supabase연동 부분 위주로 정리해보았다.

 

 

 

 

feat: supabase 연동 - add new page 기능 만들고 aside에 페이지 목록 불러오기

 

Database | Supabase Docs

 

Database | Supabase Docs

Use Supabase to manage your data.

supabase.com

 

 

 

"use client";

import { Button } from "@/components/ui";
import { supabase } from "@/lib/supabase";


function InitPage() {
    const handleCreateTask = async () => {
        console.log("버튼동작");

        const { data, error } = await supabase
            .from("boards")
            .insert([{ some_column: "someValue", other_column: "otherValue" }])
            .select();
    };

    return (
        <div className="w-full h-full flex flex-col items-center justify-center">
            <div className="flex flex-col items-center justify-center gap-5 mb-6">
                <h3 className="scroll-m-20 text-2xl font-semibold tracking-tight">
                    How to start:
                </h3>
                <div className="flex flex-col items-center gap-3">
                    <small className="text-sm font-normal leading-none">
                        1. Create a page
                    </small>
                    <small className="text-sm font-normal leading-none">
                        2. Add boards to page
                    </small>
                </div>
            </div>
            <Button
                className="text-[#E79057] bg-transparent border border-[#E79057] hover:bg-[#FFF9F5] w-[180px]"
                onClick={handleCreateTask}
            >
                Add New Page
            </Button>
        </div>
    );
}

export default InitPage;

 

const { data, error } = await supabase
            .from("boards")
            .insert([{ title: null, start_date: null, end_date: null , boards: null}])
            .select();
    };

 

 

 

"use client";

import { Button } from "@/components/ui";
import { toast } from "@/hooks/use-toast";
import { supabase } from "@/lib/supabase";


function InitPage() {
    const handleCreateTask = async () => {
        console.log("버튼동작");
        try {
            const { data, status, error } = await supabase
                .from("todos")
                .insert([
                    { title: "", start_date: null, end_date: null, boards: null },
                ])
                .select();

            console.log(data);

            if (status === 201 && data) {
                /** TOAST UI 띄우기 */
                // 설치코드: npx shadcn@latest add toast
                toast({
                    title: "새로운 투두리스트가 생성",
                    description: "수파베이스확인",
                });
            }
        } catch (error) {
            console.error(error);
            toast({
                variant: "destructive",
                title: "새로운 투두리스트가 생성",
                description: "개발자 도구창을 확인하세요",
            });
        }
    };
    return (
    ---------------------------생략---------------------------
    );
}

export default InitPage;

 

 

 

타입정의

export interface Task {
    id: number;
    title: string;
    startDate: Date;
    endtDate: Date;
    boards: Board;
}

export interface Board {
    id: string; //테이블분리할경우 타입변경될수이승ㅁ
    title: string;
    startDate: Date;
    endtDate: Date;
    contenr: string;
    isCompleted: boolean;
}

 

"use client";

/** 컴포넌트 */
import { Button, SearchBar } from "@/components/ui";
import { toast, useToast } from "@/hooks/use-toast";
import { supabase } from "@/lib/supabase";
import { useRouter } from "next/navigation";



function AsideSection() {
    const { toast } = useToast();
    const router = useRouter();

    /** 페이지목록- todos 전체 */
    const getTasks = async()=>{
        const { data: boards, error } = await supabase
        .from('boards')
        .select('*')
    };

    /** add new page */
    const handleCreateTask = async () => {
        console.log("버튼동작");
        try {
            const { data, status, error } = await supabase
                .from("todos")
                .insert([
                    { title: "", start_date: null, end_date: null, boards: null },
                ])
                .select();

            console.log(data);

            if (status === 201 && data) {
                /** TOAST UI 띄우기 */
                // 설치코드: npx shadcn@latest add toast
                toast({
                    title: "새로운 투두리스트가 생성",
                    description: "수파베이스확인",
                });
                router.push(`/board/${data[0].id}`);
            }
        } catch (error) {
            console.error(error);
            toast({
                variant: "destructive",
                title: "새로운 투두리스트가 생성",
                description: "개발자 도구창을 확인하세요",
            });
        }
    };

    return (
        <aside className="page__aside">
            {/* 검색창 UI */}
            <SearchBar placeholder="검색어를 입력하세요." />
            {/* Add New Page 버튼 UI */}
            <Button className="text-[#E79057] bg-white border border-[#E79057] hover:bg-[#FFF9F5]" onClick={handleCreateTask}>Add New Page</Button>
            {/* TODO 목록 UI 하나 */}
            <div className="flex flex-col mt-4 gap-2">
                <small className="text-sm font-medium leading-none text-[#A6A6A6]">9Diin의 TODO-BOARD</small>
                <ul className="flex flex-col">
                    <li className="bg-[#F5F5F5] min-h-9 flex items-center gap-2 py-2 px-[10px] rounded-sm text-sm cursor-pointer">
                        <div className="h-[6px] w-[6px] rounded-full bg-[#00F38D]"></div>
                        등록된 제목이 없습니다.
                    </li>
                </ul>
            </div>
        </aside>
    );
}

export { AsideSection };

 

function AsideSection() {
    const { toast } = useToast();
    const router = useRouter();

    const [tasks, setTasks] = useState<Task[]>([]);

    /** 페이지목록- todos 전체 */
    const getTasks = async()=>{
        try {
            const { data, status } = await supabase.from('todos').select('*');

            if (status === 200 && data !== null) setTasks(data);
        } catch (error) {
            console.error(error);
            toast({
                variant: "destructive",
                title: "에러",
                description: "에러발생",
            });
        }
    };

    /** add new page */
    const handleCreateTask = async () => {
        console.log("버튼동작");
        try {
            const { data, status, error } = await supabase
                .from("todos")
                .insert([
                    { title: "", start_date: null, end_date: null, boards: null },
                ])
                .select();

            console.log(data);

            if (status === 201 && data) {
                /** TOAST UI 띄우기 */
                // 설치코드: npx shadcn@latest add toast
                toast({
                    title: "새로운 투두리스트가 생성",
                    description: "수파베이스확인",
                });
                router.push(`/board/${data[0].id}`);
            }
        } catch (error) {
            console.error(error);
            toast({
                variant: "destructive",
                title: "새로운 투두리스트가 생성",
                description: "개발자 도구창을 확인하세요",
            });
        }
    };


    useEffect(()=>{
        getTasks();
    });
  return (
        <aside className="page__aside">
            {/* 검색창 UI */}
            <SearchBar placeholder="검색어를 입력하세요." />
            {/* Add New Page 버튼 UI */}
            <Button className="text-[#E79057] bg-white border border-[#E79057] hover:bg-[#FFF9F5]" onClick={handleCreateTask}>Add New Page</Button>
            {/* TODO 목록 UI 하나 */}
            <div className="flex flex-col mt-4 gap-2">
                <small className="text-sm font-medium leading-none text-[#A6A6A6]">9Diin의 TODO-BOARD</small>
                <ul className="flex flex-col">
                    { tasks.length===0?(<li className="bg-[#F5F5F5] min-h-9 flex items-center gap-2 py-2 px-[10px] rounded-sm text-sm cursor-pointer">
                        <div className="h-[6px] w-[6px] rounded-full bg-[#00F38D]"></div>
                        등록된 제목이 없습니다.
                    </li>) :(tasks.map((task: Task)=>{
                        return (
                            <li className="bg-[#F5F5F5] min-h-9 flex items-center gap-2 py-2 px-[10px] rounded-sm text-sm cursor-pointer">
                        <div className="h-[6px] w-[6px] rounded-full bg-[#00F38D]"></div>
                        {task.title ? task.title: "제목 없음"}
                    </li>
                        );
                    }))  }
                    
                </ul>
            </div>
        </aside>
    );
}

 

 

 

 

 

const { toast } 하면 머가다르지

→에러뜸!

 

 

https://velog.io/@chaerin00/React-%EC%9D%98-%EC%9D%98%EB%AF%B8

 

React { }의 사용

React에서 굳이 궁금해하지 않았던 { }가 쓰이는 규칙👨‍🏫

velog.io

 

 

"use client";

/** 컴포넌트 */
import { Button, SearchBar } from "@/components/ui";
import { toast, useToast } from "@/hooks/use-toast";
import { supabase } from "@/lib/supabase";
import { Task } from "@/types";
import { useRouter, useParams } from "next/navigation";
import { useEffect, useState } from "react";

function AsideSection() {
    const { toast } = useToast();
    const router = useRouter();
    const { id }= useParams();
    const [tasks, setTasks] = useState<Task[]>([]);

    /** 페이지목록- todos 전체 */
    const getTasks = async () => {
        try {
            const { data, status } = await supabase.from("todos").select("*");

            if (status === 200 && data !== null) setTasks(data);
        } catch (error) {
            console.error(error);
            toast({
                variant: "destructive",
                title: "에러",
                description: "에러발생",
            });
        }
    };

    /** add new page */
    const handleCreateTask = async () => {
        console.log("버튼동작");
        try {
            const { data, status, error } = await supabase
                .from("todos")
                .insert([
                    {
                        title: "",
                        start_date: null,
                        end_date: null,
                        boards: null,
                    },
                ])
                .select();

            console.log(data);

            if (status === 201 && data) {
                /** TOAST UI 띄우기 */
                // 설치코드: npx shadcn@latest add toast
                toast({
                    title: "새로운 투두리스트가 생성",
                    description: "수파베이스확인",
                });
                router.push(`/board/${data[0].id}`);
            }
        } catch (error) {
            console.error(error);
            toast({
                variant: "destructive",
                title: "생성실패",
                description: "개발자 도구창을 확인하세요",
            });
        }
    };

    useEffect(() => {
        getTasks();
    }, []);

    return (
        <aside className="page__aside">
            {/* 검색창 UI */}
            <SearchBar placeholder="검색어를 입력하세요." />
            {/* Add New Page 버튼 UI */}
            <Button
                className="text-[#E79057] bg-white border border-[#E79057] hover:bg-[#FFF9F5]"
                onClick={handleCreateTask}>
                Add New Page
            </Button>
            {/* TODO 목록 UI 하나 */}
            <div className="flex flex-col mt-4 gap-2">
                <small className="text-sm font-medium leading-none text-[#A6A6A6]">
                    고마11의 TODO-board
                </small>
                <ul className="flex flex-col">
                    {tasks.length ? (
                        tasks.map((task: Task) => {
                            return (
                                <li
                                    key={task.id}
                                    className={`${ task.id === Number(id) ? `bg-[#F5F5F5]` : `bg-transparent` }
                                    min-h-9 flex items-center gap-2 py-2 px-[10px] rounded-sm text-sm cursor-pointer`}
                                    onClick={() =>
                                        router.push(`/board/${task.id}`)
                                    }
                                >
                                    <div
                                        className={`${ task.id === Number(id) ? `bg-[#00F38D]` : `bg-neutral-400` }
                                        h-[6px] w-[6px] rounded-full`}> 
                                    </div>
                                    <span
                                        className={`${ task.id === Number(id) ? `` : `text-neutral-400` }`} >
                                        {task.title ? task.title : "등록된 TASK 제목이 없습니다."}
                                    </span>
                                </li>
                            );
                        })
                    ) : (
                        <li className="bg-[#F5F5F5] text-neutral-400 w-full flex items-center justify-center min-h-9  gap-2 py-2 px-[10px] rounded-sm text-sm cursor-pointer">
                            생성된 TASK가 없습니다.
                        </li>
                    )}
                </ul>
            </div>
        </aside>
    );
}

export { AsideSection };

 

useParams로 링크의 id관리하기

→ 그 id가 parameter임

 

[React] useParams() 사용하여 파라미터 가져오기

 

[React] useParams() 사용하여 파라미터 가져오기

리액트에서 라우터 사용 시 파라미터 정보를 가져와 활용하고 싶다면 useParams()라는 훅을 사용하면 된다. 라우터를 사용하고 있다는 가정 하에 useParams 사용 방법에 대해 알아보도록 하겠다.1) useP

velog.io

→해결은

 

 

feat: 게시물(Task) 페이지 기능 개발- 특정 id 값에 따른 TASK 데이터 불러오기 select "*”

feat: 게시물(Task) 페이지 기능 개발- 특정 id 값에 따른 TASK 데이터 불러오기 select "*" · nrkcode/next-next-app-supabase@b4ac53e · GitHub

 

feat: 게시물(Task) 페이지 기능 개발- 특정 id 값에 따른 TASK 데이터 불러오기 select "*" · nrkcode/next-ne

nrkcode committed Nov 23, 2024

github.com

import { toast, useToast } from "@/hooks/use-toast";
import { supabase } from "@/lib/supabase";
import { Task } from "@/types";
import { useRouter, useParams } from "next/navigation";
import { useEffect, useState } from "react";

function BoardPage() {
    const { toast } = useToast();
    const router = useRouter();
    const { id }= useParams();
    const [task, setTask] = useState<Task[]>([]);

    /** 특정 id 값에 따른 TASK 데이터 */
    const getTask = async () => {
        try {
            const { data, status } = await supabase.from("todos").select("*");

            if (status === 200 && data !== null) setTask(data);
        } catch (error) {
            console.error(error);
            toast({
                variant: "destructive",
                title: "에러",
                description: "에러발생",
            });
        }
    };

    /** Board Card 생성 및 데이터베이스에 저장 */
    const handleCreateBoard  = async () => {};
    
    useEffect(() => {
        getTask();
    }, []);
    
    ...
    <h3 className="scroll-m-20 text-2xl font-semibold tracking-tight">There is no board yet.</h3>
                    <small className="text-sm font-medium leading-none text-[#6D6D6D] mt-3 mb-7">Click the button and start flashing!</small>
                    <button onClick={handleCreateBoard}>

 

 

 

[feat] 게시물(Task) 페이지 기능 개발
Add New Board 버튼 클릭 시, BoardCard 생성

 

   const [boards, setBoards] = useState<Board[]>(task?.boards || []);
    
  
  /** Board Card 생성 및 데이터베이스에 저장 */
    const handleCreateBoard  = async () => {
        const newBoard: Board = {
            id: nanoid(), // 추후에 Supabase boards 컬럼을 다른 테이블로 분리할 경우, 타입이 변경될 수 있음
            title: "",
            startDate: null,
            endtDate: null,
            content: "",
            isCompleted: false,
        };
        setBoards((prevBoards) => [...prevBoards, newBoard]);
    };

 

task?.boards를 가져와서 setBoards함수를 통해 boards에 넣음

boards를 수파베이스로 보냄

 



    const updateTaskOneColumnByID = async () => {
        try {
            const {data, status} = await supabase.from("todos").update({boards:boards}).eq("id",id);
        } catch (error) {
            console.error(error);
        }
    }

 

 

 

 

이번주 후기

이번 주는 돌아가며 자기소개도 해서 다른 분들의 이름을 알게 되었다.

온라인 강의실에 대답을 다들 안하셔서 머쓱하지만 나라도.. 라는 마음으로 열심히 대답하고 있다..

리액트랑 넥스트 강의를 빨리 완강해야 할텐데..

수업시간에 빨리빨리 타자 쳐서 따라가야겠다.

아예 모르는 걸 하려니까 어렵다ㅜ

열심히 합시다.

 

 

——————————————————————————

본 후기는 [유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 과정(B-log) 리뷰로 작성 되었습니다.

#유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #Next.js #프론트엔드개발자양성과정 #개발자교육과정

비수도권 거주자라 온라인 zep으로 참여했다.

 

일주일 학습 요약

1주차에는 자바스크립트를 간단히 배우고 리액트 프로젝트 2개를 같이 만들었다.

 

cd desktop
npm create vite@latest
Project name: » vite-project (프로젝트 이름은 본인이 원하는 이름으로 생성)
Select a framework: » React
Select a variant: » TypeScript
cd 생성한 프로젝트명
npm install
code . -r

 

 

https://ui.shadcn.com/docs/installation/vite

 

Vite

Install and configure Vite.

ui.shadcn.com

shadcn UI를 이용할 것이라서 위 문서를 보고 테일윈드 깔아주었다.

npm install -D tailwindcss postcss autoprefixer

npx tailwindcss init -p

VSCode 오류 - npx : 이 시스템에서 스크립트를 실행할 수 없으므로 ...

npx오류로 구글링하여 해결하였다.

 

axios와 jotai도 활용하였다

npm install react-router-dom
npm install localforage match-sorter sort-by
npm install sass --save-dev
npm install react-kakao-maps-sdk
npm install axios
npm install jotai

 

지도 생성하기 | react-kakao-maps-sdk docs

 

지도 생성하기 | react-kakao-maps-sdk docs

지도를 생성하는 가장 기본적인 예제입니다.

react-kakao-maps-sdk.jaeseokim.dev

 

 

import { Map } from "react-kakao-maps-sdk";
import useKakaoLoader from "@/hooks/useKakaoLoader";
import { Card } from "@/components";

function GetKakaoMapWidget() {
    useKakaoLoader()

    return (
        <Card className="w-1/4 min-w-[25%] h-full">
            {/* 지도를 표시할 컨테이너 */}
            <Map
                id="map"
                center={{
                    /** 지도의 중심좌표 */
                    lat: 37.5683,
                    lng: 126.9778,
                }}
                style={{
                    /** 지도의 크기 */
                    width: "100%",
                    height: "100%",
                    borderRadius: "8px",
                }}
                /** 지도의 확대 레벨 */
                level={13}
            />
        </Card>
    );
}

export { GetKakaoMapWidget };

 

use state와 props를 이용한 데이터 관리, 날씨api연결

function HomePage() {
    const [cityName, setCityName] = useAtom(cityNameAtom);


    const [weatherData,setWeatherData] = useState(defaultWeatherData);
    const [tideData, setTideData] = useState<ForecastTideDay>(defaultTideData);
    const [oneWeekWeatherSummary, setOneWeekWeatherSummary] = useState([]);

    const APP_KEY = "/** 앱키 */";
    const BASE_URL = "http://api.weatherapi.com/v1";

    const fetchApi= async ()=>{
        
        try{
            const res = await axios.get(`${BASE_URL}/forecast.json?q=${cityName}&days=7&key=${APP_KEY}`) ;
            console.log(res);

            if (res.status === 200){
                setWeatherData(res.data);
            }

        } catch(error){
            console.error(error);
        }finally{
            console.log("fetchApi 호출은 되었습니다.");
        }

    };
    
    const fetchTideApi= async ()=>{
//길어져서 생략
    };
    
    const getOneWeekWeather = async ()=>{
        
        try{
            /** Promise 인스턴스 방법을 사용했을 땐, resolve에 해당 */  
            const res = await axios.get(`${BASE_URL}/forecast.json?q=${cityName}&days=7&key=${APP_KEY}`) ;
            console.log(res);

            if (res.status === 200 && res.data ){
                const newData=res.data.forecast.forecastday.map((item: ForecastDay)=>
                {
                    return{
                        maxTemp: Math.round(item.day.maxtemp_c),
                        minTemp: Math.round(item.day.mintemp_c),
                        date: item.date_epoch,
                        iconCode: item.day.condition.code,
                        isDay: item.day.condition.icon.includes("day"),
                    };
            });
            setOneWeekWeatherSummary(newData);
        }
        } catch(error){
            /** Promise 인스턴스 방법을 사용했을 땐, resolve에 해당 */  
            console.error(error);
        }

    };
    

    useEffect(()=>{
        fetchApi();
        fetchTideApi();
        getOneWeekWeather();
    }, [cityName])

 

다음 프로젝트도 비슷하게 진행했다. 한 번 해보고 나니 더 수월하게 진행할 수 있었다.

 

과제

 

ui 이미지만 보고 혼자서 해 봤던 코드!

https://github.com/nrkcode/React-hw

 

GitHub - nrkcode/React-hw

Contribute to nrkcode/React-hw development by creating an account on GitHub.

github.com

 

 

깃허브 연결 과제는 이미 깃허브를 많이 사용해보아서 쉽게 해결했다.

프롭스에서 html 특수문자를 string {""}형식으로 전송하니 문자코드 그대로 출력되었는데, 중괄호를 벗기니까 원래 의도한 ℃로 잘 출력되었다.

props 받는 코드를 수정하거나 조건문을 달거나 하려고 여러 시도를 해 보았는데 괄호 제거라는.. 생각보다 간단한 방법으로 해결되었다.

https://ko.legacy.reactjs.org/docs/jsx-in-depth.html

 

JSX 이해하기 – React

A JavaScript library for building user interfaces

ko.legacy.reactjs.org

prop에 대해 알아보자!! 하고 찾아본 문서에서 힌트를 얻었다. 역시 공식문서를 보는 습관이 들어야겠다.

 

 

스크롤바 없애기는 알려주신 방법 말고 다른 방법을 구글링해서 찾아보았다.

 

 

npm: tailwind-scrollbar-hide

 

tailwind-scrollbar-hide

tailwindcss plugin for hide scrollbar. Latest version: 1.1.7, last published: 3 years ago. Start using tailwind-scrollbar-hide in your project by running `npm i tailwind-scrollbar-hide`. There are 95 other projects in the npm registry using tailwind-scroll

www.npmjs.com

 

npm install tailwind-scrollbar-hide

 

// tailwind.config.js
module.exports = {
  theme: {
    // ...
  },
  plugins: [
    require('tailwind-scrollbar-hide')
    // ...
  ]
}

 

 

어려웠던 점 & 더 찾아본 정보들

 

동기/비동기함수&화살표함수

function a() {
    console.log("1");

    setTimeout( () => {
        console.log("2");
    }, 1000);

    console.log("3");
}

a();

화살표 함수로 적어도 된다

const a = () => {
    console.log("1");

    setTimeout( () => console.log("2"), 1000);

    console.log("3");
}

a();

이렇게도 된다

자바스크립트 동기/비동기함수 할 때 좀 놓쳐서 자습을 더 했다

 

코드 템플릿 단축키가 정말 유용해서 더 찾아봤다!

https://velog.io/@rgfdds98/React-snippets-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%A2%85%EB%A5%98%EB%B3%84-%EB%8B%A8%EC%B6%95%ED%82%A4

 

 

 

일주일 해 보고 느낀 점은,

하루에 8시간 수업이다 보니 한 번 놓치면 따라가기 쉽지 않고 제때 복습을 해야 잊어버리지 않겠다는 점이다.

주말에 문서를 정리하다 보니 상세한 부분이 잘 기억이 나지 않아서 다음 주 부터는 가능하다면 하루에 한 번 일지를 써봐도 좋을 것 같다.

노션에라도 정리해봐야겠다.

그리고 자바스크립트, css 지식이 너무 부족하다는 것을 느꼈다.

알려주시는 부분이라도 열심히 습득하고 주말에 추가적인 공부를 꼭 더 해야겠다..

주말에 추가 공부 하기 위해서는 진도 나간 부분의 공부는 평일에 꼭 끝내야겠다.

 

——————————————————————————

본 후기는 본 후기는 [유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 과정(B-log) 리뷰로 작성 되었습니다.

#유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #Next.js #프론트엔드개발자양성과정 #개발자교육과정

+ Recent posts