fix: 품목관리 수정 기능 버그 수정 및 Sales 페이지 추가
## 품목관리 수정 버그 수정 - FG(제품) 수정 시 품목명 반영 안되는 문제 해결 - productName → name 필드 매핑 추가 - FG 품목코드 = 품목명 동기화 로직 추가 - Materials(SM, RM, CS) 수정페이지 진입 오류 해결 - UNIQUE 제약조건 위반 오류 해결 ## Sales 페이지 - 거래처관리 (client-management-sales-admin) 페이지 구현 - 견적관리 (quote-management) 페이지 구현 - 관련 컴포넌트 및 훅 추가 ## 기타 - 회원가입 페이지 차단 처리 - 디버깅용 콘솔 로그 추가 (PUT 요청/응답 확인용) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
74
src/components/organisms/FormActions.tsx
Normal file
74
src/components/organisms/FormActions.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* FormActions - 폼 하단 액션 버튼 그룹
|
||||
*/
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import { Button } from "../ui/button";
|
||||
import { Save, X } from "lucide-react";
|
||||
|
||||
export interface FormActionsProps {
|
||||
onSave?: () => void;
|
||||
onCancel?: () => void;
|
||||
saveLabel?: string;
|
||||
cancelLabel?: string;
|
||||
saveDisabled?: boolean;
|
||||
cancelDisabled?: boolean;
|
||||
saveLoading?: boolean;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
}
|
||||
|
||||
export function FormActions({
|
||||
onSave,
|
||||
onCancel,
|
||||
saveLabel = "저장",
|
||||
cancelLabel = "취소",
|
||||
saveDisabled = false,
|
||||
cancelDisabled = false,
|
||||
saveLoading = false,
|
||||
children,
|
||||
className = "",
|
||||
align = 'right',
|
||||
}: FormActionsProps) {
|
||||
|
||||
const alignClasses = {
|
||||
left: "justify-start",
|
||||
center: "justify-center",
|
||||
right: "justify-end",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col md:flex-row gap-3 ${alignClasses[align]} ${className}`}>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<>
|
||||
{onCancel && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={onCancel}
|
||||
disabled={cancelDisabled}
|
||||
className="w-full md:w-auto"
|
||||
>
|
||||
<X className="h-4 w-4 mr-2" />
|
||||
{cancelLabel}
|
||||
</Button>
|
||||
)}
|
||||
{onSave && (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onSave}
|
||||
disabled={saveDisabled || saveLoading}
|
||||
className="w-full md:w-auto"
|
||||
>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
{saveLoading ? "저장 중..." : saveLabel}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
35
src/components/organisms/FormFieldGrid.tsx
Normal file
35
src/components/organisms/FormFieldGrid.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* FormFieldGrid - 반응형 폼 필드 그리드
|
||||
*
|
||||
* 모바일: 1컬럼
|
||||
* 태블릿: 2컬럼
|
||||
* 데스크톱: 3컬럼 (또는 사용자 지정)
|
||||
*/
|
||||
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export interface FormFieldGridProps {
|
||||
children: ReactNode;
|
||||
columns?: 1 | 2 | 3 | 4;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function FormFieldGrid({
|
||||
children,
|
||||
columns = 3,
|
||||
className = "",
|
||||
}: FormFieldGridProps) {
|
||||
|
||||
const gridClasses = {
|
||||
1: "grid-cols-1",
|
||||
2: "grid-cols-1 md:grid-cols-2",
|
||||
3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3",
|
||||
4: "grid-cols-1 md:grid-cols-2 lg:grid-cols-4",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`grid ${gridClasses[columns]} gap-4 ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
62
src/components/organisms/FormSection.tsx
Normal file
62
src/components/organisms/FormSection.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* FormSection - 폼 섹션 카드 컴포넌트
|
||||
*
|
||||
* 등록 페이지의 각 섹션을 카드로 감싸는 컴포넌트
|
||||
* 제목, 설명, 아이콘을 포함할 수 있습니다.
|
||||
*/
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
||||
import { LucideIcon } from "lucide-react";
|
||||
|
||||
export interface FormSectionProps {
|
||||
title?: string;
|
||||
description?: string;
|
||||
icon?: LucideIcon;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
headerAction?: ReactNode;
|
||||
variant?: 'default' | 'highlighted';
|
||||
}
|
||||
|
||||
export function FormSection({
|
||||
title,
|
||||
description,
|
||||
icon: Icon,
|
||||
children,
|
||||
className = "",
|
||||
headerAction,
|
||||
variant = 'default',
|
||||
}: FormSectionProps) {
|
||||
|
||||
const variantClasses = {
|
||||
default: "",
|
||||
highlighted: "border-blue-200 bg-blue-50/30",
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={`${variantClasses[variant]} ${className}`}>
|
||||
{(title || description) && (
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
{Icon && <Icon className="h-5 w-5 text-primary" />}
|
||||
<div>
|
||||
{title && <CardTitle>{title}</CardTitle>}
|
||||
{description && (
|
||||
<CardDescription className="mt-1">{description}</CardDescription>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{headerAction && (
|
||||
<div>{headerAction}</div>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
)}
|
||||
<CardContent className={title || description ? "" : "pt-6"}>
|
||||
{children}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user