Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion src/hooks/__tests__/useAdminAlcohols.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { http, HttpResponse } from 'msw';
import { server } from '@/test/mocks/server';
import { renderHook } from '@/test/test-utils';
import { wrapApiError } from '@/test/mocks/data';
import { useAdminAlcoholList } from '../useAdminAlcohols';
import { useAdminAlcoholList, useCategoryReferences } from '../useAdminAlcohols';
import { getCategoryGroup } from '@/types/api/alcohol.api';

const BASE = '/admin/api/v1/alcohols';

Expand Down Expand Up @@ -72,4 +73,58 @@ describe('useAdminAlcohols hooks', () => {
expect(result.current.data!.meta.totalElements).toBeGreaterThan(0);
});
});

// ==========================================
// useCategoryReferences
// ==========================================
describe('useCategoryReferences', () => {
it('카테고리 레퍼런스 목록을 반환한다', async () => {
const { result } = renderHook(() => useCategoryReferences());

await waitFor(() => expect(result.current.isSuccess).toBe(true));

expect(result.current.data!.length).toBeGreaterThan(0);
});

it('실제 API 응답에는 categoryGroup이 없다', async () => {
const { result } = renderHook(() => useCategoryReferences());

await waitFor(() => expect(result.current.isSuccess).toBe(true));

// 백엔드 CategoryReference API는 categoryGroup을 내려주지 않음
result.current.data!.forEach((ref) => {
expect(ref.categoryGroup).toBeUndefined();
});
});
});

// ==========================================
// getCategoryGroup (프론트엔드 매핑)
// ==========================================
describe('getCategoryGroup', () => {
it('메인 카테고리를 올바른 그룹으로 매핑한다', () => {
expect(getCategoryGroup('싱글 몰트')).toBe('SINGLE_MALT');
expect(getCategoryGroup('블렌디드')).toBe('BLEND');
expect(getCategoryGroup('블렌디드 몰트')).toBe('BLENDED_MALT');
expect(getCategoryGroup('버번')).toBe('BOURBON');
expect(getCategoryGroup('라이')).toBe('RYE');
});

it('싱글몰트 알코올 변형도 SINGLE_MALT로 매핑한다', () => {
expect(getCategoryGroup('싱글몰트 알코올')).toBe('SINGLE_MALT');
});

it('기타 하위 카테고리는 OTHER로 매핑한다', () => {
expect(getCategoryGroup('테네시')).toBe('OTHER');
expect(getCategoryGroup('싱글 그레인')).toBe('OTHER');
expect(getCategoryGroup('싱글 팟 스틸')).toBe('OTHER');
expect(getCategoryGroup('위트')).toBe('OTHER');
expect(getCategoryGroup('콘')).toBe('OTHER');
expect(getCategoryGroup('스피릿')).toBe('OTHER');
});

it('알 수 없는 카테고리도 OTHER로 매핑한다', () => {
expect(getCategoryGroup('새로운카테고리')).toBe('OTHER');
});
});
});
20 changes: 13 additions & 7 deletions src/pages/whisky/WhiskyList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,14 @@ import { Badge } from '@/components/ui/badge';
import { Pagination } from '@/components/common/Pagination';
import { useAdminAlcoholList } from '@/hooks/useAdminAlcohols';
import type { AlcoholSearchParams, AlcoholCategory } from '@/types/api';
import { ALCOHOL_CATEGORIES, CATEGORY_GROUP_LABELS, getCategoryGroup } from '@/types/api';

const CATEGORY_OPTIONS: { value: AlcoholCategory | 'ALL'; label: string }[] = [
{ value: 'ALL', label: '전체' },
{ value: 'SINGLE_MALT', label: '싱글몰트' },
{ value: 'BLEND', label: '블렌디드' },
{ value: 'BLENDED_MALT', label: '블렌디드 몰트' },
{ value: 'BOURBON', label: '버번' },
{ value: 'RYE', label: '라이' },
{ value: 'OTHER', label: '기타' },
...ALCOHOL_CATEGORIES.map((value) => ({
value,
label: CATEGORY_GROUP_LABELS[value],
})),
];

export function WhiskyListPage() {
Expand Down Expand Up @@ -246,7 +245,14 @@ export function WhiskyListPage() {
<TableCell className="text-muted-foreground">
{item.engName}
</TableCell>
<TableCell>{item.korCategoryName}</TableCell>
<TableCell>
<div className="flex flex-col gap-0.5">
<span>{item.korCategoryName}</span>
<span className="text-xs text-muted-foreground">
{CATEGORY_GROUP_LABELS[getCategoryGroup(item.korCategoryName)]}
</span>
</div>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{new Date(item.modifiedAt).toLocaleDateString('ko-KR')}
</TableCell>
Expand Down
21 changes: 21 additions & 0 deletions src/test/mocks/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
TastingTagAlcohol,
AlcoholListItem,
AlcoholDeleteResponse,
CategoryReference,
BannerListItem,
BannerDetail,
BannerCreateResponse,
Expand Down Expand Up @@ -158,6 +159,26 @@ export const mockAlcoholDeleteResponse: AlcoholDeleteResponse = {
responseAt: '2024-06-01T00:00:00',
};

// ============================================
// Category Reference Mock Data
// ============================================

/** 실제 API 응답과 동일한 구조 (categoryGroup 없음) */
export const mockCategoryReferences: CategoryReference[] = [
{ korCategory: '라이', engCategory: 'Rye' },
{ korCategory: '버번', engCategory: 'Bourbon' },
{ korCategory: '블렌디드', engCategory: 'Blend' },
{ korCategory: '블렌디드 몰트', engCategory: 'Blended Malt' },
{ korCategory: '스피릿', engCategory: 'Spirit' },
{ korCategory: '싱글 그레인', engCategory: 'Single Grain' },
{ korCategory: '싱글 몰트', engCategory: 'Single Malt' },
{ korCategory: '싱글 팟 스틸', engCategory: 'Single Pot Still' },
{ korCategory: '싱글몰트 알코올', engCategory: 'Single Malts' },
{ korCategory: '위트', engCategory: 'Wheat' },
{ korCategory: '콘', engCategory: 'Corn' },
{ korCategory: '테네시', engCategory: 'Tennessee' },
];

// ============================================
// Banner Mock Data
// ============================================
Expand Down
55 changes: 53 additions & 2 deletions src/types/api/alcohol.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,57 @@ export type AlcoholUpdateRequest = AlcoholApiTypes['update']['request'];
/** 술 수정 응답 데이터 */
export type AlcoholUpdateResponse = AlcoholApiTypes['update']['response'];

// ============================================
// 카테고리 그룹 라벨
// ============================================

/** 카테고리 그룹별 한글 라벨 */
export const CATEGORY_GROUP_LABELS: Record<AlcoholCategory, string> = {
SINGLE_MALT: '싱글몰트',
BLEND: '블렌디드',
BLENDED_MALT: '블렌디드 몰트',
BOURBON: '버번',
RYE: '라이',
OTHER: '기타',
};

/** 카테고리 그룹 키 목록 (타입 안전) */
export const ALCOHOL_CATEGORIES: AlcoholCategory[] = Object.keys(CATEGORY_GROUP_LABELS) as AlcoholCategory[];

/**
* korCategory → categoryGroup 매핑
* 메인 5개 그룹에 해당하는 카테고리만 명시, 나머지는 모두 OTHER
*/
const CATEGORY_TO_GROUP_MAP: Record<string, AlcoholCategory> = {
'싱글 몰트': 'SINGLE_MALT',
'싱글몰트 알코올': 'SINGLE_MALT',
'블렌디드': 'BLEND',
'블렌디드 몰트': 'BLENDED_MALT',
'버번': 'BOURBON',
'라이': 'RYE',
};

/**
* korCategory 문자열로부터 categoryGroup을 결정한다.
* CategoryReference API가 categoryGroup을 내려주지 않으므로 프론트에서 매핑.
* 메인 5개 그룹에 해당하지 않으면 OTHER로 분류.
*/
export function getCategoryGroup(korCategory: string): AlcoholCategory {
return CATEGORY_TO_GROUP_MAP[korCategory] ?? 'OTHER';
}

/**
* categoryGroup → 고정 카테고리 이름 매핑 (메인 5개 그룹용)
* OTHER는 자유 입력이므로 포함하지 않음
*/
export const GROUP_TO_CATEGORY: Record<Exclude<AlcoholCategory, 'OTHER'>, { korCategory: string; engCategory: string }> = {
SINGLE_MALT: { korCategory: '싱글 몰트', engCategory: 'Single Malt' },
BLEND: { korCategory: '블렌디드', engCategory: 'Blend' },
BLENDED_MALT: { korCategory: '블렌디드 몰트', engCategory: 'Blended Malt' },
BOURBON: { korCategory: '버번', engCategory: 'Bourbon' },
RYE: { korCategory: '라이', engCategory: 'Rye' },
};

// ============================================
// 카테고리 레퍼런스 타입
// ============================================
Expand All @@ -357,6 +408,6 @@ export interface CategoryReference {
korCategory: string;
/** 영문 카테고리 */
engCategory: string;
/** 카테고리 그룹 */
categoryGroup: AlcoholCategory;
/** 카테고리 그룹 (API 응답에 포함되지 않을 수 있음) */
categoryGroup?: AlcoholCategory;
}
Loading