기술공부/Next.js

Next.js기반 교회 웹사이트 제작 [12] - prisma(조회 실전적용)

helpilsang 2025. 3. 17. 01:51

자 우선 목표를 말해볼게

이 서브메뉴를 클릭 시

우선 간단하게 이렇게 화면이 나오게 할거야 

저번에 분리해논 구조를 먼저 보자 

1. src/app/leadership/page.js
    - 서브메뉴를 클릭하면 이동하는 page

2. component/Profile.js
    - leadership/page.js 에서 tab별 profile 정보를 보여주는 component 요소

 

여기에 추가적으로 서브메뉴를 클릭했을 때 어떤 화면이 클릭되었는지 좀 더 명확하게 보여줄 수 있게 sidebar를 만듬

우선 결과 페이지 부터 보여줄게

 

결과

왼쪽 sidebar랑 아직 header는 연결이 안됏어 profile만 작업된거니까 이부분 코드만 먼저 설명할게

1. src/app/leadership/page.js

import Profile from "@/components/Profile";
import prisma from "../../../prisma/client";
import Sidebar from "@/components/Sidebar";
export default async function Leadership() {
  let allUsers = [];
  let allCommons = [];

  try {
    allUsers = await prisma.USERS.findMany();
    allCommons = await prisma.COMMON.findMany({
      where: {
        PARENT_ID: "PS",
      },
    });
    console.log("All Users:", allUsers);
    console.log("All allCommons:", allCommons);
  } catch (error) {
    console.error("Error fetching users:", error);
    console.log("Prisma instance:", prisma);
  }

  return (
    <div className="flex justify-center w-full">
      {/* Sidebar */}
      <Sidebar />
      {/* Main Content */}
      <div className="flex">
        <Profile allUsers={allUsers} allCommons={allCommons} />
      </div>
    </div>
  );
}

짧게 간단 설명

  1. prisma 객체를 import 하여 사용
  2. findMany를 이용하여 USERS, COMMON 테이블의 전체 데이터 조회 (조건부 조회도 적용)
  3. component/Profile.js로 props 전달

 

2. component/Profile.js

"use client";
import { useState } from "react";

export default function Profile({ allUsers, allCommons }) {
  const [activeTab, setActiveTab] = useState("전체");

  /** common 테이블의 common_id와 users 테이블의 position이 같은 데이터를 찾아서 같은 데이터면 Common_nm을 반환하고 아니면 기타로 반환하는거야*/
  const getPositionName = (postion) => {
    const common = allCommons.find((common) => common.COMMON_ID === postion);
    return common ? common.COMMON_NM : "기타";
  };
  /**
   *  reduce 문법 설명
   *  배열의 요소를 하나의 값으로 줄이거나, 변환할 때 사용
   *  reduce((누적값, 현재값, 인덱스, 요소) => { return 결과 }, 초기값)
   *  ex_ const sum = [1, 2, 3, 4, 5].reduce((acc, cur) => acc + cur, 0); console.log(sum); // 15
   *  ex_2 const users = [{ id: 1, name: 'Alice'}, { id:2, name: 'Bob'}, {id: 3, name: 'Charlie'}];
   *  const ages = users.reduce((acc, cur) => { acc[cur.id] = acc.name;}, {}); console.log(args); // {1: 'Alice', 2: 'Bob', 3: 'Charlie'}
   *  이렇게 배열을 key-value 객체로 변환할때도 사용함
   *
   *  우리는 여기서 allusers와 allcommon에서 공통인 common_id와 position를 비교하여 common_nm을 가져온뒤
   *  common_nm을 key로 만들고 common_id가 같은 allusers의 user_nm, common_id의 common_nm, user_img를 reduce를통해 객체로 만들어야됨
   *
   *  여기서 아직 클라우드 스토리지에 파일이 올라가 있지 않아서 이미지 조회는 차후에 코드 업데이트 할 예정
   * **/

  const staffMembers = allUsers.reduce((acc, user) => {
    const staffName = getPositionName(user.POSITION);
    /** acc[직분]이 없으면 배열을 생성 */
    if (!acc[staffName]) {
      acc[staffName] = [];
    }
    /** acc[직분]에 user의 이름, 직분, 이미지를 추가 */
    acc[staffName].push({
      name: user.USER_NM,
      position: staffName,
      image: user.IMAGE ? user.IMAGE : "/images/leadership/default_profile.png",
    });
    return acc;
  }, {});

  /** Object.keys(객체)를 사용하면 객체의 key값만 가져올 수 있음 */
  const tabs = ["전체", ...Object.keys(staffMembers)];

  return (
    <div className="max-w-6xl mx-auto p-6">
      {/* Tabs */}
      <div className="grid grid-cols-8 mb-8 border-b">
        {tabs.map((tab) => (
          <button
            key={tab}
            className={`py-3 text-center text-lg font-medium transition-colors duration-200 ${
              activeTab === tab
                ? "text-blue-600 border-b-2 border-blue-600"
                : "text-gray-600 hover:text-blue-500"
            }`}
            onClick={() => setActiveTab(tab)}
          >
            {tab}
          </button>
        ))}
      </div>

      {/* Content Section */}
      <div className="mb-8">
        {/* Section Title */}
        <div className="flex items-center mb-6">
          <div className="w-2 h-6 bg-blue-600 mr-3"></div>
          <h2 className="text-xl font-bold text-blue-600">{activeTab}</h2>
        </div>

        {/*  
          이 부분은 grid를 사용하여 이미지와 이름, 직분을 보여주는 부분이야
          1. Object.values(객체)를 사용하면 객체의 value값만 가져올 수 있고
          2. flat()를 사용하면 2차원 배열을 1차원 배열로 flattening할 수 있음

          예시)
          1. Object.values

          const staffMembers = {
            위임목사: [
              { name: "서동욱", position: "목사" },
              { name: "김미옥", position: "사모" }
            ],
            원로목사: [
              { name: "박지성", position: "목사" }
            ],
            교육목사: [
              { name: "이영희", position: "목사" },
              { name: "최민수", position: "전도사" }
            ]
          };
          const staffMembers = Object.values(staffMembers);
          console.log(staffMembers);
          결과 :
          [
            [
              { name: "서동욱", position: "목사" },
              { name: "김미옥", position: "사모" }
            ],
            [
              { name: "박지성", position: "목사" }
            ],
            [
              { name: "이영희", position: "목사" },
              { name: "최민수", position: "전도사" }
            ]
          ]
          2. flat()

          const flatArr = staffMembers.flat();
          console.log(flatArr);
          결과 :
          [
            { name: "서동욱", position: "목사" },
            { name: "김미옥", position: "사모" },
            { name: "박지성", position: "목사" },
            { name: "이영희", position: "목사" },
            { name: "최민수", position: "전도사" }
          ]
        */}
        <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-6">
          {activeTab === "전체"
            ? Object.values(staffMembers)
                .flat()
                .map((member, index) => (
                  <div key={index} className="flex flex-col items-center">
                    <div className="w-40 h-48 overflow-hidden mb-3 border border-gray-200 rounded shadow-sm">
                      <img
                        src={member.image}
                        alt={member.name}
                        className="w-full h-full object-cover"
                      />
                    </div>
                    <h3 className="text-lg font-bold">{member.name}</h3>
                    <p className="text-gray-600">{member.position}</p>
                  </div>
                ))
            : staffMembers[activeTab]?.map((member, index) => (
                <div key={index} className="flex flex-col items-center">
                  <div className="w-40 h-48 overflow-hidden mb-3 border border-gray-200 rounded shadow-sm">
                    <img
                      src={member.image}
                      alt={member.name}
                      className="w-full h-full object-cover"
                    />
                  </div>
                  <h3 className="text-lg font-bold">{member.name}</h3>
                  <p className="text-gray-600">{member.position}</p>
                </div>
              ))}
        </div>
      </div>
    </div>
  );
}

여기는 props로 받아온 데이터를 내가 사용하기에 알맞은 데이터로 변환하고 세팅한 코드야 
코드에 예시랑 문법설명 찾아가면서 주석 달아놨으니까 참고해

내일도...강릉 ~ 양양 여행 예정이라 미리 부랴부랴 썻다

이게 참 프젝하랴 블로그 쓰랴  2개 같이하니까 진도가 너무 거북이네 그래도 나중에 몇번 더 보고 이해하면서 해야 안까묵겟지??

아 생각해보니 학교 인강안들었네 콩쥐야...ㅈ댔어
노트북 챙겨가서 밤에 인강좀 들어야겠다

콩쥐야 잣댓어...ㅜㅠㅜㅠㅜㅜㅠ

담에보자