refactor: UniversalListPage externalIsLoading 지원 및 스켈레톤 개선
- UniversalListPage에 externalIsLoading prop 추가 - CardTransactionDetailClient DevFill 자동입력 기능 추가 - 여러 컴포넌트 로딩 상태 처리 개선 - skeleton 컴포넌트 확장 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { DetailPageSkeleton } from '@/components/ui/skeleton';
|
||||
import { BoardDetail } from '@/components/board/BoardDetail';
|
||||
import { getPost } from '@/components/board/actions';
|
||||
import type { Post, Comment } from '@/components/board/types';
|
||||
@@ -60,7 +60,7 @@ export default function BoardDetailPage() {
|
||||
}, [boardCode, postId, router]);
|
||||
|
||||
if (isLoading) {
|
||||
return <ContentLoadingSpinner text="게시글을 불러오는 중..." />;
|
||||
return <DetailPageSkeleton sections={1} fieldsPerSection={4} />;
|
||||
}
|
||||
|
||||
if (!post) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function BoardEditRedirectPage() {
|
||||
const router = useRouter();
|
||||
@@ -20,5 +20,9 @@ export default function BoardEditRedirectPage() {
|
||||
router.replace(`/ko/board/board-management/${id}?mode=edit`);
|
||||
}, [router, id]);
|
||||
|
||||
return <ContentLoadingSpinner text="페이지 이동 중..." />;
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import { ArrowLeft, Save, MessageSquare } from 'lucide-react';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { DetailPageSkeleton } from '@/components/ui/skeleton';
|
||||
import { PageLayout } from '@/components/organisms/PageLayout';
|
||||
import { PageHeader } from '@/components/organisms/PageHeader';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -147,7 +147,7 @@ export default function DynamicBoardEditPage() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<PageLayout>
|
||||
<ContentLoadingSpinner text="게시글을 불러오는 중..." />
|
||||
<DetailPageSkeleton />
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
|
||||
import WorkerStatusListClient from '@/components/business/construction/worker-status/WorkerStatusListClient';
|
||||
import { getWorkerStatusList, getWorkerStatusStats } from '@/components/business/construction/worker-status/actions';
|
||||
import type { WorkerStatus, WorkerStatusStats } from '@/components/business/construction/worker-status/types';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { ListPageSkeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function WorkerStatusPage() {
|
||||
const [data, setData] = useState<WorkerStatus[]>([]);
|
||||
@@ -25,7 +25,7 @@ export default function WorkerStatusPage() {
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return <ContentLoadingSpinner />;
|
||||
return <ListPageSkeleton showHeader={false} showStats={true} statsCount={4} />;
|
||||
}
|
||||
|
||||
return <WorkerStatusListClient initialData={data} initialStats={stats} />;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import { Suspense } from 'react';
|
||||
import { AttendanceManagement } from '@/components/hr/AttendanceManagement';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { ListPageSkeleton } from '@/components/ui/skeleton';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function AttendanceManagementPage() {
|
||||
return (
|
||||
<Suspense fallback={<ContentLoadingSpinner text="근태 정보를 불러오는 중..." />}>
|
||||
<Suspense fallback={<ListPageSkeleton showHeader={false} showStats={true} statsCount={4} />}>
|
||||
<AttendanceManagement />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { Suspense } from 'react';
|
||||
import { DepartmentManagement } from '@/components/hr/DepartmentManagement';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { ListPageSkeleton } from '@/components/ui/skeleton';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
/**
|
||||
@@ -20,7 +20,7 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function DepartmentManagementPage() {
|
||||
return (
|
||||
<Suspense fallback={<ContentLoadingSpinner text="부서 정보를 불러오는 중..." />}>
|
||||
<Suspense fallback={<ListPageSkeleton showHeader={false} />}>
|
||||
<DepartmentManagement />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { FormSectionSkeleton } from '@/components/ui/skeleton';
|
||||
import { format } from 'date-fns';
|
||||
import { ko } from 'date-fns/locale';
|
||||
import { toast } from 'sonner';
|
||||
@@ -263,7 +263,7 @@ function DocumentNewContent() {
|
||||
|
||||
export default function DocumentNewPage() {
|
||||
return (
|
||||
<Suspense fallback={<ContentLoadingSpinner text="문서 양식을 불러오는 중..." />}>
|
||||
<Suspense fallback={<FormSectionSkeleton rows={6} />}>
|
||||
<DocumentNewContent />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { EmployeeForm } from '@/components/hr/EmployeeManagement/EmployeeForm';
|
||||
import { getEmployeeById, deleteEmployee, updateEmployee } from '@/components/hr/EmployeeManagement/actions';
|
||||
import { ServerErrorPage } from '@/components/common/ServerErrorPage';
|
||||
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { DetailPageSkeleton } from '@/components/ui/skeleton';
|
||||
import type { Employee, EmployeeFormData } from '@/components/hr/EmployeeManagement/types';
|
||||
|
||||
export default function EmployeeDetailPage() {
|
||||
@@ -89,7 +89,7 @@ export default function EmployeeDetailPage() {
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <ContentLoadingSpinner text="사원 정보를 불러오는 중..." />;
|
||||
return <DetailPageSkeleton sections={2} fieldsPerSection={8} />;
|
||||
}
|
||||
|
||||
if (!employee) {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import { Suspense } from 'react';
|
||||
import { EmployeeManagement } from '@/components/hr/EmployeeManagement';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { ListPageSkeleton } from '@/components/ui/skeleton';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function EmployeeManagementPage() {
|
||||
return (
|
||||
<Suspense fallback={<ContentLoadingSpinner text="사원 정보를 불러오는 중..." />}>
|
||||
<Suspense fallback={<ListPageSkeleton showHeader={false} showStats={true} statsCount={4} />}>
|
||||
<EmployeeManagement />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import { Suspense } from 'react';
|
||||
import { SalaryManagement } from '@/components/hr/SalaryManagement';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { ListPageSkeleton } from '@/components/ui/skeleton';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function SalaryManagementPage() {
|
||||
return (
|
||||
<Suspense fallback={<ContentLoadingSpinner text="급여 정보를 불러오는 중..." />}>
|
||||
<Suspense fallback={<ListPageSkeleton showHeader={false} showStats={true} statsCount={4} />}>
|
||||
<SalaryManagement />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import { Suspense } from 'react';
|
||||
import { VacationManagement } from '@/components/hr/VacationManagement';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { ListPageSkeleton } from '@/components/ui/skeleton';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function VacationManagementPage() {
|
||||
return (
|
||||
<Suspense fallback={<ContentLoadingSpinner text="휴가 정보를 불러오는 중..." />}>
|
||||
<Suspense fallback={<ListPageSkeleton showHeader={false} showStats={true} statsCount={4} />}>
|
||||
<VacationManagement />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PageLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { ListPageSkeleton } from '@/components/ui/skeleton';
|
||||
|
||||
/**
|
||||
* Protected Group Loading UI
|
||||
@@ -7,11 +7,8 @@ import { PageLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
* - AuthenticatedLayout 내에서 표시됨 (사이드바, 헤더 유지)
|
||||
* - React Suspense 자동 적용
|
||||
* - 페이지 전환 시 즉각적인 피드백
|
||||
* - 공통 레이아웃 스타일로 통일 (min-h-[calc(100vh-200px)])
|
||||
*
|
||||
* Note: 특정 경로에서 Skeleton UI를 사용하려면 해당 경로에
|
||||
* 별도의 loading.tsx를 생성하세요. (예: settings/accounts/loading.tsx)
|
||||
* - 스켈레톤 UI로 레이아웃 유지하며 로딩 표시
|
||||
*/
|
||||
export default function ProtectedLoading() {
|
||||
return <PageLoadingSpinner />;
|
||||
return <ListPageSkeleton />;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { Suspense } from 'react';
|
||||
import { ItemMasterDataManagement } from '@/components/items/ItemMasterDataManagement';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { ListPageSkeleton } from '@/components/ui/skeleton';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
/**
|
||||
@@ -20,7 +20,7 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function ItemMasterDataManagementPage() {
|
||||
return (
|
||||
<Suspense fallback={<ContentLoadingSpinner text="품목기준정보를 불러오는 중..." />}>
|
||||
<Suspense fallback={<ListPageSkeleton showHeader={false} />}>
|
||||
<ItemMasterDataManagement />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import { use, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function ProcessEditRedirectPage({
|
||||
params,
|
||||
@@ -23,5 +23,9 @@ export default function ProcessEditRedirectPage({
|
||||
router.replace(`/ko/master-data/process-management/${id}?mode=edit`);
|
||||
}, [router, id]);
|
||||
|
||||
return <ContentLoadingSpinner text="페이지 이동 중..." />;
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import { Suspense } from 'react';
|
||||
import ProcessListClient from '@/components/process-management/ProcessListClient';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { ListPageSkeleton } from '@/components/ui/skeleton';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -14,7 +14,7 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function ProcessManagementPage() {
|
||||
return (
|
||||
<Suspense fallback={<ContentLoadingSpinner text="공정 목록을 불러오는 중..." />}>
|
||||
<Suspense fallback={<ListPageSkeleton showHeader={false} />}>
|
||||
<ProcessListClient />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
import { Suspense } from 'react';
|
||||
import ProductionDashboard from '@/components/production/ProductionDashboard';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { ListPageSkeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function ProductionDashboardPage() {
|
||||
return (
|
||||
<Suspense fallback={<ContentLoadingSpinner text="생산 현황을 불러오는 중..." />}>
|
||||
<Suspense fallback={<ListPageSkeleton showHeader={false} showStats={true} statsCount={6} />}>
|
||||
<ProductionDashboard />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
import { Suspense } from 'react';
|
||||
import WorkerScreen from '@/components/production/WorkerScreen';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { ListPageSkeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function WorkerScreenPage() {
|
||||
return (
|
||||
<Suspense fallback={<ContentLoadingSpinner text="작업자 화면을 불러오는 중..." />}>
|
||||
<Suspense fallback={<ListPageSkeleton showHeader={false} showStats={true} statsCount={4} />}>
|
||||
<WorkerScreen />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function ClientEditRedirectPage() {
|
||||
const router = useRouter();
|
||||
@@ -20,5 +20,9 @@ export default function ClientEditRedirectPage() {
|
||||
router.replace(`/ko/sales/client-management-sales-admin/${id}?mode=edit`);
|
||||
}, [router, id]);
|
||||
|
||||
return <ContentLoadingSpinner text="페이지 이동 중..." />;
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ import {
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
} from "lucide-react";
|
||||
import { ContentLoadingSpinner } from "@/components/ui/loading-spinner";
|
||||
import { DetailPageSkeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export default function QuoteDetailPage() {
|
||||
const router = useRouter();
|
||||
@@ -274,7 +274,7 @@ export default function QuoteDetailPage() {
|
||||
}, 0) || 0;
|
||||
|
||||
if (isLoading) {
|
||||
return <ContentLoadingSpinner text="견적 정보를 불러오는 중..." />;
|
||||
return <DetailPageSkeleton sections={2} fieldsPerSection={6} />;
|
||||
}
|
||||
|
||||
if (!quote) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function PopupEditRedirectPage() {
|
||||
const router = useRouter();
|
||||
@@ -20,5 +20,9 @@ export default function PopupEditRedirectPage() {
|
||||
router.replace(`/ko/settings/popup-management/${id}?mode=edit`);
|
||||
}, [router, id]);
|
||||
|
||||
return <ContentLoadingSpinner text="페이지 이동 중..." />;
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user