refactor: UniversalListPage externalIsLoading 지원 및 스켈레톤 개선

- UniversalListPage에 externalIsLoading prop 추가
- CardTransactionDetailClient DevFill 자동입력 기능 추가
- 여러 컴포넌트 로딩 상태 처리 개선
- skeleton 컴포넌트 확장

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-22 20:54:16 +09:00
parent 207520e1d6
commit 19237be4aa
71 changed files with 244 additions and 155 deletions

View File

@@ -11,7 +11,7 @@ import { useRouter } from 'next/navigation';
import { cn } from '@/lib/utils';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { PageLoadingSpinner } from '@/components/ui/loading-spinner';
import { FormSectionSkeleton } from '@/components/ui/skeleton';
import { Alert, AlertDescription } from '@/components/ui/alert';
import {
Card,
@@ -616,12 +616,7 @@ export default function DynamicItemForm({
// 로딩 상태
if (isLoading && selectedItemType) {
return (
<PageLoadingSpinner
text="폼 구조를 불러오는 중..."
minHeight="min-h-[40vh]"
/>
);
return <FormSectionSkeleton />;
}
// 에러 상태

View File

@@ -13,7 +13,7 @@ import { useRouter } from 'next/navigation';
import DynamicItemForm from '@/components/items/DynamicItemForm';
import type { DynamicFormData, BOMLine } from '@/components/items/DynamicItemForm/types';
import type { ItemType } from '@/types/item';
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
import { DetailPageSkeleton } from '@/components/ui/skeleton';
import {
isMaterialType,
transformMaterialDataForSave,
@@ -368,7 +368,7 @@ export function ItemDetailEdit({ itemCode, itemType: urlItemType, itemId: urlIte
// 로딩 상태
if (isLoading) {
return <ContentLoadingSpinner text="품목 정보를 불러오는 중..." />;
return <DetailPageSkeleton sections={2} fieldsPerSection={6} />;
}
// 에러 상태

View File

@@ -11,7 +11,7 @@ import { useRouter } from 'next/navigation';
import { notFound } from 'next/navigation';
import ItemDetailClient from '@/components/items/ItemDetailClient';
import type { ItemMaster, ItemType, ProductCategory, PartType, PartUsage } from '@/types/item';
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
import { DetailPageSkeleton } from '@/components/ui/skeleton';
import { ServerErrorPage } from '@/components/common/ServerErrorPage';
// Materials 타입 (SM, RM, CS는 Material 테이블 사용)
@@ -253,7 +253,7 @@ export function ItemDetailView({ itemCode, itemType, itemId }: ItemDetailViewPro
// 로딩 상태
if (isLoading) {
return <ContentLoadingSpinner text="품목 정보를 불러오는 중..." />;
return <DetailPageSkeleton sections={2} fieldsPerSection={6} />;
}
// 에러 상태

View File

@@ -18,7 +18,6 @@ import { Checkbox } from '@/components/ui/checkbox';
import { TableRow, TableCell } from '@/components/ui/table';
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
import { Search, Plus, Edit, Trash2, Package } from 'lucide-react';
import { TableLoadingSpinner } from '@/components/ui/loading-spinner';
import { useItemList } from '@/hooks/useItemList';
import { handleApiError } from '@/lib/api/error-handler';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
@@ -122,11 +121,6 @@ export default function ItemListClient() {
});
}, [debouncedSearchTerm, selectedType, search]);
// 로딩 상태
if (isLoading) {
return <TableLoadingSpinner text="품목 목록을 불러오는 중..." />;
}
// 유형 변경 핸들러
const handleTypeChange = (value: string) => {
setSelectedType(value);
@@ -491,6 +485,7 @@ export default function ItemListClient() {
itemsPerPage: pagination.perPage,
onPageChange: handlePageChange,
}}
externalIsLoading={isLoading}
/>
{/* 개별 삭제 확인 다이얼로그 */}

View File

@@ -6,7 +6,7 @@ import { PageHeader } from '@/components/organisms/PageHeader';
import { useItemMaster } from '@/contexts/ItemMasterContext';
import type { SectionTemplate, BOMItem, TemplateField } from '@/contexts/ItemMasterContext';
import { MasterFieldTab, HierarchyTab, SectionsTab } from './ItemMasterDataManagement/tabs';
import { LoadingSpinner } from '@/components/ui/loading-spinner';
import { DetailPageSkeleton } from '@/components/ui/skeleton';
import { ServerErrorPage } from '@/components/common/ServerErrorPage';
// 2025-12-24: Phase 2 UI 컴포넌트 분리
import { AttributeTabContent } from './ItemMasterDataManagement/components';
@@ -450,11 +450,7 @@ function ItemMasterDataManagementContent() {
// 초기 로딩 중 UI
if (isInitialLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<LoadingSpinner size="lg" text="데이터를 불러오는 중..." />
</div>
);
return <DetailPageSkeleton />;
}
// 에러 발생 시 UI

View File

@@ -16,7 +16,7 @@ import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { FormInput, Search, Info, Loader2, Hash, Calendar, CheckSquare, ChevronDown, Type, AlignLeft, Database } from 'lucide-react';
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
import { ContentSkeleton } from '@/components/ui/skeleton';
import type { ItemField, ItemMasterField } from '@/contexts/ItemMasterContext';
import type { FieldUsageResponse } from '@/types/item-master-api';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
@@ -157,7 +157,7 @@ export function ImportFieldDialog({
<div className="space-y-4">
{isLoading ? (
<ContentLoadingSpinner text="필드 목록을 불러오는 중..." />
<ContentSkeleton type="list" rows={5} />
) : filteredFields.length === 0 ? (
<div className="text-center py-8">
<FormInput className="h-12 w-12 mx-auto text-muted-foreground mb-4" />

View File

@@ -7,7 +7,7 @@ import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Package, Folder, Search, Info, Loader2 } from 'lucide-react';
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
import { ContentSkeleton } from '@/components/ui/skeleton';
import type { ItemSection } from '@/contexts/ItemMasterContext';
import type { SectionUsageResponse } from '@/types/item-master-api';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
@@ -109,7 +109,7 @@ export function ImportSectionDialog({
<div className="space-y-4">
{isLoading ? (
<ContentLoadingSpinner text="섹션 목록을 불러오는 중..." />
<ContentSkeleton type="list" rows={5} />
) : filteredSections.length === 0 ? (
<div className="text-center py-8">
<Folder className="h-12 w-12 mx-auto text-muted-foreground mb-4" />