[팀 프로젝트] 9주차 작업 일지

개발일지/스터디:EZ-FillStack
2026.03.28
반응형

 

9주차 이야기. 9주차에서 프론트엔드 자체의 작업은 거의 마무리 되어 있었고, 백엔드로부터 자료만 넘겨받으면 되는 상황에 가까워졌다. 이번 9주차에서 내가 했던 내용에 대해서 정리해본다.

 

도메인 타입 취합

관리자 화면은 `src/lib/adminEntityTypes.ts`, 북마크는 `BookmarkedEvent`, 카드는 props에 `status: string`… 같은 “체험/리뷰/문의”인데 타입이 파일마다 달라서, 나중에 API만 붙여도 필드명이 어긋날 게 뻔했다. 이에 따라 `types/event`, `types/inquiry`, `types/review`로 모으고, 쓰이던 곳을 전부 이쪽으로 맞췄다. `adminEntityTypes.ts`는 역할이 없어져서 삭제했다

 

// types/event.ts
export type EventType = {
  id: number;
  title: string;
  status: 'UPCOMING' | 'OPEN' | 'CLOSED' | 'FINISHED';
  version?: number;
  thumbnailUrl?: string;
  placeName?: string;
  applyEndDateTime?: string;
  capacity?: number;
  currentParticipants?: number;
  // ...
};

export type EventStatus = EventType['status'];

 

// types/inquiry.ts
export type Inquiry = {
  id: number;
  memberId: number;
  title: string;
  content: string;
  replyEmail: string;
  status: 'UNANSWERED' | 'ANSWERED'; // 예전 PENDING 정리
  createdAt: string;
  answeredAt: string | null;
};

 

 

카테고리 아이콘 지정

슬슬 마무리를 위해서 가운데 메뉴에 있는 카테고리 아이콘을 지정하기로 했다. 지정에 있어 좀 더 깔끔할 필요가 있어 보여 카테고리에 `eng` 키를 두고 PNG를 `categoryIcons.ts`에서 매핑했다.

 

// lib/categories.ts
export const categories = [
  { label: '맛집', id: 1, eng: 'restaurant' },
  { label: '뷰티', id: 2, eng: 'beauty' },
  { label: '여행', id: 3, eng: 'travel' },
  { label: '문화', id: 4, eng: 'culture' },
  { label: '식품', id: 5, eng: 'food' },
] as const;
// assets/icons/categoryIcons.ts
export const categoryIconByEng: Record<string, string> = {
  restaurant, beauty, travel, culture, food,
};

 

 

오픈 예정 캐러셀 이벤트 카드 적용

오픈 예정 캐러셀 파트에 있던 카드들을 컴포넌트로 적용하여 수정했다.

// EventCard — size와 관계없이 위치, lg가 아니면 신청 인원
{placeName && (
  <div className="text-xs text-muted-foreground flex items-center gap-1">
    <MapPin className="h-3.5 w-3.5" />
    <span>{placeName}</span>
  </div>
)}
{size != 'lg' && (
  <div className="text-xs text-muted-foreground">
    신청 {currentParticipants} / {capacity}명
  </div>
)}

 

 

검색 및 필터 기능 추가

백엔드에서 검색 및 필터에 대한 구현을 완성하여, 프론트에서도 검색 기능 및 필터 기능의 화면을 추가할 수 있게 되었다. 파라미터를 통일할 필요가 생겨, 목록 탐색의 단일 소스를 쿼리스트링으로 잡았다. 검색의 경우 `?keyword=&page=`로 잡았다.

// GlobalLayout
<form
  onSubmit={(e) => {
    e.preventDefault();
    const next = keyword.trim();
    navigate(`/search?keyword=${encodeURIComponent(next)}&page=1`);
  }}
>
  <Input value={keyword} onChange={(e) => setKeyword(e.target.value)} />
</form>

 

그리고 `SearchPage`를 새로 만들고 `mainRoutes`에 연결했다. 키워드로 `title/placeName`을 필터하고, `PAGE_SIZE`로 잘라 `Pagination`에 넘긴다.

const keyword = searchParams.get('keyword') ?? '';
const page = Math.max(1, Number(searchParams.get('page')) || 1);

const filtered = useMemo(() => {
  const kw = keyword.trim().toLowerCase();
  if (!kw) return mockEvents;
  return mockEvents.filter((e) =>
    `${e.title ?? ''} ${e.placeName ?? ''}`.toLowerCase().includes(kw),
  );
}, [keyword]);

 

마이 페이지 내에서 필터 기능도 만들었다. `?keyword=&period=&page=` 형태로 쿼리스트링을 잡았다. 대표적으로 신청 내역은 다음과 같이 처리 되었다.

type PeriodKey = '7d' | '15d' | '1m' | '2m' | '3m' | 'all';

const keyword = searchParams.get('keyword') ?? '';
const period = (searchParams.get('period') as PeriodKey) || 'all';

const filteredItems = items.filter((x) => {
  const kw = normalizeKeyword(keyword);
  const keywordOk = kw
    ? `${x.title} ${x.content}`.toLowerCase().includes(kw)
    : true;
  const days = periodToDays(period);
  const periodOk = days == null ? true : withinDays(x.appliedAt, days);
  return keywordOk && periodOk;
});

 

기간 버튼·검색 submit·페이지 변경 모두 `setSearchParams({ keyword, period, page })`로 맞춰서, URL만 보면 현재 필터를 알 수 있게 했다.

 

 

목업·이미지 추가

메인·카테고리·관심·신청·회원관리에 `picsum` 시드 URL을 넣고, 프로필은 원형 이미지로 보이게 바꿨다.

thumbnailUrl: `https://picsum.photos/seed/experience${i}/250/160`

{item.thumbnailUrl && (
  <img src={item.thumbnailUrl} alt="행사 이미지" />
)}

{row.profileImageUrl && (
  <img src={row.profileImageUrl} alt="프로필 이미지" className="w-10 h-10 rounded-full" />
)}

 

이 부분은 어디까지나 목업이므로 백엔드와 연결된다면 삭제될 예정이다.

 

이렇게 프론트엔드들은 과정이 마무리되어가고 있다.

반응형
«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31