o2o-castad-frontend/src/pages/Dashboard/ADO2ContentsPage.tsx

244 lines
9.0 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { getAllVideos, isLoggedIn } from '../../utils/api';
import { VideoListItem } from '../../types/api';
import LoginPromptModal from '../../components/LoginPromptModal';
import VideoDetailModal from '../../components/VideoDetailModal';
import CitySelectModal from '../../components/CitySelectModal';
interface ADO2ContentsPageProps {
onBack?: () => void;
}
const ADO2ContentsPage: React.FC<ADO2ContentsPageProps> = () => {
const { t } = useTranslation();
const authed = isLoggedIn();
const [selectedVideoId, setSelectedVideoId] = useState<number | null>(null);
const [videos, setVideos] = useState<VideoListItem[]>([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(authed);
const [error, setError] = useState<string | null>(null);
const [page, setPage] = useState(1);
const [hasNext, setHasNext] = useState(false);
const [hasPrev, setHasPrev] = useState(false);
const [totalPages, setTotalPages] = useState(1);
const pageSize = 20;
const [sortBy, setSortBy] = useState<'created_at' | 'like_count' | 'comment_count'>('created_at');
const [order, setOrder] = useState<'desc' | 'asc'>('desc');
const [searchInput, setSearchInput] = useState('');
const [storeName, setStoreName] = useState('');
const [region, setRegion] = useState('');
const [showCityModal, setShowCityModal] = useState(false);
useEffect(() => {
if (!authed) return;
fetchVideos();
}, [page, sortBy, order, storeName, region]);
const fetchVideos = async () => {
setLoading(true);
setError(null);
try {
const response = await getAllVideos(page, pageSize, sortBy, storeName, order, region);
setVideos(response.items);
setTotal(response.total);
setTotalPages(response.total_pages);
setHasNext(response.has_next);
setHasPrev(response.has_prev);
} catch (err) {
console.error('Failed to fetch all videos:', err);
setError(t('ado2Contents.loadFailed'));
} finally {
setLoading(false);
}
};
const handleCardClick = (videoId: number) => {
setSelectedVideoId(videoId);
};
const formatDate = (dateString: string) => {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}.${month}.${day}`;
};
return (
<div className="ado2-contents-page">
<div className="ado2-contents-header">
<h1 className="ado2-contents-title">{t('sidebar.ado2Contents')}</h1>
<span className="ado2-contents-count">{t('ado2Contents.totalCount', { count: total })}</span>
</div>
<div className="ado2-contents-filters">
<select
className="ado2-filter-select"
value={`${sortBy}__${order}`}
onChange={(e) => {
const [sb, ord] = e.target.value.split('__') as [typeof sortBy, typeof order];
setSortBy(sb);
setOrder(ord);
setPage(1);
}}
>
<option value="created_at__desc">{t('ado2Contents.sortLatest')}</option>
<option value="created_at__asc">{t('ado2Contents.sortOldest')}</option>
<option value="like_count__desc">{t('ado2Contents.sortLikes')}</option>
<option value="comment_count__desc">{t('ado2Contents.sortComments')}</option>
</select>
<button
type="button"
className={`ado2-region-pill ${region ? 'active' : ''}`}
onClick={() => setShowCityModal(true)}
>
{region || t('ado2Contents.regionPlaceholder')}
{region && (
<span className="ado2-region-clear" onClick={(e) => { e.stopPropagation(); setRegion(''); setPage(1); }}></span>
)}
</button>
<form
className="ado2-filter-search"
onSubmit={(e) => {
e.preventDefault();
setStoreName(searchInput);
setPage(1);
}}
>
<input
type="text"
className="ado2-filter-input"
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
placeholder={t('ado2Contents.searchPlaceholder')}
/>
<button type="submit" className="ado2-filter-btn">
{t('ado2Contents.searchBtn')}
</button>
</form>
</div>
{loading ? (
<div className="ado2-contents-loading">
<div className="loading-spinner"></div>
<p>{t('ado2Contents.loading')}</p>
</div>
) : error ? (
<div className="ado2-contents-error">
<p>{error}</p>
<button onClick={fetchVideos} className="retry-btn">{t('ado2Contents.retry')}</button>
</div>
) : videos.length === 0 ? (
<div className="ado2-contents-empty">
<p>{t('ado2Contents.noContent')}</p>
</div>
) : (
<>
<div className="ado2-contents-grid">
{videos.map((video) => (
<div
key={video.video_id}
className="ado2-content-card"
style={{ cursor: 'pointer' }}
onClick={() => handleCardClick(video.video_id)}
role="button"
tabIndex={0}
onKeyDown={(e) => e.key === 'Enter' && handleCardClick(video.video_id)}
>
<div className="content-card-thumbnail ado2-gallery-thumbnail-wrap">
{video.thumbnail_url ? (
<img
src={video.thumbnail_url}
alt={video.store_name}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
) : video.result_movie_url ? (
<video
src={video.result_movie_url}
className="content-video-preview"
preload="metadata"
muted
playsInline
/>
) : (
<div className="content-no-video" />
)}
{/* 호버 오버레이 */}
<div className="ado2-gallery-overlay">
<div className="ado2-gallery-play-btn">
<svg width="28" height="28" viewBox="0 0 24 24" fill="currentColor">
<polygon points="5,3 19,12 5,21"/>
</svg>
</div>
</div>
</div>
<div className="content-card-info">
<div className="content-card-text">
<h3 className="content-card-title">{video.store_name}</h3>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<p className="content-card-date">{formatDate(video.created_at)}</p>
<span className="content-card-like">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" strokeWidth="2">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
</svg>
{video.like_count ?? 0}
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ marginLeft: '6px' }}>
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
</svg>
{video.comment_count ?? 0}
</span>
</div>
</div>
</div>
</div>
))}
</div>
<div className="ado2-contents-pagination">
<button
className="pagination-btn"
onClick={() => setPage(p => p - 1)}
disabled={!hasPrev}
>
{t('ado2Contents.previous')}
</button>
<span className="pagination-info">{page} / {totalPages}</span>
<button
className="pagination-btn"
onClick={() => setPage(p => p + 1)}
disabled={!hasNext}
>
{t('ado2Contents.next')}
</button>
</div>
</>
)}
{!authed && (
<LoginPromptModal onClose={() => { window.location.href = '/'; }} />
)}
{selectedVideoId !== null && (
<VideoDetailModal
videoId={String(selectedVideoId)}
onClose={() => setSelectedVideoId(null)}
/>
)}
{showCityModal && (
<CitySelectModal
selected={region}
onSelect={(city) => { setRegion(city); setPage(1); }}
onClose={() => setShowCityModal(false)}
/>
)}
</div>
);
};
export default ADO2ContentsPage;