init studio admin frontend

This commit is contained in:
2026-07-01 17:34:15 +09:00
commit c50bd2b941
64 changed files with 22452 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
import { LoaderCircle } from 'lucide-react';
import { useEffect, useState } from 'react';
import { Badge } from '../../components/ui';
import { formatDateTime as formatDateTimeValue } from '../../lib/date-format';
export type BadgeTone = 'neutral' | 'green' | 'amber' | 'red' | 'blue' | 'violet';
export function statusTone(status?: string | null): BadgeTone {
if (!status) return 'neutral';
if (['활성', '완료', '게시됨', '게시중', '실행 중', '배포됨', '성공', 'Running', 'ACTIVE'].includes(status)) return 'green';
if (['주의', '예약됨', '예약', '대기', '준비중', '카나리', '만료', 'PENDING'].includes(status)) return 'amber';
if (['잠김', '실패', '오프라인', '비활성', '종료', 'FAILED', 'Error', 'CrashLoopBackOff'].includes(status)) return 'red';
if (['런처'].includes(status)) return 'violet';
return 'neutral';
}
export function StatusBadge({ status, tone }: { status: string; tone?: BadgeTone }) {
return <Badge tone={tone ?? statusTone(status)}>{status}</Badge>;
}
export function formatDateTime(value?: string | null) {
return formatDateTimeValue(value);
}
export function LoadingModal({ open }: { open: boolean }) {
if (!open) return null;
return (
<div className="fixed inset-x-0 bottom-0 top-16 z-[15] flex items-center justify-center bg-background/20 px-4 backdrop-blur-[1px] lg:left-[17rem]">
<div className="flex min-h-28 min-w-72 items-center justify-center gap-3 rounded-lg border border-border bg-card px-8 py-6 text-sm font-semibold shadow-2xl lg:-translate-x-[8.5rem]">
<LoaderCircle className="size-5 animate-spin text-primary" />
데이터를 불러오는 중입니다.
</div>
</div>
);
}
export function useMinimumLoading(loading: boolean, minimumMs = 500) {
const [visible, setVisible] = useState(loading);
useEffect(() => {
if (loading) {
setVisible(true);
return;
}
const timer = window.setTimeout(() => setVisible(false), minimumMs);
return () => window.clearTimeout(timer);
}, [loading, minimumMs]);
return visible;
}