Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1d25408
refactor: ์ƒ์ˆ˜ & ์œ ํ‹ธ ์ •๋ฆฌ
Feb 12, 2026
74628cd
refactor: ์ปค์Šคํ…€ ํ›… ์ƒ์„ฑ
Feb 12, 2026
9975d9a
refactor: ์ปค์Šคํ…€ ํ›… ์ƒ์„ฑ
Feb 12, 2026
289c6fc
refactor: ์ •๋ ฌ๋กœ์ง
Feb 12, 2026
00e1b3b
refactor: ์กฐํ•ฉ๋ช…, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
Feb 12, 2026
06ee11d
refactor: ๊ธฐ๊ธฐ ์‚ญ์ œ ์ปดํฌ๋„ŒํŠธ
Feb 12, 2026
881df00
refactor: ์กฐํ•ฉ์‚ญ์ œ ์ปดํฌ๋„ŒํŠธ
Feb 12, 2026
d5bd2a3
refactor: ์กฐํ•ฉ๋ช… ์ €์žฅ ํ™•์ธ ์ปดํฌ๋„ŒํŠธ
Feb 12, 2026
c5c8e97
refactor: ์‚ญ์ œ ์™„๋ฃŒ ํŒ์—… ์ปดํฌ๋„ŒํŠธ
Feb 12, 2026
bd35390
refactor: mypage sidebar ์ปดํฌ๋„ŒํŠธ
Feb 12, 2026
277e855
refactor: ๋“œ๋ž๋‹ค์šด ๋ฉ”๋‰ด ์ปดํฌ๋„ŒํŠธ
Feb 12, 2026
921cc16
refactor: ๋นˆ ์กฐํ•ฉ ์ปดํฌ๋„ŒํŠธ
Feb 12, 2026
c01fba0
refactor: ์กฐํ•ฉ ์ปดํฌ๋„ŒํŠธ
Feb 12, 2026
906a5dc
refactor: ์ƒ์„ธ๋ณด๊ธฐ ์ปดํฌ๋„ŒํŠธ
Feb 12, 2026
b547819
refactor: mypage.tsx
Feb 12, 2026
ec6662d
refactor: useClickOutisde
Feb 12, 2026
52386a1
refactor: ๋ชจ๋‹ฌ ์Šคํฌ๋กค ๋ฐฉ์ง€ ํ›…
Feb 12, 2026
ea93cb5
refactor: ์Šคํฌ๋กค ๋ฐฉ์ง€ ํ›…
Feb 12, 2026
b99a586
refactor: MyPage.tsx
Feb 12, 2026
e153b60
refactor: ์ฝ˜์†” ์‚ญ์ œ
Feb 12, 2026
bc12fa1
remove: ๋งˆ์ดํŽ˜์ด์ง€, ๊ฒ€์ƒ‰์—์„œ ์•ˆ ์“ฐ์ด๋Š” ๋ฐ์ดํ„ฐ ์‚ญ์ œ
Feb 12, 2026
45de40e
refactor: useCallback์œผ๋กœ ํ•ธ๋“ค๋Ÿฌ ๋ฉ”๋ชจ์ด์ œ์ด์…˜
Feb 12, 2026
39099b4
refactor: ์ž์‹ ์ปดํฌ๋„ŒํŠธ React.memo ์ ์šฉ
Feb 12, 2026
9cc9694
refactor: any ํƒ€์ž… ์ œ๊ฑฐ
Feb 12, 2026
6a16f96
refactor: anyํƒ€์ž… ์ œ๊ฑฐ
Feb 12, 2026
676b6ae
refactor: Lazy Loading์œผ๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™”
Feb 12, 2026
d4c3983
fix: ๋นŒ๋“œ์—๋Ÿฌ ํ•ด๊ฒฐ
Feb 12, 2026
5b80d59
refactor: mockdata.ts ์‚ญ์ œ ๋ฐ ์ƒˆ๋กœ ์ •์˜
Feb 12, 2026
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
41 changes: 39 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
"@react-oauth/google": "^0.13.4",
"@tanstack/react-query": "^5.90.20",
"@tanstack/react-query-devtools": "^5.91.2",
"@types/react-window": "^1.8.8",
"axios": "^1.13.2",
"clsx": "^2.1.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-hook-form": "^7.71.1",
"react-router-dom": "^7.12.0",
"react-spinners": "^0.17.0",
"react-window": "^2.2.6",
"tailwind-merge": "^3.4.0",
"zod": "^4.3.5",
"zustand": "^5.0.10"
Expand Down
2 changes: 1 addition & 1 deletion src/components/DeviceSearch/DeviceDetailModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Product } from '@/constants/mockData';
import type { Product } from '@/types/product';
import type { SearchDevice } from '@/types/devices';
import PrimaryButton from '@/components/Button/PrimaryButton';
import XIcon from '@/assets/icons/X.svg?react';
Expand Down
171 changes: 171 additions & 0 deletions src/components/MyPage/CombinationCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { useState, memo } from 'react';
import StarIcon from '@/assets/icons/star.svg?react';
import StarXIcon from '@/assets/icons/starx.svg?react';
import StarHoverIcon from '@/assets/icons/starhover.svg?react';
import { formatDate } from '@/utils/format';
import type { ComboListItem } from '@/types/combo/combo';

interface CombinationCardProps {
combination: ComboListItem;
index: number;
columns: 3 | 4;
isEditing: boolean;
editingName: string;
nameError: string | null;
onTogglePin: (e: React.MouseEvent, comboId: number) => void;
onNameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onNameBlur: (name: string) => void;
}

const CombinationCard = ({
combination,
index,
columns,
isEditing,
editingName,
nameError,
onTogglePin,
onNameChange,
onNameBlur,
}: CombinationCardProps) => {
const [hoveredStarComboId, setHoveredStarComboId] = useState<number | null>(null);

// ๊ทธ๋ผ๋ฐ์ด์…˜ ๋กœ์ง
const gradientThreshold = columns === 4 ? 9 : 7;
const shouldShowGradient = combination.devices.length >= gradientThreshold;
const maxDisplay = columns === 4 ? 8 : 6;
const displayedDevices = shouldShowGradient
? combination.devices.slice(0, maxDisplay)
: combination.devices;

return (
<div className="px-36 pt-24 pb-36">
{/* ์กฐํ•ฉ ์ •๋ณด (์ƒ์„ฑ์ผ ํฌํ•จ) */}
<div className="flex flex-col gap-13 pl-20 py-24">
{isEditing ? (
/* ์ˆ˜์ • ๋ชจ๋“œ: ์ธํ’‹๋ฐ•์Šค + ๋ณ„ ์•„์ด์ฝ˜ + ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ */
<div className="flex flex-col gap-8">
<div className="flex items-center gap-8 min-h-48">
<input
type="text"
value={editingName}
onChange={onNameChange}
onClick={(e) => e.stopPropagation()}
onBlur={() => onNameBlur(editingName)}
maxLength={20}
className={`h-52 px-12 rounded-button font-body-1-sm text-gray-300 focus:outline-none ${
nameError ? 'border-2 border-warning' : 'border border-blue-600'
}`}
autoFocus
/>
{combination.isPinned ? (
<StarIcon
onClick={(e) => onTogglePin(e, combination.comboId)}
className={`!w-22 !h-22 -mt-2 cursor-pointer transition-opacity ${
hoveredStarComboId === combination.comboId ? 'opacity-80' : ''
}`}
onMouseEnter={() => setHoveredStarComboId(combination.comboId)}
onMouseLeave={() => setHoveredStarComboId(null)}
/>
) : (
<>
{hoveredStarComboId === combination.comboId ? (
<StarHoverIcon
onClick={(e) => onTogglePin(e, combination.comboId)}
className="!w-22 !h-22 -mt-2 cursor-pointer"
onMouseEnter={() => setHoveredStarComboId(combination.comboId)}
onMouseLeave={() => setHoveredStarComboId(null)}
/>
) : (
<StarXIcon
onClick={(e) => onTogglePin(e, combination.comboId)}
className="!w-22 !h-22 -mt-2 cursor-pointer"
onMouseEnter={() => setHoveredStarComboId(combination.comboId)}
onMouseLeave={() => setHoveredStarComboId(null)}
/>
)}
</>
)}
</div>
{nameError && <p className="pl-12 font-body-4-r text-warning">{nameError}</p>}
</div>
) : (
/* ์ผ๋ฐ˜ ๋ชจ๋“œ: ์กฐํ•ฉ ๋ฒˆํ˜ธ + ์ƒ์„ฑ์ผ + ์กฐํ•ฉ๋ช… */
<div className="flex flex-col gap-8">
<div className="flex items-center gap-16">
<p className="font-body-3-r text-gray-400">์กฐํ•ฉ{index + 1}</p>
<p className="font-body-3-r text-gray-400">
์ƒ์„ฑ์ผ: {formatDate(combination.createdAt)}
</p>
</div>
<div className="flex items-center gap-8">
<p className="font-body-1-sm text-black">{combination.comboName}</p>
{combination.isPinned ? (
<StarIcon
onClick={(e) => onTogglePin(e, combination.comboId)}
className={`!w-22 !h-22 -mt-3 cursor-pointer transition-opacity ${
hoveredStarComboId === combination.comboId ? 'opacity-80' : ''
}`}
onMouseEnter={() => setHoveredStarComboId(combination.comboId)}
onMouseLeave={() => setHoveredStarComboId(null)}
/>
) : (
<>
{hoveredStarComboId === combination.comboId ? (
<StarHoverIcon
onClick={(e) => onTogglePin(e, combination.comboId)}
className="!w-22 !h-22 -mt-3 cursor-pointer"
onMouseEnter={() => setHoveredStarComboId(combination.comboId)}
onMouseLeave={() => setHoveredStarComboId(null)}
/>
) : (
<StarXIcon
onClick={(e) => onTogglePin(e, combination.comboId)}
className="!w-22 !h-22 -mt-3 cursor-pointer"
onMouseEnter={() => setHoveredStarComboId(combination.comboId)}
onMouseLeave={() => setHoveredStarComboId(null)}
/>
)}
</>
)}
</div>
</div>
)}
</div>

{/* ๊ธฐ๊ธฐ ๊ทธ๋ฆฌ๋“œ - ์ผ๋ฐ˜ ๋ชจ๋“œ์—์„œ๋„ ๊ธฐ๊ธฐ ์นด๋“œ ํ‘œ์‹œ (๊ทธ๋ผ๋ฐ์ด์…˜ ํฌํ•จ) */}
<div className="pl-8 mt-24 relative">
<div
className={`grid ${columns === 4 ? 'grid-cols-4' : 'grid-cols-3'} gap-x-28 gap-y-12`}
>
{displayedDevices.map((device) => (
<div
key={device.deviceId}
className="bg-white rounded-card shadow-[0_0_4px_rgba(0,0,0,0.1)] p-12 w-244 flex items-center gap-12"
>
<div className="w-64 h-64 bg-gray-200 flex-shrink-0" />
<div className="flex flex-col gap-4 flex-1">
<p className="font-body-3-sm text-black truncate w-120">{device.name}</p>
<p className="font-body-4-r text-gray-300">{device.brandName}</p>
<p className="font-body-3-r text-gray-300">{device.deviceType}</p>
</div>
</div>
))}
</div>

{/* ๊ทธ๋ผ๋ฐ์ด์…˜ ์˜ค๋ฒ„๋ ˆ์ด */}
{shouldShowGradient && (
<div
className="absolute right-0 bottom-0 w-244 h-80 rounded-card pointer-events-none"
style={{
background:
'linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 70%)',
}}
/>
)}
</div>
</div>
);
};

export default memo(CombinationCard);
Loading