feat(WEB): Phase 2-3 V2 마이그레이션 완료 및 ServerErrorPage 적용
Phase 2 완료 (4개): - 노무관리, 단가관리(건설), 입금, 출금 Phase 3 라우팅 구조 변경 완료 (22개): - 거래처(영업), 팝업관리, 공정관리, 게시판관리, 대손추심, Q&A - 현장관리, 실행내역, 견적관리, 견적(테스트) - 입찰관리, 이슈관리, 현장설명회, 견적서(건설) - 협력업체, 시공관리, 기성관리, 품목관리(건설) - 회계 도메인: 거래처, 매출, 세금계산서, 매입 신규 컴포넌트: - ErrorCard: 에러 페이지 UI 통일 - ServerErrorPage: V2 페이지 에러 처리 필수 - V2 Client 컴포넌트 및 Config 파일들 총 47개 상세 페이지 중 28개 완료, 19개 제외/불필요 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
174
src/components/ui/error-card.tsx
Normal file
174
src/components/ui/error-card.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { SearchX, Home, ArrowLeft, Map, AlertCircle, ServerCrash } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
||||
type ErrorType = 'not-found' | 'network' | 'error';
|
||||
|
||||
interface ErrorCardProps {
|
||||
type?: ErrorType;
|
||||
title?: string;
|
||||
description?: string;
|
||||
tips?: string[];
|
||||
showBackButton?: boolean;
|
||||
showHomeButton?: boolean;
|
||||
backButtonLabel?: string;
|
||||
homeButtonLabel?: string;
|
||||
homeButtonHref?: string;
|
||||
onBack?: () => void;
|
||||
}
|
||||
|
||||
const ERROR_CONFIG: Record<ErrorType, {
|
||||
icon: typeof SearchX;
|
||||
iconColor: string;
|
||||
bgColor: string;
|
||||
emoji: string;
|
||||
defaultTitle: string;
|
||||
defaultDescription: string;
|
||||
defaultTips: string[];
|
||||
}> = {
|
||||
'not-found': {
|
||||
icon: SearchX,
|
||||
iconColor: 'text-yellow-600 dark:text-yellow-500',
|
||||
bgColor: 'from-yellow-500/20 to-orange-500/20',
|
||||
emoji: '?',
|
||||
defaultTitle: '페이지를 찾을 수 없습니다',
|
||||
defaultDescription: '요청하신 페이지가 존재하지 않거나 접근 권한이 없습니다.',
|
||||
defaultTips: [
|
||||
'메뉴에서 올바른 페이지를 선택했는지 확인',
|
||||
'해당 페이지에 접근 권한이 있는지 확인',
|
||||
'페이지가 아직 개발 중일 수 있습니다',
|
||||
],
|
||||
},
|
||||
'network': {
|
||||
icon: ServerCrash,
|
||||
iconColor: 'text-orange-600 dark:text-orange-500',
|
||||
bgColor: 'from-orange-500/20 to-red-500/20',
|
||||
emoji: '!',
|
||||
defaultTitle: '데이터를 불러올 수 없습니다',
|
||||
defaultDescription: '서버와의 연결에 문제가 발생했습니다.',
|
||||
defaultTips: [
|
||||
'인터넷 연결 상태를 확인해주세요',
|
||||
'잠시 후 다시 시도해주세요',
|
||||
'문제가 지속되면 관리자에게 문의해주세요',
|
||||
],
|
||||
},
|
||||
'error': {
|
||||
icon: AlertCircle,
|
||||
iconColor: 'text-red-600 dark:text-red-500',
|
||||
bgColor: 'from-red-500/20 to-pink-500/20',
|
||||
emoji: '!',
|
||||
defaultTitle: '오류가 발생했습니다',
|
||||
defaultDescription: '요청을 처리하는 중 문제가 발생했습니다.',
|
||||
defaultTips: [
|
||||
'페이지를 새로고침 해보세요',
|
||||
'잠시 후 다시 시도해주세요',
|
||||
'문제가 지속되면 관리자에게 문의해주세요',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function ErrorCard({
|
||||
type = 'not-found',
|
||||
title,
|
||||
description,
|
||||
tips,
|
||||
showBackButton = true,
|
||||
showHomeButton = true,
|
||||
backButtonLabel = '이전 페이지',
|
||||
homeButtonLabel = '목록으로 이동',
|
||||
homeButtonHref,
|
||||
onBack,
|
||||
}: ErrorCardProps) {
|
||||
const router = useRouter();
|
||||
const config = ERROR_CONFIG[type];
|
||||
const Icon = config.icon;
|
||||
|
||||
const handleBack = () => {
|
||||
if (onBack) {
|
||||
onBack();
|
||||
} else {
|
||||
router.back();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[calc(100vh-200px)] p-4">
|
||||
<Card className="w-full max-w-2xl border border-border/20 bg-card/50 backdrop-blur">
|
||||
<CardHeader className="text-center pb-4">
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="relative">
|
||||
<div className={`w-24 h-24 bg-gradient-to-br ${config.bgColor} rounded-2xl flex items-center justify-center`}>
|
||||
<Icon className={`w-12 h-12 ${config.iconColor}`} strokeWidth={1.5} />
|
||||
</div>
|
||||
<div className="absolute -top-1 -right-1 w-8 h-8 bg-yellow-500 rounded-full flex items-center justify-center shadow-lg">
|
||||
<span className="text-xl font-bold text-white">{config.emoji}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CardTitle className="text-2xl md:text-3xl font-bold text-foreground mb-2">
|
||||
{title || config.defaultTitle}
|
||||
</CardTitle>
|
||||
<p className="text-muted-foreground">
|
||||
{description || config.defaultDescription}
|
||||
</p>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-6">
|
||||
{/* 안내 메시지 */}
|
||||
<div className="bg-muted/50 rounded-xl p-6 space-y-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<Map className="w-5 h-5 text-primary mt-0.5 flex-shrink-0" />
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-foreground font-medium">
|
||||
다음을 확인해주세요:
|
||||
</p>
|
||||
<ul className="text-sm text-muted-foreground space-y-1 list-disc list-inside">
|
||||
{(tips || config.defaultTips).map((tip, index) => (
|
||||
<li key={index}>{tip}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 액션 버튼 */}
|
||||
<div className="flex flex-col sm:flex-row gap-3 pt-4">
|
||||
{showBackButton && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex-1 rounded-xl"
|
||||
onClick={handleBack}
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
{backButtonLabel}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{showHomeButton && homeButtonHref && (
|
||||
<Button
|
||||
asChild
|
||||
className="flex-1 rounded-xl bg-primary hover:bg-primary/90"
|
||||
>
|
||||
<Link href={homeButtonHref}>
|
||||
<Home className="w-4 h-4 mr-2" />
|
||||
{homeButtonLabel}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 도움말 */}
|
||||
<div className="pt-6 border-t border-border/20 text-center">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
좌측 메뉴에서 이용 가능한 페이지를 확인하실 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user