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
68 changes: 48 additions & 20 deletions app/result/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function Page() {
}

return midpointData.data.map((midpoint, index) => {
const { endStation, endStationLine, userRoutes } = midpoint;
const { endStation, endStationLine, userRoutes, hot } = midpoint;

const routesWithColor = userRoutes.map((route) => {
return {
Expand Down Expand Up @@ -107,18 +107,52 @@ export default function Page() {
transferPath: myRoute?.transferPath || [],
transferPathLines,
userRoutes: routesWithColor,
hot,
};
});
}, [midpointData, myNickname, id]);

// 카테고리 텍스트 생성 함수
const getCategoryText = (category: string | undefined): string => {
if (!category) return '밍글링 추천 1위';
const getCategoryText = (
category: string | undefined,
hot: boolean | undefined,
rank: number,
): string => {
const purposeText = '[모임 목적]';
const lastChar = purposeText.charCodeAt(purposeText.length - 1);
const hasJongseong = (lastChar - 0xac00) % 28 !== 0;
const purposeTextWithPostfix = `${purposeText}${hasJongseong ? '이' : '가'} 많은 장소`;


if (hot === true && rank === 1) {
return `밍글링 추천 1위 · ${purposeTextWithPostfix}`;
}


else if (hot === true && rank === 2) {
return `밍글링 추천 2위 · ${purposeTextWithPostfix}`;
}


else if (hot === true) {
return purposeTextWithPostfix;
}


else if (rank === 1) {
return '밍글링 추천 1위';
}

else if (rank === 2) {
return '밍글링 추천 2위';
}

else if (!category) return '밍글링 추천 1위';
Comment on lines +142 to +150
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

rank >= 3이고 category가 없는 경우 '밍글링 추천 1위'로 폴백됩니다.

hot이 false이고 rank가 3 이상일 때, category도 없으면 Line 150의 else if (!category) 분기에 도달하여 '밍글링 추천 1위'를 반환합니다. Top3 결과에서 3번째 항목에 "1위" 라벨이 표시될 수 있습니다.

기존 코드에서도 동일한 동작이었으나, 이번에 rank 기반 로직을 추가했으므로 함께 수정하는 것이 좋겠습니다.

🛡️ 수정 제안
     else if (rank === 2) {
       return '밍글링 추천 2위';
     }
 
-    else if (!category) return '밍글링 추천 1위';
+    else if (!category) return `밍글링 추천 ${rank}위`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else if (rank === 1) {
return '밍글링 추천 1위';
}
else if (rank === 2) {
return '밍글링 추천 2위';
}
else if (!category) return '밍글링 추천 1위';
else if (rank === 1) {
return '밍글링 추천 1위';
}
else if (rank === 2) {
return '밍글링 추천 2위';
}
else if (!category) return `밍글링 추천 ${rank}위`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/result/`[id]/page.tsx around lines 142 - 150, The fallback branch that
returns '밍글링 추천 1위' when !category incorrectly applies to items with rank >= 3;
update the logic that produces the label (the branch checking rank === 1, rank
=== 2, and else if (!category)) so the !category case respects rank: either
check !category earlier and compute the label from rank (e.g. return `밍글링 추천
${rank}위` for rank >= 3) or change the else if (!category) to include rank
checks (e.g. else if (!category && rank <= 2) ... else return `밍글링 추천
${rank}위`), ensuring rank, category and the exact returned string literals are
adjusted accordingly.


// 카테고리 종성에 따라 "이/가"를 다르게 렌더링
const lastChar = category.charCodeAt(category.length - 1);
const hasJongseong = (lastChar - 0xac00) % 28 !== 0;
return `${category}${hasJongseong ? '이' : '가'} 많은 장소`;
const categoryLastChar = category.charCodeAt(category.length - 1);
const categoryHasJongseong = (categoryLastChar - 0xac00) % 28 !== 0;
return `${category}${categoryHasJongseong ? '이' : '가'} 많은 장소`;
};
Comment on lines +116 to 156
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

purposeText에 실제 카테고리 값 대신 리터럴 플레이스홀더 '[모임 목적]'이 하드코딩되어 있습니다.

PR 명세에서 [모임 목적]은 실제 모임 목적(예: "카페", "술집")으로 치환되어야 할 플레이스홀더입니다. 현재 코드는 이 문자열을 그대로 UI에 노출하므로, 사용자에게 "[모임 목적]이 많은 장소"라는 의미 없는 텍스트가 표시됩니다.

category 파라미터를 활용하여 실제 카테고리 값으로 텍스트를 생성해야 합니다. 또한 '[모임 목적]'의 마지막 문자가 ']'(비한글)이므로 종성 판별 로직도 의도와 다른 결과를 산출합니다.

🐛 수정 제안: category 파라미터를 활용한 수정
   const getCategoryText = (
     category: string | undefined,
     hot: boolean | undefined,
     rank: number,
   ): string => {
-    const purposeText = '[모임 목적]';
-    const lastChar = purposeText.charCodeAt(purposeText.length - 1);
-    const hasJongseong = (lastChar - 0xac00) % 28 !== 0;
-    const purposeTextWithPostfix = `${purposeText}${hasJongseong ? '이' : '가'} 많은 장소`;
+    const getPurposeTextWithPostfix = (text: string) => {
+      const lastChar = text.charCodeAt(text.length - 1);
+      const hasJongseong = (lastChar - 0xac00) % 28 !== 0;
+      return `${text}${hasJongseong ? '이' : '가'} 많은 장소`;
+    };
+
+    const purposeTextWithPostfix = category
+      ? getPurposeTextWithPostfix(category)
+      : '인기 많은 장소';
 
     if (hot === true && rank === 1) {
       return `밍글링 추천 1위 · ${purposeTextWithPostfix}`;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getCategoryText = (
category: string | undefined,
hot: boolean | undefined,
rank: number,
): string => {
const purposeText = '[모임 목적]';
const lastChar = purposeText.charCodeAt(purposeText.length - 1);
const hasJongseong = (lastChar - 0xac00) % 28 !== 0;
const purposeTextWithPostfix = `${purposeText}${hasJongseong ? '이' : '가'} 많은 장소`;
if (hot === true && rank === 1) {
return `밍글링 추천 1위 · ${purposeTextWithPostfix}`;
}
else if (hot === true && rank === 2) {
return `밍글링 추천 2위 · ${purposeTextWithPostfix}`;
}
else if (hot === true) {
return purposeTextWithPostfix;
}
else if (rank === 1) {
return '밍글링 추천 1위';
}
else if (rank === 2) {
return '밍글링 추천 2위';
}
else if (!category) return '밍글링 추천 1위';
// 카테고리 종성에 따라 "이/가"를 다르게 렌더링
const lastChar = category.charCodeAt(category.length - 1);
const hasJongseong = (lastChar - 0xac00) % 28 !== 0;
return `${category}${hasJongseong ? '이' : '가'} 많은 장소`;
const categoryLastChar = category.charCodeAt(category.length - 1);
const categoryHasJongseong = (categoryLastChar - 0xac00) % 28 !== 0;
return `${category}${categoryHasJongseong ? '이' : '가'} 많은 장소`;
};
const getCategoryText = (
category: string | undefined,
hot: boolean | undefined,
rank: number,
): string => {
const getPurposeTextWithPostfix = (text: string) => {
const lastChar = text.charCodeAt(text.length - 1);
const hasJongseong = (lastChar - 0xac00) % 28 !== 0;
return `${text}${hasJongseong ? '이' : '가'} 많은 장소`;
};
const purposeTextWithPostfix = category
? getPurposeTextWithPostfix(category)
: '인기 많은 장소';
if (hot === true && rank === 1) {
return `밍글링 추천 1위 · ${purposeTextWithPostfix}`;
}
else if (hot === true && rank === 2) {
return `밍글링 추천 2위 · ${purposeTextWithPostfix}`;
}
else if (hot === true) {
return purposeTextWithPostfix;
}
else if (rank === 1) {
return '밍글링 추천 1위';
}
else if (rank === 2) {
return '밍글링 추천 2위';
}
else if (!category) return '밍글링 추천 1위';
// 카테고리 종성에 따라 "이/가"를 다르게 렌더링
const categoryLastChar = category.charCodeAt(category.length - 1);
const categoryHasJongseong = (categoryLastChar - 0xac00) % 28 !== 0;
return `${category}${categoryHasJongseong ? '이' : '가'} 많은 장소`;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/result/`[id]/page.tsx around lines 116 - 156, The function
getCategoryText currently uses a hardcoded purposeText ('[모임 목적]') instead of
the actual category; update getCategoryText to use the category parameter (with
a safe fallback when category is undefined) for all text construction and for
the jongseong check, e.g., compute lastChar and hasJongseong from category (or
from a default like '장소' if category is missing), then use
`${category}${hasJongseong ? '이' : '가'} 많은 장소` and include that computed
purposeTextWithPostfix in the hot/rank branches; ensure you keep the existing
special-case returns (e.g., when rank 1/2 or hot) but replace any occurrence of
the placeholder with the actual category and add a null/empty guard so undefined
category still returns the intended default ('밍글링 추천 1위' or a sensible phrase).


const [selectedResultId, setSelectedResultId] = useState<number>(1);
Expand Down Expand Up @@ -151,7 +185,7 @@ export default function Page() {
<>
<section className="border-gray-1 flex w-full flex-col gap-5 bg-white md:w-77.5 md:gap-3">
<div className="px-5 pt-5 md:p-0">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<button
onClick={handleModifyStart}
className="flex items-center justify-center"
Expand All @@ -162,14 +196,7 @@ export default function Page() {
<div className="text-gray-9 text-[22px] font-semibold tracking-[-1.94%]">
최종 위치 결과 Top3
</div>
<button
className="text-blue-5 bg-blue-1 hover:bg-blue-2 flex h-7 cursor-pointer items-center gap-1 rounded px-2.5 text-[11px] font-semibold transition-colors"
type="button"
onClick={(e) => openModal('SHARE', { meetingId: id }, e)}
>
<Image src="/icon/share.svg" alt="공유 아이콘" width={12} height={12} />
결과 공유하기
</button>

</div>
</div>

Expand Down Expand Up @@ -207,7 +234,7 @@ export default function Page() {
locationResults.map((result) => {
const category =
meetingData?.data?.purposes?.[meetingData.data.purposes.length - 1];
const categoryText = getCategoryText(category);
const categoryText = getCategoryText(category, result.hot, result.id);

const handleRecommendClick = (e: React.MouseEvent) => {
e.stopPropagation();
Expand Down Expand Up @@ -309,12 +336,13 @@ export default function Page() {
)}
</div>
</div>

<button
onClick={handleModifyStart}
className="bg-blue-5 hover:bg-blue-8 absolute right-5 bottom-0 left-5 h-12 rounded text-lg font-semibold text-white transition-transform active:scale-[0.98] md:right-0 md:left-0"
onClick={(e) => openModal('SHARE', { meetingId: id }, e)}
className="flex items-center justify-center gap-2.5 bg-blue-5 hover:bg-blue-8 absolute right-5 bottom-0 left-5 h-12 rounded text-lg font-semibold text-white transition-transform active:scale-[0.98] md:right-0 md:left-0"
>
내 출발지 수정하기
<Image src="/icon/share-white.svg" alt="공유 아이콘" width={20} height={20} />
결과 공유하기
</button>
</div>
</section>
Expand Down
109 changes: 52 additions & 57 deletions components/modal/transferModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,43 +116,6 @@ export default function TransferModal({
}
};

// stations 배열에서 호선별 경로 추출 함수
const extractRouteSteps = (route: UserRoute) => {
const steps: Array<{ linenumber: string; station: string; isLast: boolean }> = [];

if (!route.stations || route.stations.length === 0) {
return steps;
}

let currentLine = route.stations[0]?.linenumber || '';

route.stations.forEach((station, index) => {
if (station.linenumber !== currentLine || index === route.stations.length - 1) {
if (currentLine) {
steps.push({
linenumber: currentLine,
station: station.station,
isLast: index === route.stations.length - 1,
});
}
currentLine = station.linenumber;
}
});

if (route.stations.length > 0) {
const lastStation = route.stations[route.stations.length - 1];
if (steps.length === 0 || steps[steps.length - 1].station !== lastStation.station) {
steps.push({
linenumber: lastStation.linenumber,
station: lastStation.station,
isLast: true,
});
}
}

return steps;
};

return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent
Expand All @@ -176,8 +139,6 @@ export default function TransferModal({
</div>
) : (
userRoutes.map((route, index) => {
const routeSteps = extractRouteSteps(route);

return (
<div
key={index}
Expand All @@ -200,34 +161,68 @@ export default function TransferModal({
{/* 하단: 환승 경로 (세로 배치) */}
<div className="relative flex gap-4 p-5">
<div className="flex flex-col items-center gap-[10px]">
{routeSteps.map((step, idx) => (
<div key={idx} className="flex flex-col items-center gap-[10px]">
<div
className={`flex min-w-[60px] items-center justify-center gap-1 rounded-[5px] px-[7px] py-[2px] text-[13px] leading-[1.385] font-normal tracking-[0.252px] text-white ${getLineBadgeStyle(
step.linenumber
)}`}
>
<Image src="/icon/train.svg" alt="train" width={12} height={12} />
<span>{step.linenumber}</span>
</div>

<div className="flex flex-col items-center gap-[10px]">
<div
className={`flex min-w-[60px] items-center justify-center gap-1 rounded-[5px] px-[7px] py-[2px] text-[13px] leading-[1.385] font-normal tracking-[0.252px] text-white ${getLineBadgeStyle(
route.startStationLine
)}`}
>
<Image src="/icon/train.svg" alt="train" width={12} height={12} />
<span>{route.startStationLine}</span>
</div>

{route.transferPath && route.transferPath.length > 0 && (
<div className="flex items-center justify-center">
<Image src="/icon/down.svg" alt="arrow-down" width={12} height={12} />
</div>
</div>
))}
)}
</div>

<div className="bg-gray-8 flex min-w-[60px] items-center justify-center rounded-[5px] px-[7px] py-[2px] text-[13px] leading-[1.385] font-normal tracking-[0.252px] text-white">
하차

{route.transferPath &&
route.transferPath.map((transfer, idx) => (
<div key={idx} className="flex flex-col items-center gap-[10px]">
<div
className={`flex min-w-[60px] items-center justify-center gap-1 rounded-[5px] px-[7px] py-[2px] text-[13px] leading-[1.385] font-normal tracking-[0.252px] text-white ${getLineBadgeStyle(
transfer.linenumber
)}`}
>
<Image src="/icon/train.svg" alt="train" width={12} height={12} />
<span>{transfer.linenumber}</span>
</div>

{idx < route.transferPath.length - 1 && (
<div className="flex items-center justify-center">
<Image src="/icon/down.svg" alt="arrow-down" width={12} height={12} />
</div>
)}
</div>
))}


<div className="flex flex-col items-center gap-[10px]">
<div className="flex items-center justify-center">
<Image src="/icon/down.svg" alt="arrow-down" width={12} height={12} />
</div>
<div className="bg-gray-8 flex min-w-[60px] items-center justify-center rounded-[5px] px-[7px] py-[2px] text-[13px] leading-[1.385] font-normal tracking-[0.252px] text-white">
하차
</div>
</div>
</div>

<div className="text-gray-8 flex flex-col gap-[30px] text-[13px] leading-[1.385] font-normal tracking-[0.252px]">
{routeSteps.map((step, idx) => (
<span key={idx}>{step.station}역</span>
))}

<span>{route.startStation}역</span>


{route.transferPath &&
route.transferPath.map((transfer, idx) => (
<span key={idx}>{transfer.station}역</span>
))}

<span>{endStation || routeSteps[routeSteps.length - 1]?.station}역</span>
{/* 종료역 */}
<span>{endStation}역</span>
</div>
</div>
</div>
Expand Down
3 changes: 3 additions & 0 deletions public/icon/share-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export type MeetingStatusResponse = ApiResponse<MeetingStatusData>;
export interface MidpointData {
endStation: string;
endStationLine: string;
hot: boolean;
latitude: number;
longitude: number;
userRoutes: {
Expand Down
Loading