From 8af0fbc463ea82423af0f1ee511dffac58681dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=80=EC=A7=80?= <81403265+eunji0714@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:22:24 +0900 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A1=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/_components/common/containers/InfoSectionContainer.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/_components/common/containers/InfoSectionContainer.tsx b/src/app/_components/common/containers/InfoSectionContainer.tsx index 484b6289..887093b1 100644 --- a/src/app/_components/common/containers/InfoSectionContainer.tsx +++ b/src/app/_components/common/containers/InfoSectionContainer.tsx @@ -13,7 +13,6 @@ const InfoSectionContainer = ({ title, row, }: InfoSectionContainerProps) => { - const widthValue = row ? 330 : '100%'; // width, height 재설정해야함 return (
{title &&

{title}

} @@ -21,7 +20,7 @@ const InfoSectionContainer = ({ {image && ( Image Date: Sat, 17 Aug 2024 08:24:30 +0900 Subject: [PATCH 2/5] =?UTF-8?q?refactor:=20=EB=8B=B9=EC=B2=A8=20=ED=99=95?= =?UTF-8?q?=EC=A0=95=EC=9E=90=20=EA=B5=AC=EB=B6=84=20=EC=A1=B0=EA=B1=B4=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/_components/user/workation/WkResultInfo.tsx | 5 ++--- src/app/_components/user/workation/WkSlider.tsx | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/_components/user/workation/WkResultInfo.tsx b/src/app/_components/user/workation/WkResultInfo.tsx index 5f0ba1eb..0d715b43 100644 --- a/src/app/_components/user/workation/WkResultInfo.tsx +++ b/src/app/_components/user/workation/WkResultInfo.tsx @@ -7,7 +7,6 @@ import InfoSectionModule from '@/_components/common/modules/InfoSectionModule'; const WkResultInfo = ({ id }: { id: number }) => { const { data, isLoading, isError } = useGetWkPenaltyQuery({ wktId: id }); - console.log(data); const gcd = (a: number, b: number): number => { if (b === 0) return a; return gcd(b, a % b); @@ -37,7 +36,7 @@ const WkResultInfo = ({ id }: { id: number }) => { winner.applyStatusType === 'CANCEL' || (

최초 @@ -54,7 +53,7 @@ const WkResultInfo = ({ id }: { id: number }) => { waiter.waitingNum < 0 || (

{index + 1} diff --git a/src/app/_components/user/workation/WkSlider.tsx b/src/app/_components/user/workation/WkSlider.tsx index 38576b8d..0d8087c1 100644 --- a/src/app/_components/user/workation/WkSlider.tsx +++ b/src/app/_components/user/workation/WkSlider.tsx @@ -5,6 +5,8 @@ import { useGetWkSimulationQuery } from '@/_hooks/user/useGetWkSimulationQuery'; import { useSession } from 'next-auth/react'; const Slider = ({ id }: { id: number }) => { + console.log('id', id); + const [animate, setAnimate] = useState(false); const [hoveredAccount, setHoveredAccount] = useState(null); const [tooltipPosition, setTooltipPosition] = useState({ left: '0%' }); @@ -22,7 +24,6 @@ const Slider = ({ id }: { id: number }) => { }, []); const { data, isLoading, isError } = useGetWkSimulationQuery({ wktId: id }); - if (isLoading) return null; if (isError) return null; if (!data) return null; From 5f9c2c87d382d45d29eef53947ba426e1a9b0870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=80=EC=A7=80?= <81403265+eunji0714@users.noreply.github.com> Date: Sat, 17 Aug 2024 09:29:03 +0900 Subject: [PATCH 3/5] =?UTF-8?q?refactor:=20=EB=8B=B9=EC=B2=A8=EC=9C=84?= =?UTF-8?q?=EC=B9=98=EC=97=90=20=EB=A9=88=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/user/workation/WkSlider.tsx | 169 ++++++++++-------- 1 file changed, 97 insertions(+), 72 deletions(-) diff --git a/src/app/_components/user/workation/WkSlider.tsx b/src/app/_components/user/workation/WkSlider.tsx index 0d8087c1..1359e7a6 100644 --- a/src/app/_components/user/workation/WkSlider.tsx +++ b/src/app/_components/user/workation/WkSlider.tsx @@ -1,15 +1,14 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import UserSubtitleAtom from '@/_components/user/common/atoms/UserSubtitleAtom'; import UserButtonAtom from '@/_components/user/common/atoms/UserButtonAtom'; import { useGetWkSimulationQuery } from '@/_hooks/user/useGetWkSimulationQuery'; import { useSession } from 'next-auth/react'; const Slider = ({ id }: { id: number }) => { - console.log('id', id); - const [animate, setAnimate] = useState(false); const [hoveredAccount, setHoveredAccount] = useState(null); const [tooltipPosition, setTooltipPosition] = useState({ left: '0%' }); + const sliderRef = useRef(null); const [hoveredIndex, setHoveredIndex] = useState(null); const { data: sessionData } = useSession(); const userId = String(sessionData?.accountId); @@ -63,28 +62,39 @@ const Slider = ({ id }: { id: number }) => { setHoveredIndex(null); }; - // 일치하는 accountId의 raffleIndex 값을 기반으로 멈추는 위치를 계산 + const handleMouseMove = (event) => { + const rect = sliderRef.current.getBoundingClientRect(); + const xPos = event.clientX - rect.left; // Calculate mouse position within the slider + const relativePosition = (xPos / rect.width) * totalRange; // Convert to relative position within the total range + + // Find the closest account based on mouse position + let closestAccount = null; + let minDiff = Infinity; + data.raffleMemberIndexInfos.forEach((member) => { + const diff = Math.abs(member.raffleIndex - relativePosition); + if (diff < minDiff) { + minDiff = diff; + closestAccount = member.accountId; + } + }); + setHoveredAccount(closestAccount); + setTooltipPosition({ left: `${(xPos / rect.width) * 100}%` }); + }; + + // pausePositions 계산 로직 변경 const pausePositions = data.rafflePickedIndexInfos .map((winner) => { - const matchingMemberIndex = data.raffleMemberIndexInfos.findIndex( - (member) => member.accountId === winner.accountId, - ); - if (matchingMemberIndex !== -1) { - const currentPos = calculateLeftPosition(matchingMemberIndex); - const nextPos = - matchingMemberIndex < data.raffleMemberIndexInfos.length - 1 - ? calculateLeftPosition(matchingMemberIndex + 1) - : 100; - // 당첨자 위치와 그 다음 위치 사이의 중간점 - return (currentPos + nextPos) / 2; - } - return null; + return { + position: (winner.pickedIndex / totalRange) * 100, + pickedIndex: winner.pickedIndex, + }; }) - .filter((position) => position !== null); + .sort((a, b) => a.pickedIndex - b.pickedIndex) // pickedIndex를 기준으로 오름차순 정렬 + .map((winner) => winner.position); // 최종적으로 포지션만 추출 // 동적으로 키프레임 생성 const generateKeyframes = () => { - const keyframes: { [key: string]: { left: string } } = { + const keyframes = { '0%': { left: '0%' }, }; @@ -93,16 +103,16 @@ const Slider = ({ id }: { id: number }) => { const holdTime = 2; // 각 위치에서 멈출 시간 (초 단위) const runningTime = totalDuration - holdTime * pausePositions.length; // 멈추지 않는 동안의 총 시간 - pausePositions?.forEach((position, i) => { - const positionKey = `${(((position! * runningTime) / 100 + totalPauseTime) / totalDuration) * 100}%`; + pausePositions.forEach((position, i) => { + const positionKey = `${(((position * runningTime) / 100 + totalPauseTime) / totalDuration) * 100}%`; keyframes[positionKey] = { left: `${position}%` }; totalPauseTime += holdTime; - const holdKey = `${(((position! * runningTime) / 100 + totalPauseTime) / totalDuration) * 100}%`; + const holdKey = `${(((position * runningTime) / 100 + totalPauseTime) / totalDuration) * 100}%`; keyframes[holdKey] = { left: `${position}%` }; }); - keyframes['100%'] = { left: 'calc(100%)' }; + keyframes['100%'] = { left: '100%' }; return keyframes; }; @@ -124,9 +134,14 @@ const Slider = ({ id }: { id: number }) => { onClick={restartAnimation} />

-
- -
- -
- {data.rafflePickedIndexInfos.map((winner) => ( -
{winner.pickedIndex}
- ))} - {data?.raffleMemberIndexInfos.map((member, index) => ( +
handleMouseEnter(member.accountId, index)} - onMouseLeave={handleMouseLeave} - > - {member.accountId === userId && ( -
- {userId} -
- )} - {hoveredIndex === index && ( -
- )} -
- ))} - - {hoveredAccount && ( + className={`absolute top-0 h-full rounded-full bg-primary ${animate ? 'animate-fillTrack' : ''}`} + /> +
- {hoveredAccount} -
- )} + /> + {data?.raffleMemberIndexInfos.map((member, index) => ( +
handleMouseEnter(member.accountId, index)} + onMouseLeave={handleMouseLeave} + > + {member.accountId === userId && ( +
+ {userId} +
+ )} + {hoveredIndex === index && ( +
+ )} +
+ ))} + {data?.rafflePickedIndexInfos.map((winner, index) => ( +
+ 당첨 +
+ ))} + {hoveredAccount && ( +
+ {hoveredAccount} +
+ )} +
)} From 518b56f9c7741988e2c004963ff50f67d9145b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=80=EC=A7=80?= <81403265+eunji0714@users.noreply.github.com> Date: Sat, 17 Aug 2024 09:46:38 +0900 Subject: [PATCH 4/5] =?UTF-8?q?refactor:=20=EC=8A=AC=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=8D=94=20hover=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/user/workation/WkSlider.tsx | 55 ++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/src/app/_components/user/workation/WkSlider.tsx b/src/app/_components/user/workation/WkSlider.tsx index 1359e7a6..0bea6ec6 100644 --- a/src/app/_components/user/workation/WkSlider.tsx +++ b/src/app/_components/user/workation/WkSlider.tsx @@ -10,12 +10,16 @@ const Slider = ({ id }: { id: number }) => { const [tooltipPosition, setTooltipPosition] = useState({ left: '0%' }); const sliderRef = useRef(null); const [hoveredIndex, setHoveredIndex] = useState(null); + const [visibleWinner, setVisibleWinner] = useState(null); const { data: sessionData } = useSession(); const userId = String(sessionData?.accountId); const restartAnimation = () => { setAnimate(false); - setTimeout(() => setAnimate(true), 10); + setTimeout(() => { + setAnimate(true); + setVisibleWinner(null); // Ensure winner is reset on animation restart + }, 10); }; useEffect(() => { @@ -23,9 +27,7 @@ const Slider = ({ id }: { id: number }) => { }, []); const { data, isLoading, isError } = useGetWkSimulationQuery({ wktId: id }); - if (isLoading) return null; - if (isError) return null; - if (!data) return null; + if (isLoading || isError || !data) return; const totalRange = data?.raffleMemberIndexInfos.reduce((max, member) => { return Math.max(max, member.raffleIndex); @@ -52,7 +54,7 @@ const Slider = ({ id }: { id: number }) => { const handleMouseEnter = (accountId: string, index: number) => { setHoveredAccount(accountId); setTooltipPosition({ - left: `${calculateLeftPosition(index) + getSliderWidth(index) / 3}%`, + left: `${calculateLeftPosition(index) + getSliderWidth(index)}%`, }); setHoveredIndex(index); }; @@ -65,20 +67,34 @@ const Slider = ({ id }: { id: number }) => { const handleMouseMove = (event) => { const rect = sliderRef.current.getBoundingClientRect(); const xPos = event.clientX - rect.left; // Calculate mouse position within the slider - const relativePosition = (xPos / rect.width) * totalRange; // Convert to relative position within the total range + const relativePosition = (xPos / rect.width) * 100; // Convert to relative percentage position - // Find the closest account based on mouse position + // Find the closest account based on mouse position percentage let closestAccount = null; let minDiff = Infinity; - data.raffleMemberIndexInfos.forEach((member) => { - const diff = Math.abs(member.raffleIndex - relativePosition); - if (diff < minDiff) { - minDiff = diff; - closestAccount = member.accountId; + + data.raffleMemberIndexInfos.forEach((member, index) => { + // Calculate the start and end percentage positions for this index + const startPercent = index === 0 ? 0 : calculateLeftPosition(index); + const endPercent = startPercent + getSliderWidth(index); + + // Check if the current mouse position falls within this range + if (relativePosition >= startPercent && relativePosition < endPercent) { + const diff = Math.min( + Math.abs(startPercent - relativePosition), + Math.abs(endPercent - relativePosition), + ); + if (diff < minDiff) { + minDiff = diff; + closestAccount = member.accountId; + } } }); - setHoveredAccount(closestAccount); - setTooltipPosition({ left: `${(xPos / rect.width) * 100}%` }); + + if (closestAccount) { + setHoveredAccount(closestAccount); + setTooltipPosition({ left: `${(xPos / rect.width) * 100}%` }); + } }; // pausePositions 계산 로직 변경 @@ -86,6 +102,7 @@ const Slider = ({ id }: { id: number }) => { .map((winner) => { return { position: (winner.pickedIndex / totalRange) * 100, + accountId: winner.accountId, pickedIndex: winner.pickedIndex, }; }) @@ -155,11 +172,11 @@ const Slider = ({ id }: { id: number }) => { `}
{ width: `${getSliderWidth(index)}%`, zIndex: 10, backgroundColor: - member.accountId === userId ? 'orange' : 'transparent', + member.accountId === userId ? '#FDE000' : 'transparent', }} onMouseEnter={() => handleMouseEnter(member.accountId, index)} onMouseLeave={handleMouseLeave} > {member.accountId === userId && ( -
+
{userId}
)} @@ -209,7 +226,7 @@ const Slider = ({ id }: { id: number }) => { left: tooltipPosition.left, }} > - {hoveredAccount} + ID {hoveredAccount}
)}
From 078529e5c540f9924fd26962aef2610514c00178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=80=EC=A7=80?= <81403265+eunji0714@users.noreply.github.com> Date: Sat, 17 Aug 2024 11:28:54 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refactor:=20=EB=8B=B9=EC=B2=A8=EC=9E=90=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/workation/WkResultInfo.tsx | 2 +- .../_components/user/workation/WkSlider.tsx | 231 +++++++++++------- 2 files changed, 140 insertions(+), 93 deletions(-) diff --git a/src/app/_components/user/workation/WkResultInfo.tsx b/src/app/_components/user/workation/WkResultInfo.tsx index 0d715b43..e8785dfd 100644 --- a/src/app/_components/user/workation/WkResultInfo.tsx +++ b/src/app/_components/user/workation/WkResultInfo.tsx @@ -28,7 +28,7 @@ const WkResultInfo = ({ id }: { id: number }) => { return (
-
+
{data?.wktWinningUserInfos.map( diff --git a/src/app/_components/user/workation/WkSlider.tsx b/src/app/_components/user/workation/WkSlider.tsx index 0bea6ec6..f16b651f 100644 --- a/src/app/_components/user/workation/WkSlider.tsx +++ b/src/app/_components/user/workation/WkSlider.tsx @@ -8,32 +8,21 @@ const Slider = ({ id }: { id: number }) => { const [animate, setAnimate] = useState(false); const [hoveredAccount, setHoveredAccount] = useState(null); const [tooltipPosition, setTooltipPosition] = useState({ left: '0%' }); - const sliderRef = useRef(null); + const [visibleWinners, setVisibleWinners] = useState([]); + const sliderRef = useRef(null); const [hoveredIndex, setHoveredIndex] = useState(null); - const [visibleWinner, setVisibleWinner] = useState(null); const { data: sessionData } = useSession(); const userId = String(sessionData?.accountId); - const restartAnimation = () => { - setAnimate(false); - setTimeout(() => { - setAnimate(true); - setVisibleWinner(null); // Ensure winner is reset on animation restart - }, 10); - }; - - useEffect(() => { - setAnimate(true); - }, []); - const { data, isLoading, isError } = useGetWkSimulationQuery({ wktId: id }); - if (isLoading || isError || !data) return; - const totalRange = data?.raffleMemberIndexInfos.reduce((max, member) => { - return Math.max(max, member.raffleIndex); - }, 0); + const totalRange = + data?.raffleMemberIndexInfos?.reduce((max, member) => { + return Math.max(max, member.raffleIndex); + }, 0) ?? 0; const getSliderWidth = (index: number) => { + if (!data) return 0; if (index === 0) { return (data.raffleMemberIndexInfos[0].raffleIndex / totalRange) * 100; } @@ -44,13 +33,86 @@ const Slider = ({ id }: { id: number }) => { const calculateLeftPosition = (index: number) => { let left = 0; - /* eslint-disable no-plusplus */ + // eslint-disable-next-line no-plusplus for (let i = 0; i < index; i++) { left += getSliderWidth(i); } return left; }; + const sortedRafflePickedIndexInfos = [ + ...(data?.rafflePickedIndexInfos || []), + ].sort((a, b) => a.pickedIndex - b.pickedIndex); + + const pausePositions = sortedRafflePickedIndexInfos.map((winner) => { + return { + position: (winner.pickedIndex / totalRange) * 100, + }; + }); + + const totalDuration = 30; + const holdTime = 2; + const runningTime = totalDuration - holdTime * pausePositions.length; + + const generateKeyframes = () => { + const keyframes: { [key: string]: { left: string } } = { + '0%': { left: '0%' }, + }; + + let totalPauseTime = 0; + + pausePositions.forEach((positionObj) => { + const positionKey = `${(((positionObj.position * runningTime) / 100 + totalPauseTime) / totalDuration) * 100}%`; + keyframes[positionKey] = { left: `${positionObj.position}%` }; + + totalPauseTime += holdTime; + const holdKey = `${(((positionObj.position * runningTime) / 100 + totalPauseTime) / totalDuration) * 100}%`; + keyframes[holdKey] = { left: `${positionObj.position}%` }; + }); + + keyframes['100%'] = { left: '100%' }; + return keyframes; + }; + + const triggerWinnerVisibility = () => { + let totalPauseTime = 0; + + pausePositions.forEach((_, index) => { + const delay = + (pausePositions[index].position * runningTime) / 100 + + totalPauseTime * 1000; + + setTimeout( + () => { + setVisibleWinners((prev) => [...prev, index]); + }, + delay + holdTime * 1000, + ); + + totalPauseTime += holdTime; + }); + }; + + const restartAnimation = () => { + setAnimate(false); + setVisibleWinners([]); + setTimeout(() => { + setAnimate(true); + triggerWinnerVisibility(); + }, 10); + }; + + useEffect(() => { + if (data && !isLoading && !isError) { + setAnimate(false); + setVisibleWinners([]); + setTimeout(() => { + setAnimate(true); + triggerWinnerVisibility(); + }, 10); + } + }, [data, isLoading, isError]); + const handleMouseEnter = (accountId: string, index: number) => { setHoveredAccount(accountId); setTooltipPosition({ @@ -64,21 +126,20 @@ const Slider = ({ id }: { id: number }) => { setHoveredIndex(null); }; - const handleMouseMove = (event) => { + const handleMouseMove = (event: React.MouseEvent) => { + if (!data || !sliderRef.current) return; + const rect = sliderRef.current.getBoundingClientRect(); - const xPos = event.clientX - rect.left; // Calculate mouse position within the slider - const relativePosition = (xPos / rect.width) * 100; // Convert to relative percentage position + const xPos = event.clientX - rect.left; + const relativePosition = (xPos / rect.width) * 100; - // Find the closest account based on mouse position percentage - let closestAccount = null; + let closestAccount: string | null = null; let minDiff = Infinity; data.raffleMemberIndexInfos.forEach((member, index) => { - // Calculate the start and end percentage positions for this index const startPercent = index === 0 ? 0 : calculateLeftPosition(index); const endPercent = startPercent + getSliderWidth(index); - // Check if the current mouse position falls within this range if (relativePosition >= startPercent && relativePosition < endPercent) { const diff = Math.min( Math.abs(startPercent - relativePosition), @@ -97,45 +158,10 @@ const Slider = ({ id }: { id: number }) => { } }; - // pausePositions 계산 로직 변경 - const pausePositions = data.rafflePickedIndexInfos - .map((winner) => { - return { - position: (winner.pickedIndex / totalRange) * 100, - accountId: winner.accountId, - pickedIndex: winner.pickedIndex, - }; - }) - .sort((a, b) => a.pickedIndex - b.pickedIndex) // pickedIndex를 기준으로 오름차순 정렬 - .map((winner) => winner.position); // 최종적으로 포지션만 추출 - - // 동적으로 키프레임 생성 - const generateKeyframes = () => { - const keyframes = { - '0%': { left: '0%' }, - }; - - let totalPauseTime = 0; - const totalDuration = 15; // 전체 애니메이션 시간 (초) - const holdTime = 2; // 각 위치에서 멈출 시간 (초 단위) - const runningTime = totalDuration - holdTime * pausePositions.length; // 멈추지 않는 동안의 총 시간 - - pausePositions.forEach((position, i) => { - const positionKey = `${(((position * runningTime) / 100 + totalPauseTime) / totalDuration) * 100}%`; - keyframes[positionKey] = { left: `${position}%` }; - - totalPauseTime += holdTime; - const holdKey = `${(((position * runningTime) / 100 + totalPauseTime) / totalDuration) * 100}%`; - keyframes[holdKey] = { left: `${position}%` }; - }); - - keyframes['100%'] = { left: '100%' }; - return keyframes; - }; - - // 동적으로 생성된 키프레임을 스타일 태그에 주입 const customKeyframes = generateKeyframes(); + if (isLoading || isError || !data) return null; + return (
{data.rafflePickedIndexInfos[0] && ( @@ -152,7 +178,7 @@ const Slider = ({ id }: { id: number }) => { />
@@ -172,41 +198,58 @@ const Slider = ({ id }: { id: number }) => { `}
- {data?.raffleMemberIndexInfos.map((member, index) => ( -
handleMouseEnter(member.accountId, index)} - onMouseLeave={handleMouseLeave} - > - {member.accountId === userId && ( -
- {userId} -
- )} - {hoveredIndex === index && ( -
- )} -
- ))} - {data?.rafflePickedIndexInfos.map((winner, index) => ( + {data?.raffleMemberIndexInfos.map((member, index) => { + const isWinner = visibleWinners.includes( + sortedRafflePickedIndexInfos.findIndex( + (winner) => winner.accountId === member.accountId, + ), + ); + + return ( +
+ handleMouseEnter(member.accountId, index) + } + onMouseLeave={handleMouseLeave} + > + {member.accountId === userId && ( +
+ {userId} +
+ )} + {hoveredIndex === index && ( +
+ )} +
+ ); + })} + {sortedRafflePickedIndexInfos.map((winner, index) => (
{ left: `${(winner.pickedIndex / totalRange) * 100}%`, top: '-40px', transform: 'translateX(-50%)', + display: visibleWinners.includes(index) ? 'block' : 'none', }} > 당첨 +
+
+
))} {hoveredAccount && ( @@ -226,7 +273,7 @@ const Slider = ({ id }: { id: number }) => { left: tooltipPosition.left, }} > - ID {hoveredAccount} + {hoveredAccount}
)}