
이제는 배포 준비를 위해 디테일 작업에 들어갔습니다.
그래서 하나의 큰 기능보다는 작은 기능과 디자인 수정 작업이 주입니다.
수정된 부분을 하나씩 뜯어서 포스팅해보도록 하겠습니다!
🚀 헤더와 푸터
헤더는 로고를 변경하고 텍스트를 간결하게 수정했습니다.
홈페이지에 들어오면 가장 먼저 헤더의 텍스트가 눈에 보일 수 있도록 크기도 키웠습니다.
로고 부분의 코드입니다.
로고에 사용할 이미지는 public 폴더에 추가하여 정적으로 사용하였습니다.
<a className="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0">
<div className="w-14 h-14 rounded-full bg-red-400 flex items-center justify-center">
<img
src="/jam-main.png"
alt="짭알못 로고"
className="w-10 h-10"
/>
</div>
<span className="ml-3 text-xl">짭알못 JAM</span>
</a>
푸터는 배경색을 변경하고 이름의 유래 이스터에그와 인스타, 노션 링크를 연결해두었습니다.
모달 상태 관리 코드입니다.
- showModal : 모달 표시 여부 저장
- setShowModal : 상태를 업데이트하는 함수
'use client'
import React, { useState } from "react";
export default function Footer() {
const [showModal, setShowModal] = useState(false);
// 생략
버튼 클릭시 setShowModal(true)를 호출해 모달을 표시할 수 있도록 합니다.
모달 내부의 닫기 버튼에서 setShowModal(false)를 호출해 모달을 숨깁니다.
<div className="container flex justify-center items-center space-x-1 mt-2">
<span>짭알못 이름의 유래는...</span>
<button
className="text-blue-500 hover:text-blue-700"
onClick={() => setShowModal(true)}
aria-label="짭알못 이름의 유래 보기"
>
ℹ️
</button>
</div>
{showModal && (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
<div className="bg-white dark:bg-slate-800 p-6 rounded-lg shadow-lg max-w-sm w-full">
<h2 className="text-lg font-bold mb-2">짭알못의 유래</h2>
<p className="text-sm mb-4">
과거에 '금요일을 알차게 보내는 건 못 참지'라는 금요일마다 자기계발을 하는 모임이 있었습니다.
저희는 그 모임과 별개로 매주 목요일마다 자기계발 모임을 하였습니다.
어느 날 누군가 '짭알못이다!'라고 외친 이후로 그렇게 불리게 되었다는 슬픈 유래...😂
</p>
<button
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
onClick={() => setShowModal(false)}
>
닫기
</button>
</div>
</div>
)}
인스타와 노션 svg 코드입니다.
<span className="inline-flex sm:ml-auto sm:mt-0 mt-4 justify-center sm:justify-start">
<a className="ml-3" href="https://www.instagram.com/jjrm_0fficial?igsh=dmQ4YzcwNmoxdmds&utm_source=qr">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 48 48">
<path fill="currentColor" d="M 16 3 C 8.8324839 3 3 8.8324839 3 16 L 3 34 C 3 41.167516 8.8324839 47 16 47 L 34 47 C 41.167516 47 47 41.167516 47 34 L 47 16 C 47 8.8324839 41.167516 3 34 3 L 16 3 z M 16 5 L 34 5 C 40.086484 5 45 9.9135161 45 16 L 45 34 C 45 40.086484 40.086484 45 34 45 L 16 45 C 9.9135161 45 5 40.086484 5 34 L 5 16 C 5 9.9135161 9.9135161 5 16 5 z M 37 11 A 2 2 0 0 0 35 13 A 2 2 0 0 0 37 15 A 2 2 0 0 0 39 13 A 2 2 0 0 0 37 11 z M 25 14 C 18.936712 14 14 18.936712 14 25 C 14 31.063288 18.936712 36 25 36 C 31.063288 36 36 31.063288 36 25 C 36 18.936712 31.063288 14 25 14 z M 25 16 C 29.982407 16 34 20.017593 34 25 C 34 29.982407 29.982407 34 25 34 C 20.017593 34 16 29.982407 16 25 C 16 20.017593 20.017593 16 25 16 z"></path>
</svg>
</a>
<a className="ml-3" href="https://www.notion.so/4f4f5ba1b977487c8d4232941ed509b0">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 48 48">
<path fill="currentColor" d="M10.849,10.643c1.308,1.063,1.799,0.982,4.256,0.818l23.161-1.391 c0.492,0,0.083-0.49-0.081-0.571l-3.846-2.781c-0.737-0.572-1.719-1.227-3.601-1.064L8.312,7.288 c-0.818,0.081-0.981,0.49-0.655,0.818L10.849,10.643z M12,16.165V40.29c0,1.296,0.649,1.782,2.112,1.702l25.262-1.458 C40.837,40.454,41,39.561,41,38.508V14.545c0-1.051-0.406-1.619-1.3-1.538L13.3,14.545C12.326,14.626,12,15.113,12,16.165L12,16.165 z M37.441,16.724c0.166,0.746,0,1.491-0.747,1.575l-1.242,0.247v18.213c-1.078,0.579-2.072,0.91-2.9,0.91 c-1.326,0-1.659-0.414-2.652-1.655L21.78,23.265V35.6l2.57,0.579c0,0,0,1.49-2.074,1.49L16.561,38c-0.166-0.331,0-1.159,0.579-1.324 l1.492-0.414V19.954l-2.071-0.166c-0.167-0.746,0.247-1.821,1.408-1.905L24.1,17.47l8.451,12.915V18.96l-2.155-0.247 c-0.166-0.912,0.497-1.574,1.325-1.655L37.441,16.724z M6.515,5.102l23.124-1.703c2.84-0.243,3.571-0.08,5.355,1.216l7.382,5.188 C43.594,10.695,44,10.937,44,11.91v28.455c0,1.783-0.649,2.838-2.921,2.999l-26.855,1.622c-1.705,0.081-2.517-0.162-3.409-1.297 l-5.436-7.053C4.405,35.338,4,34.367,4,33.231V7.937C4,6.479,4.649,5.263,6.515,5.102z"></path>
</svg>
</a>
</span>
svg 코드는 아래 사이트에서 사용했습니다.
📅 월 별 드롭다운을 캘린더로
사실 이 기능이 생각보다 시간이 걸려서 이거만 포스팅했어도 됐었을거 같긴합니다 ㅋㅋ...
기존의 드롭다운이 UX가 좋지 않은거 같아 쉽게 접할 수 있는 캘린더 UX로 변경하였습니다.
- [<<] 와 [>>] : 1년 전과 후로 이동하는 버튼
- [<] 와 [>] : 전 달과 다음 달로 이동하는 버튼
- [0000년 00월] : 현재 선택된 년도와 월이 표시되며 월별로 선택할 수 있는 캘린더 모달이 나오는 버튼
저는 React-Calendar 라이브러리를 사용하였습니다.
우선 라이브러리를 설치합니다.
npm install react-calendar
or
yarn add react-calendar
MonthPicker라는 이름으로 컴포넌트를 분리하여 코드를 작성했습니다.
- 캘린더 모달 상태 관리
- showCalendar, setShowCalendar
- handlePrev & Next
- 이동하고자 하는 년도와 월을 계산한 후 0000년 00월 형식으로 변경합니다.
- 목표 월에 데이터가 존재하는지 확인한 후 있으면 현재 년과 월로 선택합니다.
- handleMonthChange
- 캘린더 모달에서 선택한 년도와 월로 이동합니다.
import Calendar from "react-calendar";
import "react-calendar/dist/Calendar.css";
function MonthPicker({ selectedMonth, setSelectedMonth, uniqueMonths }) {
const [showCalendar, setShowCalendar] = useState(false);
const [year, month] = selectedMonth
.split("년 ")
.map((str) => str.replace("월", "").trim());
const handlePrevYear = () => {
const newYear = Number(year) - 1;
const formattedMonth = `${newYear}년 ${month}월`;
if (uniqueMonths.includes(formattedMonth)) {
setSelectedMonth(formattedMonth);
}
};
const handleNextYear = () => {
const newYear = Number(year) + 1;
const formattedMonth = `${newYear}년 ${month}월`;
if (uniqueMonths.includes(formattedMonth)) {
setSelectedMonth(formattedMonth);
}
};
const handlePrevMonth = () => {
const newMonth = new Date(Number(year), Number(month) - 2);
const formattedMonth = `${newMonth.getFullYear()}년 ${String(
newMonth.getMonth() + 1
).padStart(2, "0")}월`;
if (uniqueMonths.includes(formattedMonth)) {
setSelectedMonth(formattedMonth);
}
};
const handleNextMonth = () => {
const newMonth = new Date(Number(year), Number(month));
const formattedMonth = `${newMonth.getFullYear()}년 ${String(
newMonth.getMonth() + 1
).padStart(2, "0")}월`;
if (uniqueMonths.includes(formattedMonth)) {
setSelectedMonth(formattedMonth);
}
};
const handleMonthChange = (value) => {
const newYear = value.getFullYear();
const newMonth = value.getMonth() + 1;
const formattedMonth = `${newYear}년 ${String(newMonth).padStart(2, "0")}월`;
if (uniqueMonths.includes(formattedMonth)) {
setSelectedMonth(formattedMonth);
setShowCalendar(false);
} else {
alert("선택한 달에는 활동이 없습니다.");
}
};
위에 작성한 handle 함수들을 각 버튼에 연결합니다.
view에는 "month", "year", "decade", "century"로 4가지가 있습니다.
defaultView는 year로 minDetail은 decade까지 설정하여 month와 century view는 나타나지 않도록 설정했습니다.
더 자세한 react-calendar의 속성 설명은 GitHub에서 확인하면 좋습니다.
https://github.com/wojtekmaj/react-calendar
GitHub - wojtekmaj/react-calendar: Ultimate calendar for your React app.
Ultimate calendar for your React app. Contribute to wojtekmaj/react-calendar development by creating an account on GitHub.
github.com
return (
<div className="flex flex-col items-center space-y-4 relative dark:text-white">
<div className="flex items-center space-x-4">
<button onClick={handlePrevYear} className="px-2 py-1 border rounded">
{"<<"}
</button>
<button onClick={handlePrevMonth} className="px-2 py-1 border rounded">
{"<"}
</button>
<span
onClick={() => setShowCalendar(true)}
className="cursor-pointer px-4 py-2 border rounded"
>
{selectedMonth}
</span>
<button onClick={handleNextMonth} className="px-2 py-1 border rounded">
{">"}
</button>
<button onClick={handleNextYear} className="px-2 py-1 border rounded">
{">>"}
</button>
</div>
{showCalendar && (
<div
className="absolute top-full mt-2 bg-white dark:bg-slate-800 border p-4 rounded shadow-lg z-50 {`react-calendar-wrapper ${theme === 'dark' ? 'dark' : ''}`}"
onClick={(e) => e.stopPropagation()}
>
<Calendar
value={new Date(Number(year), Number(month) - 1)}
onClickMonth={handleMonthChange}
defaultView="year"
minDetail="decade"
tileDisabled={({ date }) => {
const formattedMonth = `${date.getFullYear()}년 ${String(
date.getMonth() + 1
).padStart(2, "0")}월`;
return !uniqueMonths.includes(formattedMonth);
}}
formatMonth={(locale, date) =>
`${String(date.getMonth() + 1).padStart(2, "0")}월`
}
/>
<button
onClick={() => setShowCalendar(false)}
className="mt-2 px-4 py-2 bg-gray-200 dark:bg-slate-700 dark:text-slate-400 rounded"
>
닫기
</button>
</div>
)}
</div>
);
그리고 기본적으로 다크 모드가 지원되지 않아 원하는 부분의 색상을 변경해주었습니다.
간혹 우선 순위때문에 적용이 안되는 부분은 !important를 추가해주면 됩니다.
.dark .react-calendar {
background-color: #1e293b;
color: #fff;
}
.dark .react-calendar__tile:hover {
background-color: #334155;
color: #fff;
}
.dark .react-calendar__year-view .react-calendar__tile:hover,
.dark .react-calendar__decade-view .react-calendar__tile:hover {
background-color: #334155;
color: #fff;
}
.dark .react-calendar__tile--now {
background-color: #ffae00;
color: #ffffff;
}
.dark .react-calendar__tile:disabled {
background-color: #334155;
color: #64748b;
}
.dark .react-calendar__tile:disabled:hover {
background-color: #334155;
color: #64748b;
}
.dark .react-calendar__navigation__arrow,
.dark .react-calendar__navigation__label {
background-color: #1e293b;
color: #ffffff;
}
.dark .react-calendar__navigation__arrow:hover,
.dark .react-calendar__navigation__label:hover {
background-color: #334155 !important;
color: #ffffff;
}
.dark .react-calendar__navigation__arrow:focus,
.dark .react-calendar__navigation__label:focus {
background-color: #334155 !important;
color: #ffffff;
}
🪪 멤버 카드
레이아웃을 수정하고, 활동 유형 태그를 추가하고, 월 별 잔디를 수정했습니다.
태그는 작업 유형을 key으로 value는 색상으로 지정합니다.
function MemberCard({ memberName, realName, iconUrl, monthData, selectedMonth }) {
const { totalTime, count, stateCounts } = monthData;
const activityByDate = monthData.activityByDate;
const tagColors = {
개발: "bg-blue-100 text-blue-500",
취준: "bg-green-100 text-green-500",
디자인: "bg-yellow-100 text-yellow-500",
프로젝트: "bg-red-100 text-red-500",
공부: "bg-pink-100 text-pink-500",
자기계발: "bg-orange-100 text-orange-500",
기타: "bg-gray-100 text-gray-500",
};
const getColorClass = (tag) => tagColors[tag] || "bg-gray-200 text-gray-700";
아이콘, 이름, 태그는 하나의 div로 지정합니다.
이름과 태그를 수직으로 이 div를 아이콘과 수평으로 배치합니다.
태그는 가장 많이 존재하는 순으로 4개까지만 표시합니다.
return (
<div className="p-2 lg:w-1/3 md:w-1/2 w-full">
<div className="h-full flex flex-col items-start border-gray-200 border p-4 rounded-lg">
<div className="flex items-center">
{iconUrl && (
<img
src={iconUrl}
alt={`${memberName}의 아이콘`}
className="w-24 h-24 mr-4 rounded-full"
/>
)}
<div className="flex flex-col">
<h2 className="text-gray-900 text-lg mb-2">
{realName} {memberName}
</h2>
<div className="flex flex-wrap">
{Object.entries(monthData.tagCounts)
.slice(0, 4)
.map(([tag, count]) => (
<span
key={tag}
className={`text-sm font-medium px-3 py-1 rounded-full mr-2 mb-2 ${getColorClass(tag)}`}
>
{tag}
</span>
))}
</div>
</div>
</div>
<div className="flex w-full space-x-4 items-end">
<div className="w-1/2 h-40">
<PieChart stateCounts={stateCounts} />
</div>
<div className="w-1/2">
<div className="text-center mb-2">
<p className="text-gray-500">
📅 {count}일 ⏰ {Math.floor(totalTime)}시간
</p>
</div>
<MonthGrid
selectedMonth={selectedMonth}
activityByDate={activityByDate}
/>
</div>
</div>
</div>
</div>
);
기존으로 표시하던 월 별 잔디의 이모지를 일(date) 박스로 그리도록 수정했습니다.
return (
<div className="grid grid-cols-7 gap-1 text-center">
{Array.from({ length: startDay }, (_, i) => (
<div key={`empty-${i}`} className="w-6 h-6 flex items-center justify-center"></div>
))}
{days.map((day) => {
const activity = activityByDate[day]?.state;
let bgColor;
switch (activity) {
case "좋음":
bgColor = "bg-green-400";
break;
case "보통":
bgColor = "bg-yellow-400";
break;
case "나쁨":
bgColor = "bg-red-400";
break;
default:
bgColor = "bg-gray-400";
}
return (
<div
key={day}
className={`w-6 h-6 flex items-center justify-center ${bgColor} text-white text-sm font-bold rounded-md`}
>
{day}
</div>
);
})}
</div>
);
전체적으로 라이트 모드와 다크 모드를 번갈아 가면서 어색한 부분이 없는지 모두 확인하여 수정했습니다.
처음부터 고려했던 부분은 아니라 global.css에 컴포넌트를 추가한 것도 있고, 직접 수정한 부분도 있습니다.
이번 PR은 커밋이 여러개라 File Changed로 확인하는게 좋습니다.
https://github.com/JoHyeonJi0408/Study-Introduction-Website/pull/5
메인 페이지 배포 전 디테일 수정 by JoHyeonJi0408 · Pull Request #5 · JoHyeonJi0408/Study-Introduction-Website
멤버 소개 페이지에 정보 추가 작업 태그 테스트 이미지 테스트 월 별 잔디 박스로 수정 월 드롭다운 달력과 버튼으로 수정 원형 차트 label 이름과 크기 수정 메인 페이지 카드에 본명과 태그 추
github.com
이제 멤버 페이지만 수정하고 테스트 배포를 해봐야겠습니다!!!😎

'프로젝트 > 웹페이지' 카테고리의 다른 글
웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 12일차 (0) | 2025.02.18 |
---|---|
웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 10일차 (1) | 2025.01.20 |
웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 9일차 (0) | 2025.01.19 |
웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 8일차 (0) | 2025.01.18 |
웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 7일차 (1) | 2025.01.16 |