Files
webflash/platform/frontend/src/pages/Admin/Index.jsx
root bdef4b7ae0 feat: add ESP32 DIY platform Phase 1 (marketplace scaffold)
- Docker Compose with Postgres, Redis, MinIO, backend, frontend (port 3200/3201)
- Prisma schema: User, Project, ProjectFile, Product, Order, FlashToken, Review, AuditLog
- Backend: JWT auth, project CRUD + file upload (MinIO + sharp WebP), admin approval flow
- Frontend: React + Vite SPA with auth, project/shop browse, seller dashboard, admin panel
- Admin: pending approval queue, user management, audit log viewer, stats dashboard
- Audit logging middleware for legal compliance
- Admin init script: createAdmin.js
- Full design document in PLATFORM_DESIGN.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 06:05:46 +09:00

58 lines
2.8 KiB
JavaScript

import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import api from '../../api/client';
export default function AdminIndex() {
const [stats, setStats] = useState(null);
useEffect(() => {
api.get('/admin/stats').then(r => setStats(r.data)).catch(() => {});
}, []);
const cards = stats ? [
{ label: '총 사용자', value: stats.users.toLocaleString(), icon: '👤' },
{ label: '전체 프로젝트', value: stats.projects.total.toLocaleString(), icon: '📁' },
{ label: '승인 대기', value: stats.projects.pending.toLocaleString(), icon: '⏳', warn: stats.projects.pending > 0 },
{ label: '총 매출', value: `${(stats.revenue.total || 0).toLocaleString()}`, icon: '💰' },
{ label: '총 주문', value: stats.revenue.orders.toLocaleString(), icon: '🛒' },
] : [];
return (
<div className="container page">
<h2 style={{ marginBottom: 24 }}>관리자 대시보드</h2>
{/* 통계 카드 */}
{stats && (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 16, marginBottom: 32 }}>
{cards.map(c => (
<div key={c.label} className="card" style={{ borderColor: c.warn ? 'var(--warn)' : undefined }}>
<div style={{ fontSize: 28, marginBottom: 8 }}>{c.icon}</div>
<div style={{ fontSize: 22, fontWeight: 700, color: c.warn ? 'var(--warn)' : 'var(--text)' }}>{c.value}</div>
<div className="text-muted" style={{ fontSize: 13 }}>{c.label}</div>
</div>
))}
</div>
)}
{/* 빠른 메뉴 */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 16 }}>
{[
{ to: '/admin/projects', icon: '📋', title: '프로젝트 승인', desc: '검토 대기 중인 프로젝트를 승인/반려' },
{ to: '/admin/users', icon: '👥', title: '사용자 관리', desc: '사용자 목록, 역할 변경, 계정 비활성화' },
{ to: '/admin/logs', icon: '📜', title: '감사 로그', desc: '모든 주요 행동 기록 조회' },
].map(m => (
<Link key={m.to} to={m.to} style={{ textDecoration: 'none' }}>
<div className="card" style={{ cursor: 'pointer', transition: 'border-color .2s' }}
onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--accent)'}
onMouseLeave={e => e.currentTarget.style.borderColor = 'var(--border)'}>
<div style={{ fontSize: 32, marginBottom: 8 }}>{m.icon}</div>
<div style={{ fontWeight: 600, marginBottom: 4 }}>{m.title}</div>
<div className="text-muted" style={{ fontSize: 13 }}>{m.desc}</div>
</div>
</Link>
))}
</div>
</div>
);
}