'use client'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import Link from 'next/link'; import { PageTitle, LabelMeta, TechBar, TechBarFill, Btn } from '@/components/ui/base'; import { API_URL } from '@/lib/config'; import { adminFetch } from '@/lib/adminFetch'; const ProjectList = styled.div`display:flex;flex-direction:column;gap:var(--space-md);`; const ProjectRow = styled(Link)`display:grid;grid-template-columns:1.4fr 1fr 180px;gap:var(--space-lg);padding:var(--space-lg);border:1px solid var(--border-color);align-items:center;text-decoration:none;transition:border-color .15s, background .15s;&:hover{border-color:var(--border-hover);background:#111}@media (max-width:1199px){grid-template-columns:1fr;}`; const Left = styled.div`display:flex;align-items:flex-start;gap:var(--space-md);min-width:0;`; const ProjectMark = styled.div`width:40px;height:40px;border-radius:10px;border:1px solid var(--border-color);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-size:14px;color:var(--text-secondary);background:#111;flex-shrink:0;`; const NameBlock = styled.div`display:flex;flex-direction:column;gap:4px;min-width:0;`; const ProjectName = styled.div`font-size:15px;font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`; const ProjectDesc = styled.div`font-size:12px;color:var(--text-secondary);line-height:1.5;`; const Middle = styled.div`display:flex;flex-direction:column;gap:8px;`; const ProgressRow = styled.div`display:flex;align-items:center;gap:var(--space-sm);`; const Right = styled.div`display:flex;flex-direction:column;align-items:flex-end;gap:8px;@media (max-width:1199px){align-items:flex-start;}`; const Meta = styled.div`font-size:11px;color:var(--text-secondary);font-family:var(--font-mono);`; const EmptyState = styled.div`padding:var(--space-xl) 0;color:var(--text-secondary);font-size:13px;font-family:var(--font-mono);`; export default function ProjectsPage() { const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); const [syncing, setSyncing] = useState(false); const [syncResult, setSyncResult] = useState(null); const loadProjects = () => { setLoading(true); fetch(`${API_URL}/api/projects`).then((r) => r.json()).then(setProjects).catch(() => {}).finally(() => setLoading(false)); }; const handleSync = async () => { setSyncing(true); setSyncResult(null); try { const res = await adminFetch('/api/admin/gitea/sync', { method: 'POST' }); const d = await res.json(); setSyncResult(`sync: ${d.created ?? 0} new, ${d.updated ?? 0} updated`); loadProjects(); } catch { setSyncResult('sync failed'); } finally { setSyncing(false); } }; const handleSprintSync = async () => { setSyncing(true); setSyncResult(null); try { const res = await adminFetch('/api/projects/sync-all-sprints', { method: 'POST' }); const d = await res.json(); setSyncResult(`sprint sync: ${d.synced ?? 0} synced across ${d.projects ?? 0} projects`); loadProjects(); } catch { setSyncResult('sprint sync failed'); } finally { setSyncing(false); } }; useEffect(() => { loadProjects(); }, []); return ( <>
프로젝트
{syncResult && {syncResult}} VOL: {String(projects.length).padStart(2, '0')} {syncing ? 'SYNCING...' : '↻ GITEA SYNC'} {syncing ? '...' : '↻ SPRINT SYNC'}
{loading ? LOADING... : projects.length === 0 ? NO PROJECTS REGISTERED : ( {projects.map((p) => ( {String(p.name ?? 'P').slice(0, 1).toUpperCase()} {p.name} {p.description ?? '설명 없음'} {p.progress ?? 0}% {p.doneSprints ?? 0} / {p.totalSprints ?? 0} SPRINTS DONE {p.currentSprint ?? p.status?.toUpperCase() ?? 'ACTIVE'} {new Date(p.updatedAt).toLocaleDateString('ko-KR')} UPDATED ))} )} ); }