-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 투표 현황 페이지 UI 구축 #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e1904da
bac260a
31bf579
b8c90f6
d740792
19fe87f
b7d9d58
be9ba3a
9ce2528
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,135 @@ | ||||||||||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import { useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||
| import Image from 'next/image'; | ||||||||||||||||||||||||||||||||||||||
| import KakaoMap from '@/components/map/kakaoMap'; | ||||||||||||||||||||||||||||||||||||||
| import StationSearch from '@/components/meeting/stationSearch'; | ||||||||||||||||||||||||||||||||||||||
| import { useOpenModal } from '@/hooks/useOpenModal'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // 목 데이터 (참여자 목록) | ||||||||||||||||||||||||||||||||||||||
| const MOCK_PARTICIPANTS = [ | ||||||||||||||||||||||||||||||||||||||
| { id: 1, name: '안', station: '홍대입구역', status: 'pending', color: 'bg-blue-500' }, | ||||||||||||||||||||||||||||||||||||||
| { id: 2, name: '손', station: '성수역', status: 'pending', color: 'bg-orange-400' }, | ||||||||||||||||||||||||||||||||||||||
| { id: 3, name: '김', station: '강남역', status: 'done', color: 'bg-red-500' }, | ||||||||||||||||||||||||||||||||||||||
| { id: 4, name: '이', station: '건대입구역', status: 'done', color: 'bg-purple-600' }, | ||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // [NEW] 목 데이터 (검색용 역 리스트) - 실제로는 API 등에서 가져올 데이터 | ||||||||||||||||||||||||||||||||||||||
| const SEARCH_STATIONS = [ | ||||||||||||||||||||||||||||||||||||||
| '검단오류(검단산업단지)', | ||||||||||||||||||||||||||||||||||||||
| '광교(경기대)', | ||||||||||||||||||||||||||||||||||||||
| '구의(광진구청)', | ||||||||||||||||||||||||||||||||||||||
| '굽은다리(강동구민회관앞)', | ||||||||||||||||||||||||||||||||||||||
| '금정역', | ||||||||||||||||||||||||||||||||||||||
| '강남역', | ||||||||||||||||||||||||||||||||||||||
| '홍대입구역', | ||||||||||||||||||||||||||||||||||||||
| '서울역', | ||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export default function MeetingPage() { | ||||||||||||||||||||||||||||||||||||||
| // 선택된 역 상태 관리 | ||||||||||||||||||||||||||||||||||||||
| const [selectedStation, setSelectedStation] = useState<string | null>(null); | ||||||||||||||||||||||||||||||||||||||
| const openModal = useOpenModal(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||
| // 전체 화면 배경 및 중앙 정렬 | ||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center justify-center p-0 md:min-h-[calc(100vh-200px)] md:py-25"> | ||||||||||||||||||||||||||||||||||||||
| {/* 메인 컨테이너 (반응형 박스) */} | ||||||||||||||||||||||||||||||||||||||
| <div className="flex h-full w-full flex-col overflow-hidden bg-white md:h-175 md:w-174 md:flex-row md:gap-4 md:rounded-xl lg:w-215"> | ||||||||||||||||||||||||||||||||||||||
| {/* [LEFT PANEL] 데스크탑 전용 정보 영역 */} | ||||||||||||||||||||||||||||||||||||||
| <section className="border-gray-1 flex w-full flex-col gap-5 bg-white md:w-77.5 md:gap-10"> | ||||||||||||||||||||||||||||||||||||||
| {/* 타이머 섹션 */} | ||||||||||||||||||||||||||||||||||||||
| <div className="px-5 pt-10 md:p-0"> | ||||||||||||||||||||||||||||||||||||||
| <div className="flex items-start justify-between"> | ||||||||||||||||||||||||||||||||||||||
| <div className="text-[22px] leading-[1.364] font-semibold tracking-[-1.948%]"> | ||||||||||||||||||||||||||||||||||||||
| <h2 className="text-gray-9"> | ||||||||||||||||||||||||||||||||||||||
| 투표 마감 시간 | ||||||||||||||||||||||||||||||||||||||
| <br /> | ||||||||||||||||||||||||||||||||||||||
| <span className="text-blue-5">03: 45</span> 남았습니다 | ||||||||||||||||||||||||||||||||||||||
| </h2> | ||||||||||||||||||||||||||||||||||||||
| <p className="text-gray-5 mt-2 text-[15px] font-normal"> | ||||||||||||||||||||||||||||||||||||||
| 아직 입력 안 한 모임원 2명 | ||||||||||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||
| className="text-blue-5 bg-blue-1 hover:bg-blue-2 flex h-6 w-30 cursor-pointer items-center gap-0.5 rounded px-3 py-1.5 text-[11px] font-semibold transition-colors" | ||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||
| onClick={(e) => openModal('SHARE', e)} | ||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||
| <Image src="/icon/share.svg" alt="공유 아이콘" width={12} height={12} /> | ||||||||||||||||||||||||||||||||||||||
| 참여 링크 공유하기 | ||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| {/* 모바일 전용 지도 영역 */} | ||||||||||||||||||||||||||||||||||||||
| <KakaoMap className="relative block aspect-video h-93.5 bg-gray-100 md:hidden" /> | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| {/* 출발지 검색 창 컴포넌트 */} | ||||||||||||||||||||||||||||||||||||||
| <StationSearch | ||||||||||||||||||||||||||||||||||||||
| stations={SEARCH_STATIONS} | ||||||||||||||||||||||||||||||||||||||
| selectedStation={selectedStation} | ||||||||||||||||||||||||||||||||||||||
| onSelect={setSelectedStation} | ||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| <div className="bg-gray-1 relative h-1 w-full md:hidden"></div> | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| {/* 참여 현황 */} | ||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-1 flex-col gap-3 overflow-hidden px-5 md:gap-3.5 md:p-0"> | ||||||||||||||||||||||||||||||||||||||
| {/* [1] 상단 고정 영역 */} | ||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center justify-between bg-white"> | ||||||||||||||||||||||||||||||||||||||
| <h3 className="text-gray-9 text-xl font-semibold">참여현황</h3> | ||||||||||||||||||||||||||||||||||||||
| <span className="text-gray-6 text-normal text-xs"> | ||||||||||||||||||||||||||||||||||||||
| <span className="text-blue-5">{MOCK_PARTICIPANTS.length}명</span>이 참여 중 | ||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| {/* [2] 재촉하기 배너 */} | ||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||
| className="bg-blue-5 hover:bg-blue-8 flex h-21 w-full cursor-pointer items-center justify-between rounded p-4 text-left text-white transition-transform active:scale-[0.98]" | ||||||||||||||||||||||||||||||||||||||
| onClick={(e) => openModal('NUDGE', e)} | ||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-col gap-0.5"> | ||||||||||||||||||||||||||||||||||||||
| <span className="text-lg leading-[1.44] font-semibold"> | ||||||||||||||||||||||||||||||||||||||
| 아직 입력하지 않은 친구 | ||||||||||||||||||||||||||||||||||||||
| <br /> | ||||||||||||||||||||||||||||||||||||||
| 재촉하기 | ||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| <div className="bg-gray-3 h-13 w-14"></div> | ||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| {/* [3] 출발지 컴포넌트 */} | ||||||||||||||||||||||||||||||||||||||
| <div className="mb-10 flex-1"> | ||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-col gap-3.5"> | ||||||||||||||||||||||||||||||||||||||
| {MOCK_PARTICIPANTS.map((user) => ( | ||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||
| key={user.id} | ||||||||||||||||||||||||||||||||||||||
| className="border-gray-2 flex h-17 items-center justify-between rounded border bg-white px-5" | ||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||
| <span className="text-gray-8 text-[17px] font-semibold">{user.station}</span> | ||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center gap-1.5"> | ||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||
| className={`flex h-7 w-7 items-center justify-center rounded-full text-xs font-normal text-white ${user.color}`} | ||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||
| {user.name} | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
| <span className="text-gray-8 text-[15px]">안가연</span> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+112
to
+120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 참여자 이름이 고정 문자열로 렌더링됩니다 🔧 제안 수정- <span className="text-gray-8 text-[15px]">안가연</span>
+ <span className="text-gray-8 text-[15px]">{user.name}</span>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
| </section> | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| {/* [RIGHT PANEL] 데스크탑 전용 지도 영역 */} | ||||||||||||||||||||||||||||||||||||||
| <section className="hidden h-full flex-1 bg-gray-100 md:block"> | ||||||||||||||||||||||||||||||||||||||
| <KakaoMap className="h-full w-full" /> | ||||||||||||||||||||||||||||||||||||||
| </section> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,37 @@ | ||||||||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import { useEffect, useRef } from 'react'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| interface KakaoMapProps { | ||||||||||||||||||||||||||||||||||||
| className?: string; // Tailwind 클래스를 받기 위해 | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| export default function KakaoMap({ className }: KakaoMapProps) { | ||||||||||||||||||||||||||||||||||||
| const mapContainer = useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||
| // 1. 카카오 스크립트가 로드되지 않았으면 중단 | ||||||||||||||||||||||||||||||||||||
| if (!window.kakao) return; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // 2. 카카오 맵 로드 (autoload=false라서 명시적으로 load 호출 필요) | ||||||||||||||||||||||||||||||||||||
| window.kakao.maps.load(() => { | ||||||||||||||||||||||||||||||||||||
| const container = mapContainer.current; | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -name "kakaoMap.tsx" -type f 2>/dev/null | head -20Repository: SWYP-mingling/Frontend Length of output: 95 🏁 Script executed: git ls-files | grep -i "kakao" | head -20Repository: SWYP-mingling/Frontend Length of output: 110 🏁 Script executed: cat -n components/map/kakaoMap.tsx | head -40Repository: SWYP-mingling/Frontend Length of output: 1338 🏁 Script executed: wc -l components/map/kakaoMap.tsxRepository: SWYP-mingling/Frontend Length of output: 96 🏁 Script executed: grep -r "window.kakao" . --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" -n | head -20Repository: SWYP-mingling/Frontend Length of output: 630 🏁 Script executed: grep -r "kakao" . --include="*.html" --include="*.tsx" --include="*.ts" -n | grep -i "script\|sdk\|load" | head -20Repository: SWYP-mingling/Frontend Length of output: 295 🏁 Script executed: grep -r "console.warn\|console.error\|useEffect" . --include="*.tsx" --include="*.ts" -A 2 | head -50Repository: SWYP-mingling/Frontend Length of output: 4237 🏁 Script executed: cat -n app/layout.tsx | grep -A 5 -B 5 "kakao"Repository: SWYP-mingling/Frontend Length of output: 494 카카오 맵 SDK 미로드 시 무음으로 종료되어 디버깅이 어렵습니다.
🛠️ 제안 수정안- if (!window.kakao) return;
+ if (!window.kakao) {
+ console.warn('Kakao Maps SDK is not loaded.');
+ return;
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| if (!container) return; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const options = { | ||||||||||||||||||||||||||||||||||||
| center: new window.kakao.maps.LatLng(37.5563, 126.9224), // 홍대입구역 좌표 | ||||||||||||||||||||||||||||||||||||
| level: 3, // 지도 확대 레벨 | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // 지도 생성 | ||||||||||||||||||||||||||||||||||||
| const map = new window.kakao.maps.Map(container, options); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // (선택) 마커 추가 예시 | ||||||||||||||||||||||||||||||||||||
| const markerPosition = new window.kakao.maps.LatLng(37.5563, 126.9224); | ||||||||||||||||||||||||||||||||||||
| const marker = new window.kakao.maps.Marker({ position: markerPosition }); | ||||||||||||||||||||||||||||||||||||
| marker.setMap(map); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| return <div ref={mapContainer} className={className} />; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 80
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 83
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 273
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 455
🏁 Script executed:
# Read the entire app/layout.tsx file cat -n app/layout.tsxRepository: SWYP-mingling/Frontend
Length of output: 2127
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 48
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 222
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 1227
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 48
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 48
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 48
🏁 Script executed:
# Read the KakaoMap component to understand error handling cat -n components/map/kakaoMap.tsxRepository: SWYP-mingling/Frontend
Length of output: 1338
🏁 Script executed:
# Check the types/kakao.d.ts file cat -n types/kakao.d.tsRepository: SWYP-mingling/Frontend
Length of output: 186
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 296
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 80
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 50
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 296
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 112
🏁 Script executed:
# Read next.config.ts cat -n next.config.tsRepository: SWYP-mingling/Frontend
Length of output: 247
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 1181
Kakao SDK 키 미설정 시 조용히 실패할 수 있습니다.
NEXT_PUBLIC_KAKAO_MAP_KEY가 비어 있으면appkey=undefined로 스크립트가 로드되어 유효하지 않은 키로 인해 SDK 로딩에 실패합니다. KakaoMap 컴포넌트의if (!window.kakao) return;체크에 의해 지도 영역이 완전히 비어지며, 사용자에게는 실패 원인을 알 수 없습니다. 키 존재 여부를 가드하거나 로드 실패 시 명확한 오류 메시지를 표시하는 것이 안전합니다.🛠️ 제안 수정안
export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { + const kakaoKey = process.env.NEXT_PUBLIC_KAKAO_MAP_KEY; + return ( <html lang="ko" className={pretendard.variable}> <body className="flex min-h-screen flex-col"> <Header /> <main className="flex-1">{children}</main> <Footer /> <GlobalModal /> - <Script - src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_MAP_KEY}&libraries=services,clusterer&autoload=false`} - strategy="beforeInteractive" - /> + {kakaoKey && ( + <Script + src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${kakaoKey}&libraries=services,clusterer&autoload=false`} + strategy="beforeInteractive" + /> + )} </body> </html> ); }🤖 Prompt for AI Agents