refactor(WEB): 컴포넌트 레지스트리 개선 및 미사용 코드 정리

- component-registry를 파일 시스템 기반 동적 스캔으로 전환 (정적 JSON 삭제)
- 미사용 컴포넌트 삭제 (EmptyState, StandardDialog)
- 회사정보 관리 페이지 개선
- 컴포넌트 계층 정의 가이드 문서 추가
- middleware 및 useDaumPostcode 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-12 14:15:09 +09:00
parent 020d74f36c
commit 8d685109d3
14 changed files with 523 additions and 7005 deletions

View File

@@ -18,7 +18,6 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
export interface StandardDialogProps {
@@ -84,136 +83,3 @@ export function StandardDialog({
);
}
/**
* 확인 다이얼로그
*/
export interface ConfirmDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
title: string;
description: string;
onConfirm: () => void;
confirmText?: string;
cancelText?: string;
confirmVariant?:
| "default"
| "destructive"
| "outline"
| "secondary"
| "ghost"
| "link";
loading?: boolean;
}
export function ConfirmDialog({
open,
onOpenChange,
title,
description,
onConfirm,
confirmText = "확인",
cancelText = "취소",
confirmVariant = "default",
loading = false,
}: ConfirmDialogProps) {
const handleConfirm = () => {
onConfirm();
onOpenChange(false);
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader className="px-6 pt-6">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<div className="px-6 pb-4"></div>
<DialogFooter>
<Button
variant="outline"
onClick={() => onOpenChange(false)}
disabled={loading}
>
{cancelText}
</Button>
<Button
variant={confirmVariant}
onClick={handleConfirm}
disabled={loading}
>
{confirmText}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
/**
* 폼 다이얼로그
*/
export interface FormDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
title: string;
description?: string;
children: React.ReactNode;
onSave: () => void;
onCancel?: () => void;
saveText?: string;
cancelText?: string;
size?: "sm" | "md" | "lg" | "xl" | "full";
loading?: boolean;
disabled?: boolean;
}
export function FormDialog({
open,
onOpenChange,
title,
description,
children,
onSave,
onCancel,
saveText = "저장",
cancelText = "취소",
size = "md",
loading = false,
disabled = false,
}: FormDialogProps) {
const handleCancel = () => {
if (onCancel) {
onCancel();
}
onOpenChange(false);
};
const handleSave = () => {
onSave();
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className={cn(sizeClasses[size])}>
<DialogHeader className="px-6 pt-6">
<DialogTitle>{title}</DialogTitle>
{description && <DialogDescription>{description}</DialogDescription>}
</DialogHeader>
<div className="px-6 pb-4 overflow-y-auto max-h-[60vh]">{children}</div>
<DialogFooter>
<Button variant="outline" onClick={handleCancel} disabled={loading}>
{cancelText}
</Button>
<Button onClick={handleSave} disabled={loading || disabled}>
{saveText}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -6,8 +6,8 @@ export { IconWithBadge } from "./IconWithBadge";
export { TableActions } from "./TableActions";
export type { TableAction } from "./TableActions";
export { StandardDialog, ConfirmDialog, FormDialog } from "./StandardDialog";
export type { StandardDialogProps, ConfirmDialogProps, FormDialogProps } from "./StandardDialog";
export { StandardDialog } from "./StandardDialog";
export type { StandardDialogProps } from "./StandardDialog";
export { YearQuarterFilter } from "./YearQuarterFilter";
export type { Quarter } from "./YearQuarterFilter";

View File

@@ -1,37 +0,0 @@
"use client";
import { LucideIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
interface EmptyStateProps {
icon?: LucideIcon;
title: string;
description?: string;
action?: {
label: string;
onClick: () => void;
};
}
export function EmptyState({ icon: Icon, title, description, action }: EmptyStateProps) {
return (
<div className="flex flex-col items-center justify-center py-12 text-center">
{Icon && (
<div className="mb-4 p-4 bg-muted rounded-full">
<Icon className="w-8 h-8 text-muted-foreground" />
</div>
)}
<h3 className="text-lg font-semibold mb-2">{title}</h3>
{description && (
<p className="text-sm text-muted-foreground mb-6 max-w-md">
{description}
</p>
)}
{action && (
<Button onClick={action.onClick}>
{action.label}
</Button>
)}
</div>
);
}

View File

@@ -6,7 +6,7 @@ export { DataTable } from "./DataTable";
export type { Column, CellType } from "./DataTable";
export { MobileCard, ListMobileCard, InfoField } from "./MobileCard";
export type { MobileCardProps, InfoFieldProps } from "./MobileCard";
export { EmptyState } from "./EmptyState";
export { EmptyState, TableEmptyState } from "@/components/ui/empty-state";
export { ScreenVersionHistory } from "./ScreenVersionHistory";
export { SearchableSelectionModal } from "./SearchableSelectionModal";
export type { SearchableSelectionModalProps, SingleSelectProps, MultipleSelectProps } from "./SearchableSelectionModal";

View File

@@ -6,7 +6,7 @@ import { Building2, Plus, Save, X, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { BusinessNumberInput } from '@/components/ui/business-number-input';
import { FormField } from '@/components/molecules/FormField';
import { AccountNumberInput } from '@/components/ui/account-number-input';
import { ImageUpload } from '@/components/ui/image-upload';
import { FileInput } from '@/components/ui/file-input';
@@ -213,50 +213,38 @@ export function CompanyInfoManagement() {
{/* 회사명 / 대표자명 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="companyName"></Label>
<Input
id="companyName"
value={formData.companyName}
onChange={(e) => handleChange('companyName', e.target.value)}
placeholder="회사명"
disabled={!isEditMode}
/>
</div>
<div className="space-y-2">
<Label htmlFor="representativeName"></Label>
<Input
id="representativeName"
value={formData.representativeName}
onChange={(e) => handleChange('representativeName', e.target.value)}
placeholder="대표자명"
disabled={!isEditMode}
/>
</div>
<FormField
label="회사명"
value={formData.companyName}
onChange={(value) => handleChange('companyName', value)}
placeholder="회사명"
disabled={!isEditMode}
/>
<FormField
label="대표자명"
value={formData.representativeName}
onChange={(value) => handleChange('representativeName', value)}
placeholder="대표자명"
disabled={!isEditMode}
/>
</div>
{/* 업태 / 업종 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="businessType"></Label>
<Input
id="businessType"
value={formData.businessType}
onChange={(e) => handleChange('businessType', e.target.value)}
placeholder="업태명"
disabled={!isEditMode}
/>
</div>
<div className="space-y-2">
<Label htmlFor="businessCategory"></Label>
<Input
id="businessCategory"
value={formData.businessCategory}
onChange={(e) => handleChange('businessCategory', e.target.value)}
placeholder="업종명"
disabled={!isEditMode}
/>
</div>
<FormField
label="업태"
value={formData.businessType}
onChange={(value) => handleChange('businessType', value)}
placeholder="업태명"
disabled={!isEditMode}
/>
<FormField
label="업종"
value={formData.businessCategory}
onChange={(value) => handleChange('businessCategory', value)}
placeholder="업종명"
disabled={!isEditMode}
/>
</div>
{/* 주소 */}
@@ -290,28 +278,20 @@ export function CompanyInfoManagement() {
{/* 이메일 / 세금계산서 이메일 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="email"> ()</Label>
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => handleChange('email', e.target.value)}
placeholder="abc@email.com"
disabled={!isEditMode}
/>
</div>
<div className="space-y-2">
<Label htmlFor="taxInvoiceEmail"> </Label>
<Input
id="taxInvoiceEmail"
type="email"
value={formData.taxInvoiceEmail}
onChange={(e) => handleChange('taxInvoiceEmail', e.target.value)}
placeholder="abc@email.com"
disabled={!isEditMode}
/>
</div>
<FormField
label="이메일 (아이디)"
value={formData.email}
onChange={(value) => handleChange('email', value)}
placeholder="abc@email.com"
disabled={!isEditMode}
/>
<FormField
label="세금계산서 이메일"
value={formData.taxInvoiceEmail}
onChange={(value) => handleChange('taxInvoiceEmail', value)}
placeholder="abc@email.com"
disabled={!isEditMode}
/>
</div>
{/* 담당자명 / 담당자 연락처 - 임시 주석처리 (추후 사용 가능) */}
@@ -352,16 +332,14 @@ export function CompanyInfoManagement() {
placeholder="파일을 선택하세요"
/>
</div>
<div className="space-y-2">
<Label htmlFor="businessNumber"></Label>
<BusinessNumberInput
id="businessNumber"
value={formData.businessNumber}
onChange={(value) => handleChange('businessNumber', value)}
placeholder="123-12-12345"
disabled={!isEditMode}
/>
</div>
<FormField
label="사업자등록번호"
type="businessNumber"
value={formData.businessNumber}
onChange={(value) => handleChange('businessNumber', value)}
placeholder="123-12-12345"
disabled={!isEditMode}
/>
</div>
</CardContent>
</Card>
@@ -374,16 +352,13 @@ export function CompanyInfoManagement() {
<CardContent className="space-y-6">
{/* 결제 은행 / 계좌 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="paymentBank"> </Label>
<Input
id="paymentBank"
value={formData.paymentBank}
onChange={(e) => handleChange('paymentBank', e.target.value)}
placeholder="은행명"
disabled={!isEditMode}
/>
</div>
<FormField
label="결제 은행"
value={formData.paymentBank}
onChange={(value) => handleChange('paymentBank', value)}
placeholder="은행명"
disabled={!isEditMode}
/>
<div className="space-y-2">
<Label htmlFor="paymentAccount"></Label>
<AccountNumberInput
@@ -398,16 +373,13 @@ export function CompanyInfoManagement() {
{/* 예금주 / 결제일 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="paymentAccountHolder"></Label>
<Input
id="paymentAccountHolder"
value={formData.paymentAccountHolder}
onChange={(e) => handleChange('paymentAccountHolder', e.target.value)}
placeholder="예금주명"
disabled={!isEditMode}
/>
</div>
<FormField
label="예금주"
value={formData.paymentAccountHolder}
onChange={(value) => handleChange('paymentAccountHolder', value)}
placeholder="예금주명"
disabled={!isEditMode}
/>
<div className="space-y-2">
<Label htmlFor="paymentDay"></Label>
{isEditMode ? (