Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
26 changes: 15 additions & 11 deletions src/__mocks/reviewDetailMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ export const reviewDetailMock: ReviewDetail[] = [
seatNumber: 'F6, F12',
},
hashtags: [
{ hashTagId: 1, hashTagName: '음향최고' },
{ hashTagId: 2, hashTagName: '시야좋음' },
{ hashTagId: 3, hashTagName: '자녀와' },
{
hashTagId: 1,
hashTagName: '음향최고',
hashTagType: '음향',
},
{ hashTagId: 2, hashTagName: '시야좋음', hashTagType: '관람환경' },
{ hashTagId: 3, hashTagName: '자녀와', hashTagType: '동반인' },
],
content: 'IMAX 사운드는 정말 감동적이에요. 앉은 좌석도 시야 확보 최고였습니다.',
rating: 5,
Expand All @@ -37,8 +41,8 @@ export const reviewDetailMock: ReviewDetail[] = [
seatNumber: 'E10',
},
hashtags: [
{ hashTagId: 4, hashTagName: '화질굿' },
{ hashTagId: 5, hashTagName: '대형스크린' },
{ hashTagId: 4, hashTagName: '화질굿', hashTagType: '관람환경' },
{ hashTagId: 5, hashTagName: '대형스크린', hashTagType: '관람환경' },
],
content: '화질도 좋고 화면 크기도 압도적입니다.',
rating: 4,
Expand All @@ -60,8 +64,8 @@ export const reviewDetailMock: ReviewDetail[] = [
seatNumber: 'A1',
},
hashtags: [
{ hashTagId: 6, hashTagName: '음질만족' },
{ hashTagId: 7, hashTagName: '출입구근처' },
{ hashTagId: 6, hashTagName: '음질만족', hashTagType: '음향' },
{ hashTagId: 7, hashTagName: '출입구근처', hashTagType: '관람환경' },
],
content: '사운드도 좋고 스크린도 컸는데 문이랑 가까워서 외부 소음이 조금 거슬렸어요 ㅎㅎ',
rating: 3.5,
Expand All @@ -86,8 +90,8 @@ export const reviewDetailMock: ReviewDetail[] = [
seatNumber: 'A1',
},
hashtags: [
{ hashTagId: 6, hashTagName: '음질만족' },
{ hashTagId: 7, hashTagName: '출입구근처' },
{ hashTagId: 6, hashTagName: '음질만족', hashTagType: '음향' },
{ hashTagId: 7, hashTagName: '출입구근처', hashTagType: '관람환경' },
],
content: '사운드도 좋고 스크린도 컸는데 문이랑 가까워서 외부 소음이 조금 거슬렸어요 ㅎㅎ',
rating: 3.5,
Expand All @@ -112,8 +116,8 @@ export const reviewDetailMock: ReviewDetail[] = [
seatNumber: 'D4',
},
hashtags: [
{ hashTagId: 8, hashTagName: '주변산만' },
{ hashTagId: 9, hashTagName: '잡음있음' },
{ hashTagId: 8, hashTagName: '주변산만', hashTagType: '관람환경' },
{ hashTagId: 9, hashTagName: '잡음있음', hashTagType: '음향' },
],
content:
'단차가 낮은 건지 앞사람 행동이 너무 거슬려요ㅜㅜ 그리고 상영관에서 냄새가 너무 마니 남!!!',
Expand Down
22 changes: 11 additions & 11 deletions src/__mocks/reviewSummaryMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ export const reviewSummaryMock: ReviewSummary[] = [
seatNumber: 'F6, F12',
},
hashtags: [
{ hashTagId: 1, hashTagName: '음향최고' },
{ hashTagId: 2, hashTagName: '시야좋음' },
{ hashTagId: 3, hashTagName: '자녀와' },
{ hashTagId: 1, hashTagName: '음향최고', hashTagType: '음향' },
{ hashTagId: 2, hashTagName: '시야좋음', hashTagType: '관람환경' },
{ hashTagId: 3, hashTagName: '자녀와', hashTagType: '동반인' },
],
content: 'IMAX 사운드는 정말 감동적이에요. 앉은 좌석도 시야 확보 최고였습니다.',
rating: 5,
Expand All @@ -37,8 +37,8 @@ export const reviewSummaryMock: ReviewSummary[] = [
seatNumber: 'E10',
},
hashtags: [
{ hashTagId: 4, hashTagName: '화질굿' },
{ hashTagId: 5, hashTagName: '대형스크린' },
{ hashTagId: 4, hashTagName: '화질굿', hashTagType: '관람환경' },
{ hashTagId: 5, hashTagName: '대형스크린', hashTagType: '관람환경' },
],
content: '화질도 좋고 화면 크기도 압도적입니다.',
rating: 4,
Expand All @@ -60,8 +60,8 @@ export const reviewSummaryMock: ReviewSummary[] = [
seatNumber: 'A1',
},
hashtags: [
{ hashTagId: 6, hashTagName: '음질만족' },
{ hashTagId: 7, hashTagName: '출입구근처' },
{ hashTagId: 6, hashTagName: '음질만족', hashTagType: '음향' },
{ hashTagId: 7, hashTagName: '출입구근처', hashTagType: '관람환경' },
],
content: '사운드도 좋고 스크린도 컸는데 문이랑 가까워서 외부 소음이 조금 거슬렸어요 ㅎㅎ',
rating: 3.5,
Expand All @@ -86,8 +86,8 @@ export const reviewSummaryMock: ReviewSummary[] = [
seatNumber: 'A1',
},
hashtags: [
{ hashTagId: 6, hashTagName: '음질만족' },
{ hashTagId: 7, hashTagName: '출입구근처' },
{ hashTagId: 6, hashTagName: '음질만족', hashTagType: '음향' },
{ hashTagId: 7, hashTagName: '출입구근처', hashTagType: '관람환경' },
],
content: '사운드도 좋고 스크린도 컸는데 문이랑 가까워서 외부 소음이 조금 거슬렸어요 ㅎㅎ',
rating: 3.5,
Expand All @@ -112,8 +112,8 @@ export const reviewSummaryMock: ReviewSummary[] = [
seatNumber: 'D4',
},
hashtags: [
{ hashTagId: 8, hashTagName: '주변산만' },
{ hashTagId: 9, hashTagName: '잡음있음' },
{ hashTagId: 8, hashTagName: '주변산만', hashTagType: '관람환경' },
{ hashTagId: 9, hashTagName: '잡음있음', hashTagType: '음향' },
],
content:
'단차가 낮은 건지 앞사람 행동이 너무 거슬려요ㅜㅜ 그리고 상영관에서 냄새가 너무 마니 남!!!',
Expand Down
2 changes: 1 addition & 1 deletion src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ api.interceptors.response.use(
return Promise.reject(apiError);
}

return res.data;
return response;
},
(error) => {
const status = error.response?.status;
Expand Down
8 changes: 8 additions & 0 deletions src/api/hashtag/hashtag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ApiResponse } from '@/types/api-response';
import api from '../api';
import type { Hashtag } from '@/types/hashtag';

export const getHashtags = async (): Promise<Hashtag[]> => {
const res = await api.get<ApiResponse<Hashtag[]>>('/api/v1/hashtag');
return res.data.data;
};
10 changes: 8 additions & 2 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { cinemaData } from '@/constants/cinema';
import { tagSections } from '@/constants/reviewTags';
import {
type TagKey,
type TagSectionConfig,
TAG_TYPE_TITLE_MAP,
REQUIRED_TAG_KEYS,
} from '@/constants/reviewTags';
import { cinemaInfoMap } from './theaterInfo';

export { cinemaData, tagSections, cinemaInfoMap };
export { cinemaData, cinemaInfoMap, TAG_TYPE_TITLE_MAP, REQUIRED_TAG_KEYS };
export type { TagSectionConfig, TagKey };
36 changes: 8 additions & 28 deletions src/constants/reviewTags.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type TagKey = 'sound' | 'environment' | 'companion';
export type TagKey = '음향' | '관람환경' | '동반인';

export interface TagSectionConfig {
key: TagKey;
Expand All @@ -7,30 +7,10 @@ export interface TagSectionConfig {
required: boolean;
}

export const tagSections: TagSectionConfig[] = [
{
key: 'sound',
title: '음향',
required: true,
options: ['#음향최고', '#음질깨끗', '#몰입감좋음', '#서라운드좋음', '#소리선명', '#현장감있음'],
},
{
key: 'environment',
title: '관람 환경',
required: true,
options: [
'#좌석편함',
'#시야탁트임',
'#쾌적한환경',
'#입출입편리',
'#청결유지잘됨',
'#분위기좋음',
],
},
{
key: 'companion',
title: '동반인',
required: false,
options: ['#혼자왔어요', '#친구와함께', '#연인과', '#형제자매와', '#부모님과', '#자녀와'],
},
];
export const TAG_TYPE_TITLE_MAP: Record<TagKey, string> = {
음향: '음향',
관람환경: '관람 환경',
동반인: '동반인',
};

export const REQUIRED_TAG_KEYS: TagKey[] = ['음향', '관람환경'];
83 changes: 66 additions & 17 deletions src/pages/review/TagPage.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,69 @@
import { ReviewStepLayout, TagSection } from '@/components';
import { useNavigate } from 'react-router-dom';
import { useReviewStore } from '@/store';
import { useEffect } from 'react';
import { tagSections } from '@/constants';
import { useEffect, useState } from 'react';
import type { Hashtag } from '@/types/hashtag';
import { getHashtags } from '@/api/hashtag/hashtag';
import {
TAG_TYPE_TITLE_MAP,
REQUIRED_TAG_KEYS,
type TagKey,
type TagSectionConfig,
} from '@/constants';

export default function ReviewTagsPage() {
const { isInitialized, tags, toggleTag } = useReviewStore();
const navigate = useNavigate();
const { isInitialized, tags, toggleTag } = useReviewStore();
const [tagSections, setTagSections] = useState<TagSectionConfig[]>([]);
const [isLoading, setIsLoading] = useState(true);
const canProceed = REQUIRED_TAG_KEYS.every((key) => tags[key].length > 0);

const handleNext = () => {
navigate('/review/form');
};

//초기 진입 조건 확인
useEffect(() => {
if (!isInitialized) {
navigate('/review');
}
}, [isInitialized, navigate]);

const canProceed = tags.sound.length > 0 && tags.environment.length > 0;
//해시태그 API
useEffect(() => {
const fetchTags = async () => {
try {
const data: Hashtag[] = await getHashtags();

const handleNext = () => {
navigate('/review/form');
};
const grouped = data.reduce<Record<TagKey, string[]>>(
(acc, tag) => {
const key = tag.hashTagType as TagKey;
if (!acc[key]) acc[key] = [];
acc[key].push(`#${tag.hashTagName}`);
return acc;
},
{} as Record<TagKey, string[]>,
);

const ORDERED_KEYS: TagKey[] = ['음향', '관람환경', '동반인'];

const parsed: TagSectionConfig[] = ORDERED_KEYS.filter((key) => grouped[key]).map(
(key) => ({
key,
title: TAG_TYPE_TITLE_MAP[key],
required: REQUIRED_TAG_KEYS.includes(key),
options: grouped[key],
}),
);
setTagSections(parsed);
} catch (error) {
console.error('해시태그 불러오기 실패:', error);
} finally {
setIsLoading(false);
}
};
fetchTags();
}, []);

return (
<ReviewStepLayout
Expand All @@ -29,16 +74,20 @@ export default function ReviewTagsPage() {
disabled={!canProceed}
>
<div className="flex flex-col overflow-y-auto pb-[88px]">
{tagSections.map(({ key, title, required, options }) => (
<TagSection
key={key}
title={title}
options={options}
required={required}
selected={tags[key]}
onChange={(value) => toggleTag(key, value)}
/>
))}
{isLoading ? (
<div className="text-caption-2">태그 불러오는 중</div>
) : (
tagSections.map(({ key, title, required, options }) => (
<TagSection
key={key}
title={title}
options={options}
required={required}
selected={tags[key]}
onChange={(value) => toggleTag(key, value)}
/>
))
)}
</div>
</ReviewStepLayout>
);
Expand Down
21 changes: 8 additions & 13 deletions src/store/useReviewStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,9 @@ interface ReviewState {
isInitialized: boolean;
setInitialized: () => void;
reset: () => void;
tags: {
sound: string[];
environment: string[];
companion: string[];
};
setTags: (type: 'sound' | 'environment' | 'companion', tags: string[]) => void;
toggleTag: (type: 'sound' | 'environment' | 'companion', tag: string) => void;
tags: Record<string, string[]>;
setTags: (type: '음향' | '관람환경' | '동반인', tags: string[]) => void;
toggleTag: (type: '음향' | '관람환경' | '동반인', tag: string) => void;
}

export const useReviewStore = create<ReviewState>((set) => ({
Expand Down Expand Up @@ -67,9 +63,9 @@ export const useReviewStore = create<ReviewState>((set) => ({
isInitialized: false,
}),
tags: {
sound: [],
environment: [],
companion: [],
음향: [],
관람환경: [],
동반인: [],
},
setTags: (type, tags) =>
set((state) => ({
Expand All @@ -80,11 +76,10 @@ export const useReviewStore = create<ReviewState>((set) => ({
})),
toggleTag: (type, tag) =>
set((state) => {
const current = state.tags[type];
const current = state.tags[type] ?? [];
const isSelected = current.includes(tag);

const totalSelected =
state.tags.sound.length + state.tags.sound.length + state.tags.companion.length;
const totalSelected = Object.values(state.tags).flat().length;

if (isSelected) {
return {
Expand Down
1 change: 1 addition & 0 deletions src/types/hashtag.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface Hashtag {
hashTagId: number;
hashTagName: string;
hashTagType: string;
}
5 changes: 0 additions & 5 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,4 @@ export default defineConfig({
'@': path.resolve(__dirname, 'src'),
},
},

});




Loading