feat(travel): 모바일 반응형 — 풀다운 리프레시 + 풀스크린 라이트박스
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -200,6 +200,24 @@
|
|||||||
.tv-map {
|
.tv-map {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 지도 높이 축소 */
|
||||||
|
.tv-map {
|
||||||
|
height: 35vh !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 라이트박스 풀스크린 */
|
||||||
|
.lightbox {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox__inner {
|
||||||
|
max-width: 100vw;
|
||||||
|
max-height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Leaflet map tooltip override */
|
/* Leaflet map tooltip override */
|
||||||
@@ -1020,6 +1038,10 @@
|
|||||||
.photo-card--wide {
|
.photo-card--wide {
|
||||||
grid-column: span 2;
|
grid-column: span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.photo-mosaic {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|||||||
import { GeoJSON, MapContainer, TileLayer, useMap } from 'react-leaflet';
|
import { GeoJSON, MapContainer, TileLayer, useMap } from 'react-leaflet';
|
||||||
import 'leaflet/dist/leaflet.css';
|
import 'leaflet/dist/leaflet.css';
|
||||||
import './Travel.css';
|
import './Travel.css';
|
||||||
|
import { useIsMobile } from '../../hooks/useIsMobile';
|
||||||
|
import PullToRefresh from '../../components/PullToRefresh';
|
||||||
|
|
||||||
/* ─────────────────────────────────────────────
|
/* ─────────────────────────────────────────────
|
||||||
Constants
|
Constants
|
||||||
@@ -547,6 +549,8 @@ const Travel = () => {
|
|||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [hasNext, setHasNext] = useState(true);
|
const [hasNext, setHasNext] = useState(true);
|
||||||
|
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const touchStartXRef = useRef(null);
|
const touchStartXRef = useRef(null);
|
||||||
const pendingAdvanceRef = useRef(null);
|
const pendingAdvanceRef = useRef(null);
|
||||||
const toastTimerRef = useRef(null);
|
const toastTimerRef = useRef(null);
|
||||||
@@ -685,6 +689,34 @@ const Travel = () => {
|
|||||||
}
|
}
|
||||||
}, [hasNext, loading, loadingMore, page, photoSummary, selectedRegion]);
|
}, [hasNext, loading, loadingMore, page, photoSummary, selectedRegion]);
|
||||||
|
|
||||||
|
/* ── Reload photos (pull-to-refresh) ──── */
|
||||||
|
const reloadPhotos = useCallback(async () => {
|
||||||
|
if (!selectedRegion) return;
|
||||||
|
cacheRef.current.delete(selectedRegion.id);
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/travel/photos?region=${encodeURIComponent(selectedRegion.id)}&page=1&size=${PAGE_SIZE}`
|
||||||
|
);
|
||||||
|
if (!res.ok) throw new Error(`지역 사진 로딩 실패 (${res.status})`);
|
||||||
|
const json = await res.json();
|
||||||
|
const items = Array.isArray(json) ? json : json.items ?? [];
|
||||||
|
const meta = Array.isArray(json) ? {} : json ?? {};
|
||||||
|
const normalized = normalizePhotos(items);
|
||||||
|
const nextHasNext = typeof meta.has_next === 'boolean'
|
||||||
|
? meta.has_next
|
||||||
|
: typeof meta.hasNext === 'boolean' ? meta.hasNext : normalized.length >= PAGE_SIZE;
|
||||||
|
const summary = hasSummaryInfo(meta)
|
||||||
|
? { total: meta.total, albums: meta.matched_albums ?? [] }
|
||||||
|
: null;
|
||||||
|
setPhotos(normalized);
|
||||||
|
setPage(2);
|
||||||
|
setHasNext(nextHasNext);
|
||||||
|
cacheRef.current.set(selectedRegion.id, {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
items: normalized, page: 2, hasNext: nextHasNext, summary,
|
||||||
|
});
|
||||||
|
if (summary) setPhotoSummary(summary);
|
||||||
|
}, [selectedRegion]);
|
||||||
|
|
||||||
/* ── Slide helpers ─────────────────────── */
|
/* ── Slide helpers ─────────────────────── */
|
||||||
const bumpSlide = (dir) => {
|
const bumpSlide = (dir) => {
|
||||||
setSlideDirection(dir);
|
setSlideDirection(dir);
|
||||||
@@ -921,7 +953,7 @@ const Travel = () => {
|
|||||||
|
|
||||||
{/* ── Photo Mosaic ─────────────────── */}
|
{/* ── Photo Mosaic ─────────────────── */}
|
||||||
{!loading && !error && selectedRegion && photos.length > 0 && (
|
{!loading && !error && selectedRegion && photos.length > 0 && (
|
||||||
<>
|
<PullToRefresh onRefresh={reloadPhotos}>
|
||||||
<div className="tv-album-header">
|
<div className="tv-album-header">
|
||||||
<div className="tv-album-header__left">
|
<div className="tv-album-header__left">
|
||||||
<span
|
<span
|
||||||
@@ -951,7 +983,7 @@ const Travel = () => {
|
|||||||
hasNext={hasNext}
|
hasNext={hasNext}
|
||||||
isLoadingMore={loadingMore}
|
isLoadingMore={loadingMore}
|
||||||
/>
|
/>
|
||||||
</>
|
</PullToRefresh>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user