diff --git a/admin-panel/src/App.tsx b/admin-panel/src/App.tsx index fe337db..3ce7a6c 100644 --- a/admin-panel/src/App.tsx +++ b/admin-panel/src/App.tsx @@ -33,15 +33,30 @@ interface EnvVars { [key: string]: string | undefined; } +interface FeedRequest { + id: string; + url: string; + requestedBy?: string; + requestMessage?: string; + status: 'pending' | 'approved' | 'rejected'; + createdAt: string; + reviewedAt?: string; + reviewedBy?: string; + adminNotes?: string; +} + function App() { const [feeds, setFeeds] = useState([]); const [stats, setStats] = useState(null); const [envVars, setEnvVars] = useState({}); + const [feedRequests, setFeedRequests] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const [newFeedUrl, setNewFeedUrl] = useState(''); - const [activeTab, setActiveTab] = useState<'dashboard' | 'feeds' | 'env' | 'batch'>('dashboard'); + const [requestFilter, setRequestFilter] = useState<'all' | 'pending' | 'approved' | 'rejected'>('all'); + const [approvalNotes, setApprovalNotes] = useState<{[key: string]: string}>({}); + const [activeTab, setActiveTab] = useState<'dashboard' | 'feeds' | 'env' | 'batch' | 'requests'>('dashboard'); useEffect(() => { loadData(); @@ -50,25 +65,28 @@ function App() { const loadData = async () => { setLoading(true); try { - const [feedsRes, statsRes, envRes] = await Promise.all([ + const [feedsRes, statsRes, envRes, requestsRes] = await Promise.all([ fetch('/api/admin/feeds'), fetch('/api/admin/stats'), - fetch('/api/admin/env') + fetch('/api/admin/env'), + fetch('/api/admin/feed-requests') ]); - if (!feedsRes.ok || !statsRes.ok || !envRes.ok) { + if (!feedsRes.ok || !statsRes.ok || !envRes.ok || !requestsRes.ok) { throw new Error('Failed to load data'); } - const [feedsData, statsData, envData] = await Promise.all([ + const [feedsData, statsData, envData, requestsData] = await Promise.all([ feedsRes.json(), statsRes.json(), - envRes.json() + envRes.json(), + requestsRes.json() ]); setFeeds(feedsData); setStats(statsData); setEnvVars(envData); + setFeedRequests(requestsData); setError(null); } catch (err) { setError('データの読み込みに失敗しました'); @@ -218,6 +236,65 @@ function App() { } }; + const approveFeedRequest = async (requestId: string, notes: string) => { + try { + const res = await fetch(`/api/admin/feed-requests/${requestId}/approve`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ adminNotes: notes }) + }); + + const data = await res.json(); + + if (res.ok) { + setSuccess(data.message || 'フィードリクエストを承認しました'); + setApprovalNotes({ ...approvalNotes, [requestId]: '' }); + loadData(); + } else { + setError(data.error || 'フィードリクエストの承認に失敗しました'); + } + } catch (err) { + setError('フィードリクエストの承認に失敗しました'); + console.error('Error approving feed request:', err); + } + }; + + const rejectFeedRequest = async (requestId: string, notes: string) => { + if (!confirm('このフィードリクエストを拒否しますか?')) { + return; + } + + try { + const res = await fetch(`/api/admin/feed-requests/${requestId}/reject`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ adminNotes: notes }) + }); + + const data = await res.json(); + + if (res.ok) { + setSuccess(data.message || 'フィードリクエストを拒否しました'); + setApprovalNotes({ ...approvalNotes, [requestId]: '' }); + loadData(); + } else { + setError(data.error || 'フィードリクエストの拒否に失敗しました'); + } + } catch (err) { + setError('フィードリクエストの拒否に失敗しました'); + console.error('Error rejecting feed request:', err); + } + }; + + const updateApprovalNotes = (requestId: string, notes: string) => { + setApprovalNotes({ ...approvalNotes, [requestId]: notes }); + }; + + const filteredRequests = feedRequests.filter(request => { + if (requestFilter === 'all') return true; + return request.status === requestFilter; + }); + if (loading) { return
読み込み中...
; } @@ -253,6 +330,12 @@ function App() { > バッチ管理 + + + + + + + + {filteredRequests.length === 0 ? ( +

+ {requestFilter === 'all' ? 'フィードリクエストがありません' : `${requestFilter === 'pending' ? '保留中' : requestFilter === 'approved' ? '承認済み' : '拒否済み'}のリクエストがありません`} +

+ ) : ( +
+ {filteredRequests.map((request) => ( +
+
+

{request.url}

+ {request.requestMessage && ( +

+ メッセージ: {request.requestMessage} +

+ )} +
+ 申請者: {request.requestedBy || '匿名'} + | + 申請日: {new Date(request.createdAt).toLocaleString('ja-JP')} + {request.reviewedAt && ( + <> + | + 審査日: {new Date(request.reviewedAt).toLocaleString('ja-JP')} + + )} +
+ + {request.status === 'pending' ? '保留中' : request.status === 'approved' ? '承認済み' : '拒否済み'} + + {request.adminNotes && ( +
+ 管理者メモ: {request.adminNotes} +
+ )} +
+ {request.status === 'pending' && ( +
+