API정보 연결 후 레이아웃 설계 및 내용 작성, TailwindCss를 사용한 디자인
: 상세페이지 정보는 KOPIS의 개발 PDF를 참고하여 상세데이터 API를 연결하였다. URL뒤에 개별 id를 붙여서 가져오는 방법으로 작성하였다.
XML데이터를 json으로 변환해주고 data정보는 zustand를 사용해 저장해주었다. axios를 사용하여 api를 불러오는 것은 detailApi.js에 저장시켰다.
// detailApi.js
import axios from "axios";
import { XMLParser } from "fast-xml-parser";
const fetchDetailData = async (Id) => {
const apiKey = import.meta.env.VITE_KOPIS_KEY; // Vite의 환경 변수 사용
const url = `http://kopis.or.kr/openApi/restful/pblprfr/${Id}?service=${apiKey}`;
try {
const response = await axios.get(url);
// XML 데이터를 JSON으로 변환
const parser = new XMLParser({
ignoreAttributes: false, // 속성 유지
attributeNamePrefix: "", // 속성명 앞에 접두어 없이 처리
});
const jsonData = parser.parse(response.data);
console.log(jsonData); // 변환된 JSON 데이터 콘솔 출력
return jsonData;
} catch (error) {
console.error("Error fetching performance details:", error);
throw new Error("데이터를 불러오는 중 오류가 발생했습니다.");
}
};
export default fetchDetailData;
// useKopisStore.js
import { create } from "zustand";
import fetchKopisDataById from "../api/detailApi";
const useKopisStore = create((set) => ({
data: null,
error: null,
fetchData: async (id) => {
try {
const result = await fetchKopisDataById(id); // API 호출
if (result.dbs.db) {
set({ data: result.dbs.db, error: null }); // 상태 업데이트
} else {
set({ error: "No data found", data: null });
}
} catch (error) {
set({ error: error.message, data: null }); // 상태 업데이트
}
},
}));
export default useKopisStore;
그리고 Detail.jsx 상세페이지에서 zustand에 있는 data를 사용해주었다.
// Detail. jsx
import React, { useEffect } from "react";
import Tabs from "./Tabs";
import useKopisStore from "../../zustand/kopisStore";
const DetailPage = () => {
const { data, fetchData, error } = useKopisStore((state) => ({
data: state.data,
fetchData: state.fetchData,
error: state.error,
}));
const id = "PF248932"; // 임시 ID
useEffect(() => {
fetchData(id);
}, [fetchData, id]);
if (error) {
return <div className="text-red-500">에러 발생: {error}</div>;
}
if (!data) {
return <div className="text-gray-500">Loading...</div>;
}
// 데이터가 유효한지 확인
const {
genrenm = "정보 없음",
prfnm = "정보 없음",
prfpdfrom = "정보 없음",
prfpdto = "정보 없음",
poster = "",
area = "정보 없음",
fcltynm = "정보 없음",
prfstate = "정보 없음",
pcseguidance = "정보 없음",
prfcast = "정보 없음",
entrpsnmH = "정보 없음",
entrpsnmS = "정보 없음",
prfruntime = "정보 없음",
} = data;
return (
<div className="w-full min-h-screen flex items-center justify-center p-4 ">
<div className="w-[1200px] p-6 ">
<div className="mb-12 border-b border-gray-800 pb-4 ">
<h2 className="text-sm font-semibold mb-4">{genrenm}</h2>
<h3 className="text-5xl font-extrabold mb-4">{prfnm}</h3>
<p className="text-sm mb-4">
{prfpdfrom} - {prfpdto}
</p>
</div>
<div className={`flex ${poster ? "gap-4" : ""} mb-4 `}>
{poster && (
<img
src={poster}
alt={prfnm}
className="w-full max-w-[430px] h-auto object-cover rounded-lg mr-8"
/>
)}
<div className=" w-[600px] ">
<div className="flex items-baseline mb-4 ">
<p className="w-20 font-bold flex-shrink-0 mr-4">출연진: </p>
<p className="flex-grow">{prfcast}</p>
</div>
<div className="flex items-baseline mb-4">
<p className="w-20 font-bold flex-shrink-0 mr-4">공연 장소: </p>
<p className="flex-grow">
{area} {fcltynm}
</p>
</div>
<div className="flex items-baseline mb-4">
<p className="w-20 font-bold flex-shrink-0 mr-4">공연 상태: </p>
<p className="flex-grow">{prfstate}</p>
</div>
<div className="flex items-baseline mb-4">
<p className="w-20 font-bold flex-shrink-0 mr-4">티켓 가격: </p>
<p className="flex-grow">{pcseguidance}</p>
</div>
<div className="flex items-baseline mb-4">
<p className="w-20 font-bold flex-shrink-0 mr-4">공연시간: </p>
<p className="flex-grow">{prfruntime}</p>
</div>
<div className="flex items-baseline mb-4">
<p className="w-20 font-bold flex-shrink-0 mr-4">주최사: </p>
<p className="flex-grow">
{entrpsnmH},{entrpsnmS}
</p>
</div>
</div>
</div>
<div className="w-full mt-16">
<Tabs />
</div>
</div>
</div>
);
};
export default DetailPage;
TailwindCss에 아직 익숙하지 않아 헷갈리는 것들을 정리해보았다.
className="" 안에 스타일을 지정하여 사용하는 방식이다.
mt => margin-top
pt => padding-top
이런식으로 단축된 용어를 사용하고 있다.
tailwind에선 일반적인 breakpoint을 기본값으로 제공한다.
sm 640px @media (min-width: 640px) { ... }
md 768px @media (min-width: 768px) { ... }
lg 1024px @media (min-width: 1024px) { ... }
xl 1280px @media (min-width: 1280px) { ... }
2xl 1536px @media (min-width: 1536px) { ... }
사용자가 직접 breakpoint 를 설정하는 것도 가능하다.
module.exports = {
theme: {
screens: {
'sm': '640px',
// => @media (min-width: 640px) { ... }
'md': '1024px',
// => @media (min-width: 1024px) { ... }
'lg': '1280px',
// => @media (min-width: 1280px) { ... }
},
}
}
// 데이터가 유효한지 확인
const {
genrenm = "정보 없음",
prfnm = "정보 없음",
prfpdfrom = "정보 없음",
prfpdto = "정보 없음",
poster = "",
area = "정보 없음",
fcltynm = "정보 없음",
prfstate = "정보 없음",
pcseguidance = "정보 없음",
prfcast = "정보 없음",
entrpsnmH = "정보 없음",
entrpsnmS = "정보 없음",
prfruntime = "정보 없음",
} = data;
로 data에 저장되어있는 값들을 구조분해할당 해와 처리해주었다.
이렇게 처리 후 데이터가 없어서 생기는 오류가 해결되었을 뿐 아니라 데이터의 정보를 꺼내쓸 때 좀 더 간편하게 쓸 수 있게 되었다.
즉 위의 상태에서 box에 flex-basis 값을 준 경우 box의 좌우 너비가 변하고, flex-direction: column인 경우 높이가 변한다.
이때, flex-shrink 속성을 0으로 설정하지 않는다면 내부 컨텐츠에 따라 유연한 크기를 갖는다.
초기값(default value)은 auto이다.
Flex Item의 flex-basis가 auto인 경우 width, height 속성이 우선한다.
Flex Item의 flex-basis가 auto가 아닌 경우, flex-basis 속성이 우선한다.
초기값(default value)은 0이고, 음수로의 설정은 불가능하다.
<div className=" w-[600px] ">
<div className="flex items-baseline mb-4 ">
<p className="w-20 font-bold flex-shrink-0 mr-4">출연진: </p>
<p className="flex-grow">{prfcast}</p>
</div>
<div className="flex items-baseline mb-4">
<p className="w-20 font-bold flex-shrink-0 mr-4">공연 장소: </p>
<p className="flex-grow">
{area} {fcltynm}
</p>
</div>
<div className="flex items-baseline mb-4">
<p className="w-20 font-bold flex-shrink-0 mr-4">공연 상태: </p>
<p className="flex-grow">{prfstate}</p>
</div>
<div className="flex items-baseline mb-4">
<p className="w-20 font-bold flex-shrink-0 mr-4">티켓 가격: </p>
<p className="flex-grow">{pcseguidance}</p>
</div>
<div className="flex items-baseline mb-4">
<p className="w-20 font-bold flex-shrink-0 mr-4">공연시간: </p>
<p className="flex-grow">{prfruntime}</p>
</div>
<div className="flex items-baseline mb-4">
<p className="w-20 font-bold flex-shrink-0 mr-4">주최사: </p>
<p className="flex-grow">
{entrpsnmH},{entrpsnmS}
</p>
</div>
</div>
</div>