콘텐츠로 이동

[YETI] PLP 필터

YETI PLP의 카테고리 탭과 필터 바 동작 정책.

  • 모든 필터 상태는 URL 파라미터와 동기화된다 (?mainCategoryId=1&subcategoryIds=2&colorCodes=blue 등)
  • 필터 변경 시 페이지 리로드 없이 AJAX로 상품 목록과 Facet을 동시에 갱신한다
  • Facet이란 현재 필터 조건에서 선택 가능한 옵션과 각 옵션의 상품 수(count)를 담은 데이터다

사용자가 PLP 진입
→ 프론트: mainCategoryId만 포함한 필터 API 호출
→ 서버: 해당 카테고리의 전체 상품 기준으로 Facet 집계 후 반환
→ 프론트: 받은 Facet으로 필터 UI 렌더링
사용자가 필터 선택 (탭, Product Type, Color, Size, Attribute, Price)
→ 프론트: URL 파라미터 업데이트 + 필터 API 재호출
→ 서버: 선택된 필터 조건으로 상품 범위 좁힘 → Facet 재집계
→ 프론트: 상품 목록 + 필터 UI 동시 갱신 (count 0인 옵션 자동 숨김)

YETI 카테고리는 두 가지 유형이 있으며, subcategory.sub_type 값에 따라 PLP 화면 구성이 달라진다.

1차 카테고리 하위에 아이콘+이름으로 구성된 탭이 있고, 탭을 선택하면 그 탭의 하위 Product Type이 필터에 표시된다.

[Hero 섹션]
[Subcategory Tabs] ← 아이콘 + 탭명, 예: 🥤 Hydration | ☕ Coffee & Tea | 🍸 Barware
[Filter Bar] ← Product Type | Color | Size | Attribute | Price
[상품 그리드]

탭 선택 전: 전체 상품 표시, 모든 탭의 Product Type이 필터에 노출
탭 선택 후: 해당 탭의 상품만 표시, 해당 탭 하위의 Product Type만 필터에 노출

탭이 없고 Product Type 필터만 존재한다.

[Hero 섹션]
[Filter Bar] ← Product Type | Color | Size | Attribute | Price
[상품 그리드]

1차 카테고리 탭명 (sub_type=TAB) 탭 아이콘 파일 하위 Product Type
Drinkware Hydration hydration.svg Bottles, Tumblers, Jugs
Drinkware Coffee & Tea coffee.svg Mugs, French Press
Drinkware Barware barware.svg Cocktail Shaker, Flask
Cases & Storage Buckets buckets.svg
Cases & Storage Hard Cases hard-cases.svg
Cases & Storage Accessories accessories.svg
Outdoor Living Chairs chairs.svg
Outdoor Living Blankets blankets.svg
Outdoor Living Dogs dogs.svg
Outdoor Living Accessories accessories.svg
1차 카테고리 Product Type (sub_type=CATEGORY)
Hard Coolers Ice Buckets, Ice Chests, Water Coolers, Wheeled Coolers
Bags Backpacks, Duffels, Tote Bags, Luggage, Lunch Coolers, Tote Coolers, Gear Case, Packing Cubes, Adjustable Strap
Kitchen Cast Iron Ranch Pan, Cast Iron Skillets, Fire Pit, Food Jars, Food Storage, Sets
Apparel Hats & Headgear, Men’s / Women’s / Kids’ / Unisex Apparel, Sweatshirts
Dogs (서브카테고리 없음 — Product Type 필터 미사용)

필터 렌더링 방식 선택 방식
Category 텍스트 버튼 (서브카테고리 단위) 단일 선택
Product Type 텍스트 버튼 단일 선택
Color 원형 스와치 이미지. 로드 실패 시 hex 폴백. 멀티컬러는 스플릿 이미지 복수 선택
Size 텍스트 버튼 복수 선택
Attribute (Beverage Type·Lid Type·Features·Collection·Material·Gender) key별 섹션 + 텍스트 버튼 key 내 복수 선택
Price 카테고리별 프리셋 구간 버튼 (동적 카운트) 단일 선택

카테고리별 필터 구성 (최종 확정)

섹션 제목: “카테고리별 필터 구성 (최종 확정)”

카테고리마다 노출하는 필터 그룹과 순서가 다르다. 아래 목록이 카테고리별 필터 바/사이드패널에 표시되는 전체 필터다 — 목록에 없는 필터는 해당 카테고리에 노출하지 않는다.

카테고리 필터 목록 (표시 순서)
Drinkware Product Type, Size, Color, Beverage Type, Lid Type, Features
Coolers Category, Product Type, Collection, Price, Color
Bags Product Type, Collection, Price, Size, Features, Gender, Color
Kitchen Product Type, Material, Features, Color, Price
Case & Storage Product Type, Price, Size, Color
Outdoor Living Product Type, Collection, Price, Color
Oracle Red Bull Racing 필터 없음 — 취급 상품 1개뿐이라 필터 UI 자체 미노출

Category 필터 (Coolers 전용): Coolers는 서브카테고리가 Hard Coolers·Soft Coolers·Accessories 3개로 구성된다 (메가메뉴 카테고리별 노출 항목 참고). 이 서브카테고리 단위 선택을 Product Type과 별개의 “Category” 필터로 노출한다. TAB 방식으로 구현할지 별도 필터 그룹으로 구현할지는 개발팀 확정 필요 — 아래 미결 사항 참고.

Gender·Material 신규 Attribute: Bags의 Gender, Kitchen의 Material은 기존 8개 attribute key에 없던 신규 key다. 아래 상품 속성 필터 (Attribute) 표에 추가 반영.


가격 구간은 백오피스 category_price_range 테이블에서 카테고리별로 관리한다.
구간이 등록되지 않은 카테고리는 Price 필터 자체가 노출되지 않는다.

카테고리 구간
Hard Coolers $200–299, $300–399, $400–499, $500+
Bags $0–27, $28–35, $40–49, $50–99, $100–199, $200–299, $300–399, $400–499
Kitchen $0–49, $50–149, $150–299, $300–1000
Apparel $0–27, $28–35, $40–49, $50–99
Cases & Storage $0–27, $28–35, $40–49, $50–99, $100–199, $200–299, $300–399
Outdoor Living $0–27, $28–35, $50–99, $100–199, $200–299, $300–399, $500+

maxPrice 없는 구간은 “이상”($N+)으로 표시. 예: min=500, max=NULL → $500+


상품에 태깅하는 속성값. product_attribute 테이블에 저장되며 ERP 연동 또는 백오피스 수동 입력으로 관리한다.
하나의 상품에 동일 key로 여러 value를 태깅할 수 있다 (예: features에 Insulated + Leakproof 동시 태깅).

key 필터 표시명 예시 value 우선순위
beverageType Beverage Type Cold Beverages, Hot Beverages, Carbonated 높음 (Drinkware 필수)
lidType Lid Type Straw, Chug, MagSafe, Hotshot 높음 (Drinkware 필수)
features Features Insulated, Leakproof, Dishwasher Safe 높음 (Drinkware/Bags/Kitchen)
collection Collection Tundra, Roadie, Panga 높음 (Coolers/Bags/Outdoor Living)
gender Gender Men’s, Women’s, Unisex 높음 (Bags 신규)
material Material Stainless Steel, Cast Iron 높음 (Kitchen 신규)
lifestyle Lifestyle Outdoor, Everyday, Travel 중간
activity Activity Hiking, Camping, Running 중간
tripLength Trip Length Day Trip, Weekend, Week+ 낮음 (Coolers/Bags)
canCount Can Count 12-can, 20-can, 35-can 낮음 (Hard Coolers)

value 목록은 운영 중 추가될 수 있으므로 관리자가 편집 가능한 구조로 구현 권장.


사용자 액션 URL 파라미터 변화 서버 동작
PLP 진입 ?mainCategoryId=X 전체 상품 기준 Facet 반환
탭 클릭 subcategoryIds=Y 추가 해당 탭 상품 기준 Facet 재계산
탭 재클릭(해제) subcategoryIds 제거 전체 상품 기준 초기 복귀
탭 전환 subcategoryIds 교체 + categoryCodes 초기화 이전 탭 Product Type 선택 해제 후 재계산
Product Type 선택/해제 categoryCodes 토글 Facet 재계산
Attribute 필터 선택/해제 attributes=key:value 토글 Facet 재계산
가격 구간 선택 minPrice=N&maxPrice=M 설정 가격 범위 내 상품 기준 Facet 재계산
Clear All subcategoryIds만 유지, 나머지 필터 파라미터 제거 탭 유지한 채 필터만 초기화

Clear All이 탭을 유지하는 이유: 탭은 필터가 아닌 카테고리 네비게이션 역할이다. 탭을 해제하면 사용자가 보던 카테고리 자체가 바뀌어버리므로, Clear All은 탭 선택 상태는 건드리지 않고 Product Type/Color/Size/Attribute/Price만 초기화한다.


  • 동일 필터 내: 단일 선택 (Product Type, Price)
  • 다른 필터 간: 복합 선택 가능
  • 선택된 항목은 리스트 최상단으로 이동
  • Desktop: 필터 사이드패널 (상품 그리드 좌측 고정, 기본 접힘) / Mobile: 전체 화면 오버레이 + 수평 스크롤
  • Facet count가 0인 옵션은 자동 숨김 (응답에서 제외됨)

1. 카테고리 관리 — TAB 구분 표시

섹션 제목: “1. 카테고리 관리 — TAB 구분 표시”

서브카테고리 목록에서 sub_type 값을 배지로 표시할 때 TAB을 추가한다.

sub_type 표시
CATEGORY 기존과 동일
GROUP 기존과 동일
TAB 신규 — 목록 행에 TAB 배지 표시

2. 서브카테고리 추가/편집 팝업 — TAB 라디오 + 탭 아이콘 업로드

섹션 제목: “2. 서브카테고리 추가/편집 팝업 — TAB 라디오 + 탭 아이콘 업로드”

구분 라디오 버튼에 TAB 항목을 추가하고, TAB 선택 시 탭 아이콘 업로드 필드를 조건부로 노출한다.

팝업 구성 (TAB 선택 상태)

[구분] ( ) CATEGORY ( ) GROUP (●) TAB
[카테고리명] ________________________________
[카테고리 코드] ________________________________
[탭 아이콘] [파일 선택] ← TAB 선택 시에만 표시 (SVG 권장, 필수 아님)
[정렬순서] ________________________________
[노출여부] ☑
[취소] [저장]
  • 구분 라디오에서 TAB 선택하는 즉시 탭 아이콘 필드 슬라이드/페이드로 노출
  • CATEGORY 또는 GROUP으로 변경하는 즉시 탭 아이콘 필드 숨김
  • 저장 시 tab_icon 컬럼에 업로드된 이미지 URL 저장. TAB이 아닌 경우 NULL

3. 가격 필터 구간 관리 (Price Range)

섹션 제목: “3. 가격 필터 구간 관리 (Price Range)”

1차 카테고리 상세/편집 화면에 “가격 필터 구간” 섹션 추가.
여기서 등록한 구간이 category_price_range 테이블에 저장되고, 필터 API에서 이 구간으로 동적 카운트를 계산해 반환한다.

UI 구성

[가격 필터 구간]
| # | 최소가($) | 최대가($) | 삭제 |
|---|----------|--------------|------|
| 1 | 0 | 27 | [×] |
| 2 | 28 | 35 | [×] |
| 3 | 40 | 49 | [×] |
| 4 | 500 | (비워두기) | [×] | ← $500+로 표시됨
[+ 구간 추가]
  • 구간 추가/삭제/순서 변경(드래그) 가능
  • 최대가를 비워두면 max_price=NULL로 저장 → PLP에서 “$500+” 형식으로 표시
  • 구간이 하나도 없는 카테고리는 PLP에서 Price 필터 자체를 미노출
  • 구간 경계값 중복/간격 검증 권장 (예: 127, 3035 같은 간격 발생 시 경고)
  • 통화 단위: YETI = USD

4. 상품 편집 화면 — 속성 태그(Attribute Tags) 섹션 추가

섹션 제목: “4. 상품 편집 화면 — 속성 태그(Attribute Tags) 섹션 추가”

기존 상품 편집 화면에 속성 태그 섹션을 추가한다.
이 데이터가 product_attribute 테이블에 저장되어 Attribute 필터 Facet 집계에 사용된다.

항목 내용
입력 방식 key별 드롭다운, 멀티 선택 가능
직접 입력 불가 — 선택 방식 전용 (표기 통일성 유지)
멀티 태깅 동일 key에 여러 value 동시 태깅 가능 (예: features → Insulated + Leakproof)
ERP 연동 시 수동 입력 대신 ERP 배치로 자동 채워짐 (연동 여부 미결)

파라미터 타입 설명 비고
sourceSite string 사이트 구분 기존
mainCategoryId number 1차 카테고리 ID 기존
subcategoryIds number[] TAB형: 선택된 탭 ID / CATEGORY형: 선택된 Product Type ID 기존
categoryCodes string[] TAB형에서 2nd Product Type 선택 시 해당 카테고리 코드 목록. 응답의 categoryCodes 값을 그대로 전달 기존
colorCodes string[] 컬러 필터 기존
sizes string[] 사이즈 필터 기존
minPrice number 가격 범위 최솟값 기존
maxPrice number 가격 범위 최댓값 기존
attributes string[] 속성 필터. "key:value" 형식. 예: ["beverageType:cold-beverages", "features:insulated"] 신규

CATEGORY Facet Resolver — TAB 분기 로직

섹션 제목: “CATEGORY Facet Resolver — TAB 분기 로직”

Facet API에서 카테고리 데이터를 반환할 때, sub_typesubcategoryIds 파라미터 유무에 따라 아래와 같이 분기한다.

sub_type subcategoryIds 파라미터 nth=1 반환 (탭/Product Type 목록) nth=2 반환 (하위 Product Type)
CATEGORY 없음 전체 서브카테고리 목록 (Product Type 필터용)
CATEGORY 있음 전체 목록 + 선택 항목 표시 선택된 항목의 하위만 (기존 동작)
TAB 없음 전체 탭 목록 (실제 count 포함) 전체 탭의 모든 2nd Product Type
TAB 있음 전체 탭 목록 (count 고정, 필터 무관) 선택된 탭의 하위 2nd만

TAB 모드에서 nth=1 count를 고정하는 이유
탭은 필터가 아닌 네비게이션 역할이다. 만약 탭의 count가 현재 필터 조건을 반영해 0이 되면 탭 자체가 UI에서 사라져버린다. 사용자가 필터를 적용한 상태에서도 탭 메뉴는 항상 전부 보여야 하므로, 탭 count는 필터 조건과 무관하게 고정한다.


product_attribute 테이블을 현재 필터 조건에 매칭되는 상품 범위 기준으로 집계한다.
기존 Color, Size Facet과 동일한 방식으로 동작하며, attribute_key별로 묶어 반환한다.

  • 집계 결과가 0개인 key는 응답에서 제외 (프론트에서 해당 필터 섹션 자동 숨김)
  • 상품 속성 데이터 출처: ERP 연동 또는 백오피스 수동 입력 (미결 — 아래 미결 사항 참고)

PRICE Facet — 프리셋 구간 동적 카운트 (신규)

섹션 제목: “PRICE Facet — 프리셋 구간 동적 카운트 (신규)”

기존에는 가격 구간을 코드에 하드코딩했으나, 이제 category_price_range 테이블에서 구간을 동적으로 조회하고 count도 실시간으로 계산한다.

동작 순서

  1. mainCategoryIdcategory_price_range 테이블에서 구간 목록 조회
  2. 현재 필터 조건(탭/Product Type/Color/Size/Attribute)에 매칭되는 상품 범위를 먼저 확정
  3. 확정된 상품 범위 안에서 각 구간별 상품 수 계산
  4. count=0인 구간은 응답에서 제외
  5. 모든 구간의 count가 0이면 prices Facet 자체를 응답에서 제외

사용자가 가격 구간을 선택하면 minPrice/maxPrice 파라미터로 전달됨. 이때 다른 Facet(Color, Size, Attribute 등)은 가격 조건이 추가 적용된 상품 범위로 재계산됨.


기존 categories Facet 응답에 아래 필드가 추가된다.

필드 위치 타입 설명
subType nth=1 항목 string "TAB" 또는 "CATEGORY"
tabIcon nth=1, subType=TAB 항목 string | null 탭 아이콘 이미지 URL. TAB이 아니면 null
categoryCodes nth=2 항목 string[] 해당 Product Type의 카테고리 코드 목록. 프론트가 이 값을 그대로 categoryCodes 파라미터로 재전달
{
"attributes": [
{
"key": "beverageType",
"displayName": "Beverage Type",
"options": [
{ "value": "cold-beverages", "label": "Cold Beverages", "count": 42 },
{ "value": "hot-beverages", "label": "Hot Beverages", "count": 18 },
{ "value": "carbonated", "label": "Carbonated", "count": 7 }
]
},
{
"key": "features",
"displayName": "Features",
"options": [
{ "value": "insulated", "label": "Insulated", "count": 95 },
{ "value": "leakproof", "label": "Leakproof", "count": 60 },
{ "value": "dishwasher-safe", "label": "Dishwasher Safe", "count": 44 }
]
}
]
}
  • count=0인 option은 포함되지 않음
  • 현재 필터 조건 기준으로 매칭 상품이 없는 key는 배열에서 제외
{
"prices": [
{ "minPrice": 0, "maxPrice": 27, "count": 15 },
{ "minPrice": 28, "maxPrice": 35, "count": 22 },
{ "minPrice": 40, "maxPrice": 49, "count": 18 },
{ "minPrice": 500, "maxPrice": null, "count": 3 }
]
}
  • maxPrice: null = “이상” ($N+)
  • count=0인 구간은 포함되지 않음
  • 전체 count=0이면 prices 키 자체를 응답에서 제외

⚠️ 기존 사이트 영향 확인 필요: 기존 JP/KR 사이트에서 count=0인 필터를 현재 노출 중인 케이스가 있는지 확인 필요. 있다면 sourceSite=YETI 조건으로 분기 처리해 기존 사이트에 영향이 없도록 한다.


기존 sub_type ENUM에 'TAB' 값을 추가하고, 탭 아이콘 URL을 저장할 tab_icon 컬럼을 추가한다.
기존 데이터는 영향 없음 (tab_icon은 NULL로 추가됨).

ALTER TABLE subcategory
MODIFY COLUMN sub_type ENUM('CATEGORY', 'GROUP', 'TAB') NOT NULL,
ADD COLUMN tab_icon VARCHAR(255) NULL AFTER sub_type;
컬럼 변경 내용
sub_type ENUM에 'TAB' 추가
tab_icon VARCHAR(255) NULL 신규 추가. sub_type='TAB'인 경우에만 사용

2. product_attribute 테이블 신규 생성

섹션 제목: “2. product_attribute 테이블 신규 생성”

상품에 속성 태그를 태깅하는 테이블. 1:N 구조 — 하나의 상품이 동일 key로 여러 value를 가질 수 있다.

CREATE TABLE product_attribute (
id BIGINT NOT NULL AUTO_INCREMENT,
product_id BIGINT NOT NULL,
attribute_key VARCHAR(50) NOT NULL, -- beverageType, lidType, features ...
attribute_value VARCHAR(100) NOT NULL, -- Cold Beverages, Straw, Insulated ...
sort INT NOT NULL DEFAULT 0,
PRIMARY KEY (id),
INDEX idx_attribute_facet (attribute_key, attribute_value), -- Facet 집계 쿼리 최적화
INDEX idx_product (product_id),
FOREIGN KEY (product_id) REFERENCES products(id)
);

예시 데이터 (상품 ID=100인 Drinkware 텀블러)

id product_id attribute_key attribute_value sort
1 100 beverageType cold-beverages 0
2 100 beverageType hot-beverages 1
3 100 lidType straw 0
4 100 features insulated 0
5 100 features leakproof 1

3. category_price_range 테이블 신규 생성

섹션 제목: “3. category_price_range 테이블 신규 생성”

카테고리별 가격 필터 프리셋 구간을 관리하는 테이블. 백오피스에서 카테고리별로 구간을 설정하면 이 테이블에 저장되고, 필터 API가 이 구간으로 동적 카운트를 계산한다.

CREATE TABLE category_price_range (
id BIGINT NOT NULL AUTO_INCREMENT,
main_category_id BIGINT NOT NULL,
min_price INT NOT NULL, -- 구간 최솟값
max_price INT NULL, -- 구간 최댓값. NULL = "이상" ($N+)
sort INT NOT NULL DEFAULT 0, -- 표시 순서
PRIMARY KEY (id),
INDEX idx_category (main_category_id),
FOREIGN KEY (main_category_id) REFERENCES main_category(id)
);

예시 데이터 (Hard Coolers 카테고리)

id main_category_id min_price max_price sort
1 3 200 299 0
2 3 300 399 1
3 3 400 499 2
4 3 500 NULL 3

데이터 세팅 (개발 완료 후 QA 전 선행 필요)

섹션 제목: “데이터 세팅 (개발 완료 후 QA 전 선행 필요)”

디자인팀에 SVG 파일 제작 요청 후 업로드하여 subcategory.tab_icon에 URL 입력.

카테고리 필요 SVG 파일
Drinkware hydration.svg, coffee.svg, barware.svg
Cases & Storage buckets.svg, hard-cases.svg, accessories.svg
Outdoor Living chairs.svg, blankets.svg, dogs.svg, accessories.svg

product_attribute 테이블 생성 후 전체 상품에 대해 attribute_key 기준으로 태그 입력.
백오피스 드롭다운 value를 기준으로 입력해야 표기가 통일됨 — 태깅 기준 가이드 문서 별도 작성 권장.

key 우선순위 대상
beverageType 높음 Drinkware 전 상품 필수
lidType 높음 Drinkware 전 상품 필수
features 높음 Drinkware/Bags/Kitchen
collection 높음 Coolers/Bags/Outdoor Living
gender 높음 Bags 전 상품 필수
material 높음 Kitchen 전 상품 필수
lifestyle 중간 전 카테고리
activity 중간 전 카테고리
tripLength 낮음 Coolers/Bags 중심
canCount 낮음 Hard Coolers 중심

항목 담당 내용
product_attribute ERP 연동 여부 ERP팀 + YETI팀 아래 10개 key의 상품 속성 데이터를 ERP에서 관리/연동할 수 있는지 확인 필요. 불가 시 백오피스 수동 입력으로 대체 (이 경우 상품 속성 태그 UI가 주요 입력 수단이 됨)
0개 Facet 미반환 분기 개발팀 기존 JP/KR 사이트에서 count=0 필터를 현재 노출 중인 케이스 확인 후 sourceSite=YETI 분기 처리 여부 결정
Coolers Category 필터 구현 방식 개발팀 Hard Coolers·Soft Coolers·Accessories 서브카테고리 선택을 기존 TAB 방식(subcategoryIds)으로 구현할지, Product Type과 별개의 새 필터 그룹으로 구현할지 확정 필요
Gender/Material Facet 응답 구조 개발팀 신규 attribute key 추가에 따른 attributes 응답 스키마 영향 확인 (기존 구조 그대로 확장 가능한지)

ERP 연동 확인이 필요한 attribute_key

key 필터 표시명
beverageType Beverage Type
lidType Lid Type
features Features
collection Collection
gender Gender
material Material
lifestyle Lifestyle
activity Activity
tripLength Trip Length
canCount Can Count

버전 날짜 변경 내용
v2.3 2026-07-28 카테고리별 필터 구성 최종 확정 — Drinkware(Product Type·Size·Color·Beverage Type·Lid Type·Features), Coolers(Category·Product Type·Collection·Price·Color), Bags(Product Type·Collection·Price·Size·Features·Gender·Color), Kitchen(Product Type·Material·Features·Color·Price), Case & Storage(Product Type·Price·Size·Color), Outdoor Living(Product Type·Collection·Price·Color). Oracle Red Bull Racing은 필터 없음. Gender·Material attribute key 신규 추가
v2.2 2026-07-23 필터 UI 위치를 “필터 바 인라인 펼침” → “사이드패널(좌측 고정, 기본 접힘)” 으로 변경
v2.1 2026-07-09 전체 재작성 — 개발자 친화적 구성으로 개선. 흐름 요약·분기 이유·예시 데이터 추가
v2.0 2026-07-09 백오피스 변경(TAB 구분·탭아이콘 업로드·가격구간 관리·속성태그 UI), 필터 API 스펙(attributes 신규·TAB 분기·PRICE 동적카운트·응답 구조), DB 스키마, 데이터 세팅 항목 추가
v1.0 2026-06-24 최초 작성