ํฐ์คํ ๋ฆฌ ๋ทฐ
ํ๋ฃจ ์ข ์ผ ๊ฑธ๋ ธ๋ visibility.. ๊ฟ์์๋ ๋์ฌ ๋จ์ด
2025.02.24 (์) Works
MBTI ํ ์คํธ ๊ฒฐ๊ณผ ํ์ด์ง๋ฅผ ๊ตฌํํด์ผ ํ๋ค.
๊ฐ์ ์๋ฒ๋ฅผ ์ด์ฉํ๋ ์ฌ๋๋ค์ ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ์๋ณผ ์ ์์ผ๋ฉฐ ์์ฑ์๋ ์์ ์ ๊ฒฐ๊ณผ๋ง์ ๊ณต๊ฐ / ๋น๊ณต๊ฐ / ์ญ์ ์ฒ๋ฆฌ ํ ์ ์๋ค.
์ด๋ ๋ค๋ฅธ ์ฌ๋์ ๋ธ๋ผ์ฐ์ ์๋ ๋ฐ์๋์ด์ผ ํ๋ฏ๋ก ํ๋ฉด์์๋ง ๋์ํ๋ ๊ฒ์ด ์๋ DB์ ๋ณ๋ ์ฌํญ์ ์ ์ฉํด์ผ ํ๋ค.

json-server
(ํด๋ฆญ) ์ด์ ์ ์์ฑํ JWT ์ธ์ฆ ๋ฐฉ๋ฒ์ ํ ํฐ์ ์ด์ฉํ์ฌ auth API์์ ํ์ ์ ๋ณด๋ฅผ ๊ด๋ฆฌํ๋ค๋ฉด,
ํ ์คํธ ๊ฒฐ๊ณผ๋ json API๋ฅผ ์ด์ฉํ์ฌ ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํ๊ณ (C), ๊ฐ์ ธ์ค๊ณ (R), ์ ๋ฐ์ดํธํ๊ณ (U), ์ญ์ (D)ํด์ผํ๋ค.
๋ฐ๋ผ์ ์๋ก์ด API ๋ฌธ์๋ฅผ ๋ฐํ์ผ๋ก ์ฐ๊ฒฐ ๋ก์ง์ ์์ฑํ๊ณ , ํด๋น ๋ด์ฉ์ ์ฌ์ฉํ๋ค.
// theResults.js
import axios from "axios";
const jsonApi = "http://localhost:5000/testResults";
export const getTestResults = async () => {
const response = await axios.get(jsonApi);
return response.data;
};
export const createTestResult = async (resultData) => {
const response = await axios.post(jsonApi, resultData);
return response.data;
};
export const deleteTestResult = async (id) => {
const response = await axios.delete(`${jsonApi}/${id}`);
return response.data;
};
export const updateTestResultVisibility = async (id, visibility) => {
const response = await axios.patch(`${jsonApi}/${id}`, { visibility });
return response.data;
};
์๊ตฌ ์กฐ๊ฑด๊ณผ ๊ตฌํ ๋ฐฉ๋ฒ
์๊ตฌ ์กฐ๊ฑด๊ณผ ๊ตฌํ ๋ฐฉ๋ฒ์ ์๊ฐํด๋ณด์๋ค.
- ๋ ๊ฐ์ ๋ฒํผ์ ์์ฑ์๋ง์ด ๋ณผ ์ ์์ด์ผ ํ๋ค. ๐๐ป[isOwner] ๊ฒฐ๊ณผ ์นด๋์ ์ ์ฅ๋ userId์ ํ์ฌ ์ฌ์ฉ์์ userId๋ฅผ ๋น๊ตํ๊ธฐ
- ํ์ฌ ํ๋ฉด๊ณผ DB ๋ชจ๋์์ ์นด๋๋ฅผ ์ญ์ ํ๋ค. ๐๐ป[visibility] ํ๋ฉด: filter ํจ์ ์ฌ์ฉํ๊ธฐ. DB: delete API ๋ก์ง ์ฌ์ฉํ๊ธฐ
- ๊ณต๊ฐ/๋น๊ณต๊ฐ๋ก ์ ํ๋์ด์ผ ํ๋ค. ๐๐ป[visibility] visibilty๋ก ์ ํ ์ ์ด
visibility
๐ค ๊ณ ๋ฏผ
์ด ๋ชจ๋ ๊ฒ๋ค์ ์ด๋ป๊ฒ ํด์ผํ๋...!! ์ ๋ง ๊ณ ๋ฏผ์ด ๋ง์์ง๋ง
์ฐ์ ๊ณต๊ฐ/๋น๊ณต๊ฐ ์ฌ๋ถ๋ ์ํ๊ฐ ๊ณ์ ๋ณ๋๋์ด์ผ ํ๋ค๋ ๊ฒ์ด๋ผ๋ ์๊ฐ์ด ๋ค์๋ค.
๋ฐ๋ผ์ isVisibility๋ฅผ state๋ก ๊ด๋ฆฌํ๋ค.
setState๋ฅผ ์ด์ฉํด์ ๊ด๋ฆฌํ์ง ์์ผ๋ฉด ๋ด์ฉ์ด ๋ณํ ๋๋ง๋ค ์๋ก๊ณ ์นจ์ ํด์ฃผ์ด์ผ ํ๋ฉด์ ์ ์ฉ์ด ๋๋ค.
const [isVisibility, setIsVisibility] = useState(false);
์ด๊ธฐ๊ฐ์ false๋ก ์ฃผ์ด ํ ์คํธ ๋ฑ๋ก ์ ๋น๊ณต๊ฐ ์ํ๋ก ๋์๋ค.
ํ ์คํธ ํ์ด์ง์์ ์นด๋๋ฅผ ์์ฑํ ๋์๋ (createTestResult) false๋ก ๋์๋ค.
const resultsData = await createTestResult({
nickname: userInfo.nickname,
result: mbtiResult,
visibility: false,
date: formattedDate,
userId: userInfo.id,
});
๋์ ์ฐจ์ด๋ state : ํ๋ฉด์์ ์ ํ ๋ด๋น / create : DB์์ ์ ํ ๋ด๋น ์ด๋ค.
์ฆ, ํ๋ฉด์์์ ๋ณํ๋ isVisibility๋ฅผ ๋ฐ๋ ๊ฐ์ผ๋ก ์ฃผ์ด์ผ ํ๊ณ , DB์์์ ๋ณํ๋ visibility์ ๊ฐ์ ๋ฐ๋๋ก ์ฃผ์ด์ผ ํ๋ค.
๐ ํด๊ฒฐ
๐ DB์์ ๊ฒฐ๊ณผ๊ฐ ๊ฐ์ ธ์ค๊ธฐ
๊ณต๊ฐ ์ฌ๋ถ๊ฐ ๋ฐ๋ ๋๋ง๋ค DB์์ ๋ณ๋๋ ๊ฐ์ ์๋ก ๋ถ๋ฌ์ ๋ฐ์ํด์ผ ํ๊ธฐ ๋๋ฌธ์ useEffect ์ฌ์ฉ
// DB: ๊ฒฐ๊ณผ๊ฐ ๊ฐ์ ธ์ค๊ธฐ
useEffect(() => {
const fetchData = async () => {
try {
const data = await getTestResults();
setCards(data);
} catch (error) {
console.error("Error fetching data:", error);
}
};
fetchData();
}, [isVisibility]);
๐ ๊ณต๊ฐ ๋น๊ณต๊ฐ ์ ํ
๋ฒํผ์ onClick ์ด๋ฒคํธ๋ฅผ ์ฃผ์ด ํธ๋ค๋งํ๋ค.
update- API ๋ก์ง์ ์ฌ์ฉํ์ฌ DB์ ๋ฐ์ํ๊ณ setState๋ฅผ ์ด์ฉํ์ฌ ํ๋ฉด์ ๋ฐ์ํ๋ค.
// ๊ณต๊ฐ ๋น๊ณต๊ฐ ์ ํ
const handleVisibility = async (card) => {
await updateTestResultVisibility(card.id, !card.visibility);
setIsVisibility(!isVisibility);
};
๐ ๊ณต๊ฐ ๋น๊ณต๊ฐ ๋ฆฌ์คํธ
์นด๋ ๋ฆฌ์คํธ๋ state๋ก ๊ด๋ฆฌ ์ค์ด๊ธฐ ๋๋ฌธ์ ์ ์ฒด cards ๋ฐฐ์ด์ filter๋ฉ์๋๋ก ์ ๋ ฌํ๋ค.
|| ๋ ์ข์ธก, ์ฐ์ธก ๋ ์ค ํ๋๋ง ๋ง์กฑํด๋ true๋ฅผ ๋ฐํํ๊ธฐ ๋๋ฌธ์
์นด๋ ํ๋์ visibility๊ฐ true์ด๊ฑฐ๋, ์นด๋์ userId์ ๋ก๊ทธ์ธ ํ ์ฌ์ฉ์์ id ๊ฐ ๊ฐ์ ๊ฒฝ์ฐ๋ฅผ ๋์๋ค.
const [cards, setCards] = useState([]);
// ๊ณต๊ฐ ๋น๊ณต๊ฐ ๋ฆฌ์คํธ filter -> ์นด๋ ๊ทธ๋ฆฌ๋ map์ ์ฌ์ฉ
const viewCards = cards.filter((card) => {
return card.visibility || card.userId === userInfo.id;
});
๐ ์นด๋ ์ญ์

์์ ๊ฐ์ confirm ์ฐฝ์ ๋์ 'ํ์ธ'์ ํด๋ฆญํ๋ค๋ฉด (true๋ผ๋ฉด)
delete- API ๋ก์ง์ ์ด์ฉํ์ฌ DB์ ๋ฐ์ํ๊ณ setState๋ก ํ๋ฉด๊น์ง ๊ตฌ์ฑํ๋ค.
// ์นด๋ ์ญ์
const deleteCard = (cardId) => {
const alertResult = window.confirm("์ ๋ง ์ญ์ ํ์๊ฒ ์ต๋๊น?");
if (alertResult === true) {
deleteTestResult(cardId);
setIsVisibility(!isVisibility);
}
};
isOwner
isOwner๋ ์ฌ์ฉ์๋ฅผ ๊ฐ์งํ์ฌ ์์ฑ์์๊ฒ๋ง ๋ ๋ฒํผ์ ๋ณด์ฌ์ฃผ๋๋ก ํ๋ ๋ณ์๋ก ์ง์ ํ์๋ค.
์ฒ์์๋ ํจ์๋ก ๋ฐ๋ก ๋นผ๊ณ ์ด๋ฅผ ๋ฒํผ ๋ถ๋ถ์ ์ ์ฉํ๋ ค ํ์๋ค.
โ์๋ชป๋ ๋ฐฉ๋ฒโ
// ์์ฑ์ ๊ธ์๋ง ๋ฒํผ ํ์ํ๊ธฐ
const isOwner = cards.some((card) => {
return card.id === userInfo.id;
});
console.log("isOwner", isOwner);
return (
...
{!isOwner ? (
<></>
) : (
<div className="flex justify-end gap-3 mt-3 mr-5">
{card.visibility ? (
<button className="px-[20px] py-[8px] text-white bg-blue-500 rounded-full hover:bg-opacity-0 hover:text-blue-500 focus:outline-2 focus:outline-offset-2 focus:outline-blue-400 font-semibold hover:bg-primary-dark transition duration-300">
๋น๊ณต๊ฐ๋ก ์ ํ
</button>
) : (
<button className="px-[20px] py-[8px] text-white bg-blue-500 rounded-full hover:bg-opacity-0 hover:text-blue-500 focus:outline-2 focus:outline-offset-2 focus:outline-blue-400 font-semibold hover:bg-primary-dark transition duration-300">
๊ณต๊ฐ๋ก ์ ํ
</button>
)}
ํ์ง๋ง ์ด๋ ๊ฒ ํ๋ฉด ์ฌ์ฉ์๊ฐ ์์ฑํ ๋ชจ๋ ์นด๋์์ isOwner ===true๊ฐ ๋์ด ์ ๋๋ก ์๋ํ์ง ์์๋ค.
๋ฐ๋ผ์ ๊ฐ ์นด๋(card.id)๊ฐ ํ์ฌ ๋ก๊ทธ์ธํ ์ ์ (userInfo.id)์ ์ผ์นํ๋์ง ๊ฐ๋ณ์ ์ผ๋ก ํ์ธํด์ผ ํ๋ค.
์๋์ ๊ฐ์ด isOwner๋ฅผ map ๋ด๋ถ์์ ์ ์ธํ๊ณ
&&๋ฅผ ์ฌ์ฉํ์ฌ isOwner๊ฐ true ์ผ ๋ (์ฌ์ฉ์์ ์นด๋์ id๊ฐ ์ผ์นํ ๋) ๋ฒํผ์ ๋ณด์ด๊ฒ ํ๋ค.
{viewCards.map((card) => {
const isOwner = card.userId === userInfo.id; // ๊ฐ๋ณ ์นด๋ ๊ธฐ์ค์ผ๋ก ์์ฑ์ ํ์ธ
return (
...
{/* ์นด๋์ ์๋ visibility๋ก ์์ฑ์์๊ฒ๋ง ๋ฒํผ ํ์ */}
{isOwner && (
<div className="flex justify-end gap-3 mt-3 mr-5">
{card.visibility ? (
<button
onClick={() => handleVisibility(card)}
className="px-[20px] py-[8px] text-white bg-blue-500 rounded-full hover:bg-opacity-0 hover:text-blue-500 focus:outline-2 focus:outline-offset-2 focus:outline-blue-400 font-semibold hover:bg-primary-dark transition duration-300"
>
๋น๊ณต๊ฐ๋ก ์ ํ
</button>
) : (
<button
onClick={() => handleVisibility(card)}
className="px-[20px] py-[8px] text-white bg-blue-500 rounded-full hover:bg-opacity-0 hover:text-blue-500 focus:outline-2 focus:outline-offset-2 focus:outline-blue-400 font-semibold hover:bg-primary-dark transition duration-300"
>
๊ณต๊ฐ๋ก ์ ํ
</button>
)}
<button
onClick={() => deleteCard(card.id)}
className="px-[20px] py-[8px] text-white bg-red-500 rounded-full hover:bg-opacity-0 hover:text-red-500 focus:outline-2 focus:outline-offset-2 focus:outline-red-400 font-semibold hover:bg-primary-dark transition duration-300"
>
์ญ์
</button>
</div>
)}
๐ TestResultList.jsx ์ ์ฒด ๋ก์ง
import { useContext, useEffect, useState } from "react";
import {
deleteTestResult,
getTestResults,
updateTestResultVisibility,
} from "../api/testResults";
import { mbtiDescriptions } from "../utils/mbtiCalculator";
import { AuthContext } from "../context/AuthContext";
import { getUserProfile } from "../api/auth";
const TestResultList = () => {
const [cards, setCards] = useState([]);
const [isVisibility, setIsVisibility] = useState(false);
const { userInfo, setUserInfo, token } = useContext(AuthContext);
// userInfo ๊ฐ์ ธ์ค๊ธฐ : isOwner์ ํ์
useEffect(() => {
const fetchUserInfo = async () => {
try {
const data = await getUserProfile(token);
setUserInfo({
id: data.id,
nickname: data.nickname,
});
} catch (error) {
console.log("Faild to fetch user info", error);
}
};
fetchUserInfo();
}, []);
// DB: ๊ฒฐ๊ณผ๊ฐ ๊ฐ์ ธ์ค๊ธฐ
useEffect(() => {
const fetchData = async () => {
try {
const data = await getTestResults();
setCards(data);
} catch (error) {
console.error("Error fetching data:", error);
}
};
fetchData();
}, [isVisibility]);
// ๊ณต๊ฐ ๋น๊ณต๊ฐ ์ ํ
const handleVisibility = async (card) => {
await updateTestResultVisibility(card.id, !card.visibility);
setIsVisibility(!isVisibility);
};
// ๊ณต๊ฐ ๋น๊ณต๊ฐ ๋ฆฌ์คํธ filter -> ์นด๋ ๊ทธ๋ฆฌ๋ map์ ์ฌ์ฉ
const viewCards = cards.filter((card) => {
return card.visibility || card.userId === userInfo.id;
});
// ์นด๋ ์ญ์
const deleteCard = (cardId) => {
const alertResult = window.confirm("์ ๋ง ์ญ์ ํ์๊ฒ ์ต๋๊น?");
if (alertResult === true) {
deleteTestResult(cardId);
setIsVisibility(!isVisibility);
}
};
return (
<div className="flex flex-col items-center justify-center mx-auto md:w-[70%] w-[90%]">
{cards.length === 0 ? (
<div className="text-center mt-[80px] mb-[30px] text-[23px] text-[#475da1]">
์์ง ๊ณต์ ๋ ๊ฒ์ฌ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค. <br />
๊ฐ์ฅ ๋จผ์ mbti ๊ฒ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๊ณต์ ํ์ธ์ !
</div>
) : (
<div className="mt-[60px] mb-[30px] text-[27px] font-bold text-[#4b86cd]">
๋ค๋ฅธ ์ฌ๋๋ค์ MBTI๋ ๊ถ๊ธํด !
</div>
)}
{viewCards.map((card) => {
const isOwner = card.userId === userInfo.id; // ๊ฐ๋ณ ์นด๋ ๊ธฐ์ค์ผ๋ก ์์ฑ์ ํ์ธ
return (
<div
key={card.id}
className="w-[95%] m-[15px] bg-[#474E93] rounded-lg"
>
<div className="p-[20px] shadow-lg">
<div className="flex flex-col justify-between md:flex-row p-2 border-b-[1px]">
<div className="text-[20px] font-bold text-violet-200">
{card.nickname}
</div>
<p className="font-light text-violet-200">{card.date}</p>
</div>
<div className="py-2">
<div className="p-2 text-[30px] text-[#fcff97] font-extrabold">
{card.result}
</div>
<div className="font-light text-violet-200">
{mbtiDescriptions[card.result]}
</div>
{/* ์นด๋์ ์๋ visibility๋ก ์์ฑ์์๊ฒ๋ง ๋ฒํผ ํ์ */}
{isOwner && (
<div className="flex justify-end gap-3 mt-3 mr-5">
{card.visibility ? (
<button
onClick={() => handleVisibility(card)}
className="px-[20px] py-[8px] text-white bg-blue-500 rounded-full hover:bg-opacity-0 hover:text-blue-500 focus:outline-2 focus:outline-offset-2 focus:outline-blue-400 font-semibold hover:bg-primary-dark transition duration-300"
>
๋น๊ณต๊ฐ๋ก ์ ํ
</button>
) : (
<button
onClick={() => handleVisibility(card)}
className="px-[20px] py-[8px] text-white bg-blue-500 rounded-full hover:bg-opacity-0 hover:text-blue-500 focus:outline-2 focus:outline-offset-2 focus:outline-blue-400 font-semibold hover:bg-primary-dark transition duration-300"
>
๊ณต๊ฐ๋ก ์ ํ
</button>
)}
<button
onClick={() => deleteCard(card.id)}
className="px-[20px] py-[8px] text-white bg-red-500 rounded-full hover:bg-opacity-0 hover:text-red-500 focus:outline-2 focus:outline-offset-2 focus:outline-red-400 font-semibold hover:bg-primary-dark transition duration-300"
>
์ญ์
</button>
</div>
)}
</div>
</div>
</div>
);
})}
</div>
);
};
export default TestResultList;
'Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [Outsourcing_Day 1] HotPlace In Seoul ๐ฅ (0) | 2025.02.27 |
|---|---|
| [MBTI-Test PJ_Final] MBTI Test (0) | 2025.02.25 |
| [MBTI-Test PJ_Day 2] JWT ์ธ์ฆ API ์ฐ๊ฒฐ (0) | 2025.02.25 |
| [MBTI-Test PJ_Day 1] Tailwind ๊ณตํต ์ปดํฌ๋ํธ ์คํ์ผ๋ง (1) | 2025.02.21 |
| [NewsFeed-Final] FootPrint๐ฃ (1) | 2025.02.18 |