fix: 품목기준관리 실시간 동기화 수정

- BOM 항목 추가/수정/삭제 시 섹션탭 즉시 반영
- 섹션 복제 시 UI 즉시 업데이트 (null vs undefined 이슈 해결)
- 항목 수정 기능 추가 (useTemplateManagement)
- 실시간 동기화 문서 추가

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-11-27 22:19:50 +09:00
parent b73603822b
commit 65a8510c0b
130 changed files with 11031 additions and 2287 deletions

View File

@@ -0,0 +1,354 @@
/**
* 원자재/부자재/소모품 (RM/SM/CS) 폼 컴포넌트
*/
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import type { ItemType } from '@/types/item';
import type { UseFormRegister, UseFormSetValue, UseFormGetValues, FieldErrors } from 'react-hook-form';
import type { CreateItemFormData } from '@/lib/utils/validation';
interface MaterialFormProps {
selectedItemType: ItemType;
itemName: string;
setItemName: (value: string) => void;
selectedSpecification: string;
setSelectedSpecification: (value: string) => void;
materialStatus: string;
setMaterialStatus: (value: string) => void;
selectedUnit: string;
setSelectedUnit: (value: string) => void;
register: UseFormRegister<CreateItemFormData>;
setValue: UseFormSetValue<CreateItemFormData>;
getValues: UseFormGetValues<CreateItemFormData>;
errors: FieldErrors<CreateItemFormData>;
}
export default function MaterialForm({
selectedItemType,
itemName,
setItemName,
selectedSpecification,
setSelectedSpecification,
materialStatus,
setMaterialStatus,
selectedUnit,
setSelectedUnit,
register,
setValue,
getValues,
errors,
}: MaterialFormProps) {
return (
<>
<div>
<Label htmlFor="itemName">
<span className="text-red-500">*</span>
</Label>
{/* 원자재/부자재는 목록에서 선택, 소모품은 직접 입력 */}
{selectedItemType === 'RM' ? (
<>
<Select
value={itemName}
onValueChange={(value) => {
setItemName(value);
setValue('itemName', value);
// 품목명 변경 시 규격 초기화
setSelectedSpecification('');
setValue('specification', '');
// 품목코드 자동생성
const spec = getValues('specification') || '';
setValue('itemCode', spec ? `${value}-${spec}` : value);
}}
>
<SelectTrigger className={errors.itemName ? 'border-red-500' : ''}>
<SelectValue placeholder="품목명을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="철판"></SelectItem>
<SelectItem value="알루미늄"></SelectItem>
<SelectItem value="스테인리스"></SelectItem>
<SelectItem value="아연도금강판"></SelectItem>
</SelectContent>
</Select>
{errors.itemName && (
<p className="text-xs text-red-500 mt-1">
{errors.itemName.message}
</p>
)}
</>
) : selectedItemType === 'SM' ? (
<>
<Select
value={itemName}
onValueChange={(value) => {
setItemName(value);
setValue('itemName', value);
// 품목명 변경 시 규격 초기화
setSelectedSpecification('');
setValue('specification', '');
// 품목코드 자동생성
const spec = getValues('specification') || '';
setValue('itemCode', spec ? `${value}-${spec}` : value);
}}
>
<SelectTrigger className={errors.itemName ? 'border-red-500' : ''}>
<SelectValue placeholder="품목명을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="볼트"></SelectItem>
<SelectItem value="너트"></SelectItem>
<SelectItem value="와셔"></SelectItem>
<SelectItem value="나사"></SelectItem>
<SelectItem value="앵커"></SelectItem>
</SelectContent>
</Select>
{errors.itemName && (
<p className="text-xs text-red-500 mt-1">
{errors.itemName.message}
</p>
)}
</>
) : (
<>
<Input
id="itemName"
placeholder="품목명을 입력하세요"
value={itemName}
onChange={(e) => {
const newName = e.target.value;
setItemName(newName);
setValue('itemName', newName);
// 품목코드 자동생성
const spec = getValues('specification') || '';
setValue('itemCode', spec ? `${newName}-${spec}` : newName);
}}
className={errors.itemName ? 'border-red-500' : ''}
/>
{errors.itemName && (
<p className="text-xs text-red-500 mt-1">
{errors.itemName.message}
</p>
)}
</>
)}
</div>
{/* 규격(사양) */}
{selectedItemType === 'CS' ? (
<div>
<Label htmlFor="specification">
() <span className="text-red-500">*</span>
</Label>
<Input
id="specification"
placeholder="예: 면-L, 고급형, A4"
{...register('specification', {
onChange: (e) => {
// 품목코드 자동생성
const spec = e.target.value;
const name = itemName || '';
setValue('itemCode', name && spec ? `${name}-${spec}` : name);
}
})}
className={errors.specification ? 'border-red-500' : ''}
/>
{errors.specification && (
<p className="text-xs text-red-500 mt-1">
{errors.specification.message}
</p>
)}
</div>
) : (
<div className="md:col-span-2">
<Label htmlFor="specification">
<span className="text-red-500">*</span>
</Label>
<Select
value={selectedSpecification}
onValueChange={(value) => {
setSelectedSpecification(value);
setValue('specification', value);
// 품목코드 자동생성
const name = itemName || '';
setValue('itemCode', name && value ? `${name}-${value}` : name);
}}
disabled={!itemName}
>
<SelectTrigger id="specification" className={errors.specification ? 'border-red-500' : ''}>
<SelectValue placeholder={itemName ? "규격을 선택하세요" : "품목명을 먼저 선택하세요"} />
</SelectTrigger>
<SelectContent>
{selectedItemType === 'RM' && itemName === '철판' && (
<>
<SelectItem value="1.0T">1.0T</SelectItem>
<SelectItem value="1.2T">1.2T</SelectItem>
<SelectItem value="1.5T">1.5T</SelectItem>
<SelectItem value="2.0T">2.0T</SelectItem>
</>
)}
{selectedItemType === 'RM' && itemName === '알루미늄' && (
<>
<SelectItem value="0.8T">0.8T</SelectItem>
<SelectItem value="1.0T">1.0T</SelectItem>
<SelectItem value="1.5T">1.5T</SelectItem>
</>
)}
{selectedItemType === 'RM' && itemName === '스테인리스' && (
<>
<SelectItem value="0.5T">0.5T</SelectItem>
<SelectItem value="1.0T">1.0T</SelectItem>
<SelectItem value="1.2T">1.2T</SelectItem>
</>
)}
{selectedItemType === 'SM' && itemName === '볼트' && (
<>
<SelectItem value="M6x20">M6×20mm</SelectItem>
<SelectItem value="M8x25">M8×25mm</SelectItem>
<SelectItem value="M10x30">M10×30mm</SelectItem>
<SelectItem value="M12x40">M12×40mm</SelectItem>
<SelectItem value="M16x50">M16×50mm</SelectItem>
</>
)}
{selectedItemType === 'SM' && itemName === '너트' && (
<>
<SelectItem value="M6">M6</SelectItem>
<SelectItem value="M8">M8</SelectItem>
<SelectItem value="M10">M10</SelectItem>
<SelectItem value="M12">M12</SelectItem>
<SelectItem value="M16">M16</SelectItem>
</>
)}
{selectedItemType === 'SM' && itemName === '와셔' && (
<>
<SelectItem value="M6">M6</SelectItem>
<SelectItem value="M8">M8</SelectItem>
<SelectItem value="M10">M10</SelectItem>
<SelectItem value="M12">M12</SelectItem>
<SelectItem value="M16">M16</SelectItem>
</>
)}
{selectedItemType === 'SM' && itemName === '나사' && (
<>
<SelectItem value="4x20">4×20mm</SelectItem>
<SelectItem value="5x25">5×25mm</SelectItem>
<SelectItem value="6x30">6×30mm</SelectItem>
<SelectItem value="8x40">8×40mm</SelectItem>
<SelectItem value="10x50">10×50mm</SelectItem>
</>
)}
{selectedItemType === 'SM' && itemName === '앵커' && (
<>
<SelectItem value="6x30">6×30mm</SelectItem>
<SelectItem value="8x40">8×40mm</SelectItem>
<SelectItem value="10x50">10×50mm</SelectItem>
<SelectItem value="12x60">12×60mm</SelectItem>
<SelectItem value="16x80">16×80mm</SelectItem>
</>
)}
</SelectContent>
</Select>
{errors.specification && (
<p className="text-xs text-red-500 mt-1">
{errors.specification.message}
</p>
)}
{!errors.specification && (
<p className="text-xs text-muted-foreground mt-1">
*
</p>
)}
</div>
)}
{/* 품목코드 (자동생성) */}
<div className="md:col-span-2">
<Label htmlFor="itemCode-auto"> ()</Label>
<Input
id="itemCode-auto"
placeholder="품목명과 규격이 입력되면 자동으로 생성됩니다"
value={(() => {
const name = itemName || '';
const spec = getValues('specification') || '';
return spec ? `${name}-${spec}` : name;
})()}
disabled
className="bg-muted text-muted-foreground"
/>
<p className="text-xs text-muted-foreground mt-1">
* '품목명-규격'
</p>
</div>
{/* 품목 상태 (RM/SM만) */}
{(selectedItemType === 'RM' || selectedItemType === 'SM') && (
<div className="md:col-span-2">
<Label htmlFor="isActive"> </Label>
<Select
value={materialStatus}
onValueChange={(value) => {
setMaterialStatus(value);
setValue('isActive', value === 'true');
}}
>
<SelectTrigger id="isActive">
<SelectValue placeholder="품목 상태" />
</SelectTrigger>
<SelectContent>
<SelectItem value="true"></SelectItem>
<SelectItem value="false"></SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground mt-1">
*
</p>
</div>
)}
{/* 단위 (RM/SM/CS 공통) */}
<div>
<Label htmlFor="unit">
<span className="text-red-500">*</span>
</Label>
<Select
value={selectedUnit}
onValueChange={(value) => {
setSelectedUnit(value);
setValue('unit', value);
}}
>
<SelectTrigger id="unit" className={errors.unit ? 'border-red-500' : ''}>
<SelectValue placeholder="단위를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="M">M ()</SelectItem>
<SelectItem value="mm">mm ()</SelectItem>
<SelectItem value="EA">EA ()</SelectItem>
<SelectItem value="SET">SET ()</SelectItem>
<SelectItem value="KG">KG ()</SelectItem>
<SelectItem value="T">T ()</SelectItem>
<SelectItem value="BOX">BOX ()</SelectItem>
<SelectItem value="L">L ()</SelectItem>
<SelectItem value="M2">M² ()</SelectItem>
<SelectItem value="M3">M³ ()</SelectItem>
<SelectItem value="ROLL">ROLL ()</SelectItem>
<SelectItem value="SHEET">SHEET ()</SelectItem>
<SelectItem value="PACK">PACK ()</SelectItem>
</SelectContent>
</Select>
{errors.unit && (
<p className="text-xs text-red-500 mt-1">
{errors.unit.message}
</p>
)}
</div>
</>
);
}

View File

@@ -0,0 +1,273 @@
/**
* 부품 (PT) 폼 컴포넌트
* - ASSEMBLY (조립 부품)
* - BENDING (절곡 부품)
* - PURCHASED (구매 부품)
*/
import { Label } from '@/components/ui/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import type { UseFormRegister, UseFormSetValue, UseFormClearErrors, FieldErrors } from 'react-hook-form';
import type { CreateItemFormData } from '@/lib/utils/validation';
import { AssemblyPartForm, BendingPartForm, PurchasedPartForm } from './parts';
export interface PartFormProps {
// Part Type
selectedPartType: string;
setSelectedPartType: (value: string) => void;
// Category
selectedCategory1: string;
setSelectedCategory1: (value: string) => void;
selectedInstallationType: string;
setSelectedInstallationType: (value: string) => void;
// ASSEMBLY
sideSpecWidth: string;
setSideSpecWidth: (value: string) => void;
sideSpecHeight: string;
setSideSpecHeight: (value: string) => void;
assemblyLength: string;
setAssemblyLength: (value: string) => void;
assemblyUnit: string;
setAssemblyUnit: (value: string) => void;
// BENDING
selectedBendingItemType: string;
setSelectedBendingItemType: (value: string) => void;
material: string;
setMaterial: (value: string) => void;
widthSum: string;
setWidthSum: (value: string) => void;
bendingLength: string;
setBendingLength: (value: string) => void;
partUnit: string;
setPartUnit: (value: string) => void;
bendingDetailsLength: number;
// PURCHASED
electricOpenerPower: string;
setElectricOpenerPower: (value: string) => void;
electricOpenerCapacity: string;
setElectricOpenerCapacity: (value: string) => void;
motorVoltage: string;
setMotorVoltage: (value: string) => void;
chainSpec: string;
setChainSpec: (value: string) => void;
// Common
partStatus: string;
setPartStatus: (value: string) => void;
needsBOM: boolean;
setNeedsBOM: (value: boolean) => void;
// Item Code Generator
generateItemCode: () => string;
// Form
register: UseFormRegister<CreateItemFormData>;
setValue: UseFormSetValue<CreateItemFormData>;
clearErrors: UseFormClearErrors<CreateItemFormData>;
errors: FieldErrors<CreateItemFormData>;
}
export default function PartForm({
selectedPartType,
setSelectedPartType,
selectedCategory1,
setSelectedCategory1,
selectedInstallationType,
setSelectedInstallationType,
sideSpecWidth,
setSideSpecWidth,
sideSpecHeight,
setSideSpecHeight,
assemblyLength,
setAssemblyLength,
assemblyUnit,
setAssemblyUnit,
selectedBendingItemType,
setSelectedBendingItemType,
material,
setMaterial,
widthSum,
setWidthSum,
bendingLength,
setBendingLength,
partUnit,
setPartUnit,
bendingDetailsLength,
electricOpenerPower,
setElectricOpenerPower,
electricOpenerCapacity,
setElectricOpenerCapacity,
motorVoltage,
setMotorVoltage,
chainSpec,
setChainSpec,
partStatus,
setPartStatus,
needsBOM,
setNeedsBOM,
generateItemCode,
register,
setValue,
clearErrors,
errors,
}: PartFormProps) {
// 부품 유형 변경 시 필드 초기화 핸들러
const handlePartTypeChange = (value: string) => {
setSelectedPartType(value);
setValue('partType', value);
clearErrors('partType');
// 공통 필드 초기화
setSelectedCategory1('');
setValue('category1', undefined);
setPartUnit('EA');
setValue('unit', 'EA');
// ASSEMBLY 부품 전용 필드 초기화
setSelectedInstallationType('');
setValue('installationType', undefined);
setSideSpecWidth('');
setValue('sideSpecWidth', '');
setSideSpecHeight('');
setValue('sideSpecHeight', '');
setAssemblyLength('');
setValue('assemblyLength', '');
setAssemblyUnit('EA');
// BENDING 부품 전용 필드 초기화
setSelectedBendingItemType('');
setValue('category2', undefined);
setMaterial('');
setValue('material', '');
setWidthSum('');
setValue('length', '');
setBendingLength('');
setValue('bendingLength', '');
// PURCHASED 부품 전용 필드 초기화
setElectricOpenerPower('');
setValue('electricOpenerPower', '');
setElectricOpenerCapacity('');
setValue('electricOpenerCapacity', '');
setMotorVoltage('');
setValue('motorVoltage', '');
setChainSpec('');
setValue('chainSpec', '');
// BOM 설정 (절곡 부품은 BOM 없음, 조립 부품은 BOM 기본 true)
setNeedsBOM(value === 'BENDING' ? false : value === 'ASSEMBLY' ? true : needsBOM);
};
return (
<>
{/* 부품 유형 선택 - 항상 표시 */}
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={selectedPartType}
onValueChange={handlePartTypeChange}
>
<SelectTrigger className={errors.partType ? 'border-red-500' : ''}>
<SelectValue placeholder="부품 유형을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ASSEMBLY"> (Assembly Part)</SelectItem>
<SelectItem value="BENDING"> (Bending Part) - </SelectItem>
<SelectItem value="PURCHASED"> (Purchased Part)</SelectItem>
</SelectContent>
</Select>
{errors.partType && (
<p className="text-xs text-red-500 mt-1">
{errors.partType.message}
</p>
)}
{!errors.partType && selectedPartType === 'BENDING' && (
<p className="text-xs text-muted-foreground mt-1">
* () , (BOM) .
</p>
)}
</div>
{/* ASSEMBLY 부품인 경우 */}
{selectedPartType === 'ASSEMBLY' && (
<AssemblyPartForm
selectedCategory1={selectedCategory1}
setSelectedCategory1={setSelectedCategory1}
selectedInstallationType={selectedInstallationType}
setSelectedInstallationType={setSelectedInstallationType}
sideSpecWidth={sideSpecWidth}
setSideSpecWidth={setSideSpecWidth}
sideSpecHeight={sideSpecHeight}
setSideSpecHeight={setSideSpecHeight}
assemblyLength={assemblyLength}
setAssemblyLength={setAssemblyLength}
assemblyUnit={assemblyUnit}
setAssemblyUnit={setAssemblyUnit}
partStatus={partStatus}
setPartStatus={setPartStatus}
needsBOM={needsBOM}
setNeedsBOM={setNeedsBOM}
register={register}
setValue={setValue}
errors={errors}
/>
)}
{/* BENDING 부품인 경우 */}
{selectedPartType === 'BENDING' && (
<BendingPartForm
selectedCategory1={selectedCategory1}
setSelectedCategory1={setSelectedCategory1}
selectedBendingItemType={selectedBendingItemType}
setSelectedBendingItemType={setSelectedBendingItemType}
material={material}
setMaterial={setMaterial}
widthSum={widthSum}
setWidthSum={setWidthSum}
bendingLength={bendingLength}
setBendingLength={setBendingLength}
partUnit={partUnit}
setPartUnit={setPartUnit}
bendingDetailsLength={bendingDetailsLength}
partStatus={partStatus}
setPartStatus={setPartStatus}
generateItemCode={generateItemCode}
register={register}
setValue={setValue}
clearErrors={clearErrors}
errors={errors}
/>
)}
{/* PURCHASED 부품인 경우 */}
{selectedPartType === 'PURCHASED' && (
<PurchasedPartForm
selectedCategory1={selectedCategory1}
setSelectedCategory1={setSelectedCategory1}
electricOpenerPower={electricOpenerPower}
setElectricOpenerPower={setElectricOpenerPower}
electricOpenerCapacity={electricOpenerCapacity}
setElectricOpenerCapacity={setElectricOpenerCapacity}
motorVoltage={motorVoltage}
setMotorVoltage={setMotorVoltage}
chainSpec={chainSpec}
setChainSpec={setChainSpec}
partUnit={partUnit}
setPartUnit={setPartUnit}
partStatus={partStatus}
setPartStatus={setPartStatus}
needsBOM={needsBOM}
setNeedsBOM={setNeedsBOM}
register={register}
setValue={setValue}
errors={errors}
/>
)}
</>
);
}

View File

@@ -0,0 +1,337 @@
/**
* 제품 (FG) 폼 컴포넌트
*/
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import { Textarea } from '@/components/ui/textarea';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { X } from 'lucide-react';
import type { UseFormRegister, UseFormSetValue, UseFormGetValues, FieldErrors } from 'react-hook-form';
import type { CreateItemFormData } from '@/lib/utils/validation';
interface ProductFormProps {
productName: string;
setProductName: (value: string) => void;
productStatus: string;
setProductStatus: (value: string) => void;
remarks: string;
setRemarks: (value: string) => void;
needsBOM: boolean;
setNeedsBOM: (value: boolean) => void;
specificationFile: File | null;
setSpecificationFile: (file: File | null) => void;
certificationFile: File | null;
setCertificationFile: (file: File | null) => void;
isSubmitting: boolean;
register: UseFormRegister<CreateItemFormData>;
setValue: UseFormSetValue<CreateItemFormData>;
getValues: UseFormGetValues<CreateItemFormData>;
errors: FieldErrors<CreateItemFormData>;
}
export default function ProductForm({
productName,
setProductName,
productStatus,
setProductStatus,
remarks,
setRemarks,
needsBOM,
setNeedsBOM,
specificationFile,
setSpecificationFile,
certificationFile,
setCertificationFile,
isSubmitting,
register,
setValue,
getValues,
errors,
}: ProductFormProps) {
return (
<>
{/* 기본 정보 */}
<div>
<Label htmlFor="productName">
<span className="text-red-500">*</span>
</Label>
<Input
id="productName"
placeholder="상품명을 입력하세요 (예: 프리미엄 스크린)"
value={productName}
onChange={(e) => {
const newName = e.target.value;
setProductName(newName);
setValue('productName', newName);
}}
className={errors.productName ? 'border-red-500' : ''}
/>
{errors.productName && (
<p className="text-xs text-red-500 mt-1">
{errors.productName.message}
</p>
)}
{!errors.productName && (
<p className="text-xs text-muted-foreground mt-1">
</p>
)}
</div>
<div>
<Label htmlFor="itemName">
<span className="text-red-500">*</span>
</Label>
<Input
id="itemName"
placeholder="품목명을 입력하세요"
{...register('itemName')}
className={errors.itemName ? 'border-red-500' : ''}
/>
{errors.itemName && (
<p className="text-xs text-red-500 mt-1">
{errors.itemName.message}
</p>
)}
{!errors.itemName && (
<p className="text-xs text-muted-foreground mt-1">
</p>
)}
</div>
<div className="md:col-span-2">
<Label> ()</Label>
<Input
value={(() => {
const pName = productName || '';
const iName = getValues('itemName') || '';
return pName && iName ? `${pName}-${iName}` : '';
})()}
disabled
className="bg-muted text-muted-foreground"
placeholder="상품명과 품목명을 입력하면 자동으로 생성됩니다"
/>
<p className="text-xs text-muted-foreground mt-1">
* '상품명-품목명'
</p>
</div>
<div>
<Label> </Label>
<Input
placeholder="로트 약자를 입력하세요"
{...register('lotAbbreviation')}
maxLength={10}
/>
<p className="text-xs text-muted-foreground mt-1">
* ()
</p>
</div>
<div>
<Label> </Label>
<Select
value={productStatus}
onValueChange={(value) => {
setProductStatus(value);
setValue('isActive', value === 'true');
}}
>
<SelectTrigger>
<SelectValue placeholder="품목 상태" />
</SelectTrigger>
<SelectContent>
<SelectItem value="true"></SelectItem>
<SelectItem value="false"></SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground mt-1">
*
</p>
</div>
</>
);
}
/**
* FG 인정 정보 섹션 컴포넌트
*/
export function ProductCertificationSection({
remarks,
setRemarks,
needsBOM,
setNeedsBOM,
specificationFile,
setSpecificationFile,
certificationFile,
setCertificationFile,
isSubmitting,
register,
}: Pick<ProductFormProps,
| 'remarks'
| 'setRemarks'
| 'needsBOM'
| 'setNeedsBOM'
| 'specificationFile'
| 'setSpecificationFile'
| 'certificationFile'
| 'setCertificationFile'
| 'isSubmitting'
| 'register'
>) {
return (
<div className="pt-6 mt-6 border-t space-y-4">
<div>
<h3 className="text-base font-semibold mb-4"> </h3>
<p className="text-sm text-muted-foreground mb-4">
</p>
</div>
{/* 인정번호, 유효기간, 파일 업로드, 비고 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="certificationNumber"></Label>
<Input
id="certificationNumber"
placeholder="인정번호를 입력하세요"
{...register('certificationNumber')}
disabled={isSubmitting}
/>
</div>
<div className="space-y-2">
<Label htmlFor="certificationStartDate"> </Label>
<Input
id="certificationStartDate"
type="date"
{...register('certificationStartDate')}
disabled={isSubmitting}
/>
</div>
<div className="space-y-2">
<Label htmlFor="certificationEndDate"> </Label>
<Input
id="certificationEndDate"
type="date"
{...register('certificationEndDate')}
disabled={isSubmitting}
/>
</div>
{/* 시방서 파일 */}
<div className="space-y-2">
<Label> (PDF, DOCX, HWP, JPG, PNG / 20MB)</Label>
<div className="flex gap-2">
<Input
type="file"
accept=".pdf,.docx,.hwp,.jpg,.jpeg,.png"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
setSpecificationFile(file);
}
}}
className="flex-1"
disabled={isSubmitting}
/>
{specificationFile && (
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setSpecificationFile(null)}
disabled={isSubmitting}
>
<X className="h-4 w-4" />
</Button>
)}
</div>
{specificationFile && (
<p className="text-xs text-muted-foreground mt-1">
: {specificationFile.name}
</p>
)}
</div>
{/* 인정서 파일 */}
<div className="space-y-2">
<Label> (PDF, DOCX, HWP, JPG, PNG / 20MB)</Label>
<div className="flex gap-2">
<Input
type="file"
accept=".pdf,.docx,.hwp,.jpg,.jpeg,.png"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
setCertificationFile(file);
}
}}
className="flex-1"
disabled={isSubmitting}
/>
{certificationFile && (
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setCertificationFile(null)}
disabled={isSubmitting}
>
<X className="h-4 w-4" />
</Button>
)}
</div>
{certificationFile && (
<p className="text-xs text-muted-foreground mt-1">
: {certificationFile.name}
</p>
)}
</div>
{/* 비고 */}
<div className="md:col-span-2">
<Label></Label>
<Textarea
value={remarks}
onChange={(e) => setRemarks(e.target.value)}
placeholder="비고 사항을 입력하세요"
rows={3}
disabled={isSubmitting}
/>
<p className="text-xs text-muted-foreground mt-1">
*
</p>
</div>
</div>
{/* 부품구성 (BOM) 필요 여부 - FG 전용, 인정 정보 카드 내부 */}
<div className="md:col-span-2 pt-6 mt-6 border-t">
<div className="flex items-center space-x-2">
<Checkbox
id="needsBOM-fg"
checked={needsBOM}
onCheckedChange={(checked) => setNeedsBOM(checked as boolean)}
/>
<Label htmlFor="needsBOM-fg" className="cursor-pointer">
(BOM)
</Label>
</div>
<p className="text-xs text-muted-foreground mt-1 ml-6">
*
</p>
</div>
</div>
);
}

View File

@@ -0,0 +1,7 @@
/**
* 품목 유형별 폼 컴포넌트 export
*/
export { default as MaterialForm } from './MaterialForm';
export { default as ProductForm, ProductCertificationSection } from './ProductForm';
export { default as PartForm } from './PartForm';

View File

@@ -0,0 +1,336 @@
/**
* 조립 부품 (ASSEMBLY) 폼 컴포넌트
* - 가이드레일, 케이스, 하단마감재
*/
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import type { UseFormRegister, UseFormSetValue, FieldErrors } from 'react-hook-form';
import type { CreateItemFormData } from '@/lib/utils/validation';
export interface AssemblyPartFormProps {
selectedCategory1: string;
setSelectedCategory1: (value: string) => void;
selectedInstallationType: string;
setSelectedInstallationType: (value: string) => void;
sideSpecWidth: string;
setSideSpecWidth: (value: string) => void;
sideSpecHeight: string;
setSideSpecHeight: (value: string) => void;
assemblyLength: string;
setAssemblyLength: (value: string) => void;
assemblyUnit: string;
setAssemblyUnit: (value: string) => void;
partStatus: string;
setPartStatus: (value: string) => void;
needsBOM: boolean;
setNeedsBOM: (value: boolean) => void;
register: UseFormRegister<CreateItemFormData>;
setValue: UseFormSetValue<CreateItemFormData>;
errors: FieldErrors<CreateItemFormData>;
}
export default function AssemblyPartForm({
selectedCategory1,
setSelectedCategory1,
selectedInstallationType,
setSelectedInstallationType,
sideSpecWidth,
setSideSpecWidth,
sideSpecHeight,
setSideSpecHeight,
assemblyLength,
setAssemblyLength,
assemblyUnit,
setAssemblyUnit,
partStatus,
setPartStatus,
needsBOM,
setNeedsBOM,
register,
setValue,
errors,
}: AssemblyPartFormProps) {
return (
<>
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={selectedCategory1}
onValueChange={(value) => {
setSelectedCategory1(value);
setValue('category1', value);
if (value === 'guide_rail') setValue('itemName', '가이드레일');
else if (value === 'case') setValue('itemName', '케이스');
else if (value === 'bottom_finish') setValue('itemName', '하단마감재');
}}
>
<SelectTrigger className={errors.category1 ? 'border-red-500' : ''}>
<SelectValue placeholder="품목명을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="guide_rail"></SelectItem>
<SelectItem value="case"></SelectItem>
<SelectItem value="bottom_finish"></SelectItem>
</SelectContent>
</Select>
{errors.category1 && (
<p className="text-xs text-red-500 mt-1">
{errors.category1.message}
</p>
)}
</div>
{/* 가이드레일: 설치 유형 */}
{selectedCategory1 === 'guide_rail' && (
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={selectedInstallationType}
onValueChange={(value) => {
setSelectedInstallationType(value);
setValue('installationType', value);
setValue('category2', value === 'wall' ? 'R' : 'S');
}}
>
<SelectTrigger>
<SelectValue placeholder="설치 유형을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="wall"> (R)</SelectItem>
<SelectItem value="side"> (S)</SelectItem>
</SelectContent>
</Select>
</div>
)}
{/* 케이스: 설치 유형 */}
{selectedCategory1 === 'case' && (
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={selectedInstallationType}
onValueChange={(value) => {
setSelectedInstallationType(value);
setValue('installationType', value);
setValue('category2', 'C');
}}
>
<SelectTrigger>
<SelectValue placeholder="설치 유형을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="standard"> (C)</SelectItem>
</SelectContent>
</Select>
</div>
)}
{/* 하단마감재: 설치 유형 */}
{selectedCategory1 === 'bottom_finish' && (
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={selectedInstallationType}
onValueChange={(value) => {
setSelectedInstallationType(value);
setValue('installationType', value);
setValue('category2', value === 'steel' ? 'B' : 'T');
}}
>
<SelectTrigger>
<SelectValue placeholder="설치 유형을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="steel"> (B)</SelectItem>
<SelectItem value="iron"> (T)</SelectItem>
</SelectContent>
</Select>
</div>
)}
{/* ASSEMBLY 공통: 단위, 비고, 측면규격 및 길이 */}
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={assemblyUnit}
onValueChange={(value) => {
setAssemblyUnit(value);
setValue('unit', value);
}}
>
<SelectTrigger>
<SelectValue placeholder="단위를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="EA">EA ()</SelectItem>
<SelectItem value="M">M ()</SelectItem>
<SelectItem value="mm">mm ()</SelectItem>
<SelectItem value="SET">SET ()</SelectItem>
<SelectItem value="KG">KG ()</SelectItem>
</SelectContent>
</Select>
</div>
<div className="md:col-span-2">
<Label></Label>
<Input
placeholder="비고 사항을 입력하세요"
{...register('note')}
/>
</div>
{/* 측면 규격 및 길이 */}
<div className="col-span-2 border-t pt-4">
<h4 className="text-sm font-semibold mb-3"> </h4>
<div className="grid grid-cols-3 gap-3">
<div>
<Label>
() <span className="text-red-500">*</span>
</Label>
<Input
type="number"
placeholder="예: 50"
value={sideSpecWidth}
onChange={(e) => {
setSideSpecWidth(e.target.value);
setValue('sideSpecWidth', e.target.value);
}}
/>
</div>
<div>
<Label>
() <span className="text-red-500">*</span>
</Label>
<Input
type="number"
placeholder="예: 100"
value={sideSpecHeight}
onChange={(e) => {
setSideSpecHeight(e.target.value);
setValue('sideSpecHeight', e.target.value);
}}
/>
</div>
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={assemblyLength}
onValueChange={(value) => {
setAssemblyLength(value);
setValue('assemblyLength', value);
}}
>
<SelectTrigger>
<SelectValue placeholder="길이를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1219">1219mm</SelectItem>
<SelectItem value="2438">2438mm</SelectItem>
<SelectItem value="3000">3000mm</SelectItem>
<SelectItem value="3500">3500mm</SelectItem>
<SelectItem value="4000">4000mm</SelectItem>
<SelectItem value="4150">4150mm</SelectItem>
<SelectItem value="4200">4200mm</SelectItem>
<SelectItem value="4300">4300mm</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<p className="text-xs text-muted-foreground mt-2">
* : {(() => {
const itemName = selectedCategory1 === 'guide_rail' ? '가이드레일' :
selectedCategory1 === 'case' ? '케이스' :
selectedCategory1 === 'bottom_finish' ? '하단마감재' : '';
const installationTypeMap: Record<string, string> = {
"standard": "표준형",
"wall": "벽면형",
"side": "측면형",
"steel": "스크린",
"iron": "철재"
};
const installTypeText = installationTypeMap[selectedInstallationType] || selectedInstallationType;
const length = assemblyLength ? parseInt(assemblyLength) : 0;
let lengthCode = "";
if (length === 1219) lengthCode = "12";
else if (length === 2438) lengthCode = "24";
else if (length === 3000) lengthCode = "30";
else if (length === 3500) lengthCode = "35";
else if (length === 4000) lengthCode = "40";
else if (length === 4150) lengthCode = "41";
else if (length === 4200) lengthCode = "42";
else if (length === 4300) lengthCode = "43";
else lengthCode = Math.floor(length / 100).toString().padStart(2, '0');
if (itemName && installTypeText && sideSpecWidth && sideSpecHeight && assemblyLength) {
return `${itemName} ${installTypeText}-${sideSpecWidth}*${sideSpecHeight}*${lengthCode}`;
}
return "품목명 설치유형-?*?*?";
})()}
</p>
{/* 품목 상태 */}
<div className="mt-4">
<Label> </Label>
<Select
value={partStatus}
onValueChange={(value) => {
setPartStatus(value);
setValue('isActive', value === 'true');
}}
>
<SelectTrigger>
<SelectValue placeholder="품목 상태" />
</SelectTrigger>
<SelectContent>
<SelectItem value="true"></SelectItem>
<SelectItem value="false"></SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground mt-1">
*
</p>
</div>
{/* 부품구성 (BOM) 필요 여부 - ASSEMBLY 전용 */}
{selectedCategory1 && (
<div className="pt-6 mt-6 border-t">
<div className="flex items-center space-x-2">
<Checkbox
id="needsBOM-assembly"
checked={needsBOM}
onCheckedChange={(checked) => setNeedsBOM(checked as boolean)}
/>
<Label htmlFor="needsBOM-assembly" className="cursor-pointer">
(BOM)
</Label>
</div>
<p className="text-xs text-muted-foreground mt-1 ml-6">
*
</p>
</div>
)}
</div>
</>
);
}

View File

@@ -0,0 +1,302 @@
/**
* 절곡 부품 (BENDING) 폼 컴포넌트
* - 가이드레일(벽면/측면), 케이스, 하단마감재 등
*/
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import type { UseFormRegister, UseFormSetValue, UseFormClearErrors, FieldErrors } from 'react-hook-form';
import type { CreateItemFormData } from '@/lib/utils/validation';
import { PART_TYPE_CATEGORIES, PART_ITEM_NAMES } from '../../constants';
export interface BendingPartFormProps {
selectedCategory1: string;
setSelectedCategory1: (value: string) => void;
selectedBendingItemType: string;
setSelectedBendingItemType: (value: string) => void;
material: string;
setMaterial: (value: string) => void;
widthSum: string;
setWidthSum: (value: string) => void;
bendingLength: string;
setBendingLength: (value: string) => void;
partUnit: string;
setPartUnit: (value: string) => void;
bendingDetailsLength: number;
partStatus: string;
setPartStatus: (value: string) => void;
generateItemCode: () => string;
register: UseFormRegister<CreateItemFormData>;
setValue: UseFormSetValue<CreateItemFormData>;
clearErrors: UseFormClearErrors<CreateItemFormData>;
errors: FieldErrors<CreateItemFormData>;
}
export default function BendingPartForm({
selectedCategory1,
setSelectedCategory1,
selectedBendingItemType,
setSelectedBendingItemType,
material,
setMaterial,
widthSum,
setWidthSum,
bendingLength,
setBendingLength,
partUnit,
setPartUnit,
bendingDetailsLength,
partStatus,
setPartStatus,
generateItemCode,
register,
setValue,
clearErrors,
errors,
}: BendingPartFormProps) {
return (
<>
{/* 품목명 선택 */}
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={selectedCategory1}
onValueChange={(val) => {
setSelectedCategory1(val);
setValue('category1', val);
}}
>
<SelectTrigger>
<SelectValue placeholder="품목명을 선택하세요" />
</SelectTrigger>
<SelectContent>
{PART_TYPE_CATEGORIES.BENDING?.categories.map((cat) => (
<SelectItem key={cat.value} value={cat.value}>
{cat.label} ({cat.code})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 종류 선택 */}
{selectedCategory1 && PART_ITEM_NAMES[selectedCategory1] && (
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={selectedBendingItemType}
onValueChange={(value) => {
setSelectedBendingItemType(value);
const selected = PART_ITEM_NAMES[selectedCategory1].find(item => item.label === value);
if (selected) {
setValue('itemName', selected.label);
setValue('category2', selected.code);
clearErrors('category2');
}
}}
>
<SelectTrigger>
<SelectValue placeholder="종류를 선택하세요" />
</SelectTrigger>
<SelectContent>
{PART_ITEM_NAMES[selectedCategory1].map((item) => (
<SelectItem key={item.value} value={item.label}>
{item.label} ({item.code})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{/* 재질, 폭 합계, 모양&길이 (Purple Section) */}
{selectedBendingItemType && (
<div className="md:col-span-2 grid grid-cols-1 md:grid-cols-3 gap-4 p-4 bg-purple-50 rounded-lg border border-purple-200">
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={material}
onValueChange={(value) => {
setMaterial(value);
setValue('material', value);
}}
>
<SelectTrigger className={errors.material ? 'border-red-500' : ''}>
<SelectValue placeholder="재질을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="EGI 1.15T">EGI 1.15T</SelectItem>
<SelectItem value="EGI 1.55T">EGI 1.55T</SelectItem>
<SelectItem value="SUS 1.2T">SUS 1.2T</SelectItem>
<SelectItem value="SUS 1.5T">SUS 1.5T</SelectItem>
</SelectContent>
</Select>
{errors.material && (
<p className="text-xs text-red-500 mt-1">
{errors.material.message}
</p>
)}
</div>
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<div className="flex items-center gap-2">
<Input
type="number"
value={widthSum}
onChange={(e) => {
setWidthSum(e.target.value);
setValue('length', e.target.value);
}}
placeholder="전개도 상세를 입력해주세요"
readOnly={bendingDetailsLength > 0}
className={`${bendingDetailsLength > 0 ? "bg-blue-50 font-medium" : ""} ${errors.length ? 'border-red-500' : ''}`}
/>
<span className="text-sm text-muted-foreground">mm</span>
</div>
{errors.length && (
<p className="text-xs text-red-500 mt-1">
{errors.length.message}
</p>
)}
{!errors.length && bendingDetailsLength > 0 && (
<p className="text-xs text-blue-600 mt-1">
*
</p>
)}
</div>
<div>
<Label>
& <span className="text-red-500">*</span>
</Label>
<Select
value={bendingLength}
onValueChange={(value) => {
setBendingLength(value);
setValue('bendingLength', value);
}}
>
<SelectTrigger className={errors.bendingLength ? 'border-red-500' : ''}>
<SelectValue placeholder="모양&길이를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="W50x3000">W50×3000mm</SelectItem>
<SelectItem value="W50x4000">W50×4000mm</SelectItem>
<SelectItem value="W80x3000">W80×3000mm</SelectItem>
<SelectItem value="W80x4000">W80×4000mm</SelectItem>
<SelectItem value="1219">1219mm</SelectItem>
<SelectItem value="2438">2438mm</SelectItem>
<SelectItem value="3000">3000mm</SelectItem>
<SelectItem value="3500">3500mm</SelectItem>
<SelectItem value="4000">4000mm</SelectItem>
<SelectItem value="4150">4150mm</SelectItem>
<SelectItem value="4200">4200mm</SelectItem>
<SelectItem value="4300">4300mm</SelectItem>
</SelectContent>
</Select>
{errors.bendingLength && (
<p className="text-xs text-red-500 mt-1">
{errors.bendingLength.message}
</p>
)}
</div>
</div>
)}
{/* 단위, 비고 (종류 선택 후 표시) */}
{selectedBendingItemType && (
<>
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={partUnit}
onValueChange={(value) => {
setPartUnit(value);
setValue('unit', value);
}}
>
<SelectTrigger>
<SelectValue placeholder="단위를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="EA">EA ()</SelectItem>
<SelectItem value="M">M ()</SelectItem>
<SelectItem value="mm">mm ()</SelectItem>
<SelectItem value="KG">KG ()</SelectItem>
<SelectItem value="L">L ()</SelectItem>
<SelectItem value="SET">SET ()</SelectItem>
<SelectItem value="BOX">BOX ()</SelectItem>
<SelectItem value="ROLL">ROLL ()</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label></Label>
<Input
placeholder="비고 사항을 입력하세요"
{...register('note')}
/>
</div>
{/* 품목코드 자동생성 */}
<div className="md:col-span-2">
<Label> ()</Label>
<Input
value={generateItemCode()}
disabled
className="bg-muted text-muted-foreground"
placeholder="품목명과 규격이 입력되면 자동으로 생성됩니다"
/>
<p className="text-xs text-muted-foreground mt-1">
{(selectedCategory1 === "guide_rail_wall" || selectedCategory1 === "guide_rail_side")
? "* 가이드레일 품목코드는 '제품구분(R/S)+종류(M/T/C/D/S/U)+모양&길이' 형식으로 자동 생성됩니다 (예: RD30, SM53)"
: "* 절곡 부품 품목코드는 '품목명+종류+길이축약' 형식으로 자동 생성됩니다 (예: 케이스후면부30)"}
</p>
</div>
</>
)}
{/* 품목 상태 */}
<div className="md:col-span-2">
<Label> </Label>
<Select
value={partStatus}
onValueChange={(value) => {
setPartStatus(value);
setValue('isActive', value === 'true');
}}
>
<SelectTrigger>
<SelectValue placeholder="품목 상태" />
</SelectTrigger>
<SelectContent>
<SelectItem value="true"></SelectItem>
<SelectItem value="false"></SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground mt-1">
*
</p>
</div>
</>
);
}

View File

@@ -0,0 +1,318 @@
/**
* 구매 부품 (PURCHASED) 폼 컴포넌트
* - 전동개폐기, 모터, 체인 등
*/
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import type { UseFormRegister, UseFormSetValue, FieldErrors } from 'react-hook-form';
import type { CreateItemFormData } from '@/lib/utils/validation';
import { PART_TYPE_CATEGORIES } from '../../constants';
export interface PurchasedPartFormProps {
selectedCategory1: string;
setSelectedCategory1: (value: string) => void;
electricOpenerPower: string;
setElectricOpenerPower: (value: string) => void;
electricOpenerCapacity: string;
setElectricOpenerCapacity: (value: string) => void;
motorVoltage: string;
setMotorVoltage: (value: string) => void;
chainSpec: string;
setChainSpec: (value: string) => void;
partUnit: string;
setPartUnit: (value: string) => void;
partStatus: string;
setPartStatus: (value: string) => void;
needsBOM: boolean;
setNeedsBOM: (value: boolean) => void;
register: UseFormRegister<CreateItemFormData>;
setValue: UseFormSetValue<CreateItemFormData>;
errors: FieldErrors<CreateItemFormData>;
}
export default function PurchasedPartForm({
selectedCategory1,
setSelectedCategory1,
electricOpenerPower,
setElectricOpenerPower,
electricOpenerCapacity,
setElectricOpenerCapacity,
motorVoltage,
setMotorVoltage,
chainSpec,
setChainSpec,
partUnit,
setPartUnit,
partStatus,
setPartStatus,
needsBOM,
setNeedsBOM,
register,
setValue,
errors,
}: PurchasedPartFormProps) {
return (
<>
{/* 품목명 선택 */}
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={selectedCategory1}
onValueChange={(val) => {
setSelectedCategory1(val);
setValue('category1', val);
const cat = PART_TYPE_CATEGORIES.PURCHASED?.categories.find(c => c.value === val);
if (cat) {
setValue('category2', cat.code);
}
}}
>
<SelectTrigger>
<SelectValue placeholder="품목명을 선택하세요" />
</SelectTrigger>
<SelectContent>
{PART_TYPE_CATEGORIES.PURCHASED?.categories.map((cat) => (
<SelectItem key={cat.value} value={cat.value}>
{cat.label} ({cat.code})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 전동개폐기 전용 필드 */}
{selectedCategory1 === 'electric_opener' && (
<>
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={electricOpenerPower}
onValueChange={(value) => {
setElectricOpenerPower(value);
setValue('electricOpenerPower', value);
}}
>
<SelectTrigger className={errors.electricOpenerPower ? 'border-red-500' : ''}>
<SelectValue placeholder="전원을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="220V">220V</SelectItem>
<SelectItem value="380V">380V</SelectItem>
</SelectContent>
</Select>
{errors.electricOpenerPower && (
<p className="text-xs text-red-500 mt-1">
{errors.electricOpenerPower.message}
</p>
)}
</div>
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={electricOpenerCapacity}
onValueChange={(value) => {
setElectricOpenerCapacity(value);
setValue('electricOpenerCapacity', value);
}}
>
<SelectTrigger className={errors.electricOpenerCapacity ? 'border-red-500' : ''}>
<SelectValue placeholder="용량을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="150">150 KG</SelectItem>
<SelectItem value="300">300 KG</SelectItem>
<SelectItem value="400">400 KG</SelectItem>
<SelectItem value="500">500 KG</SelectItem>
<SelectItem value="600">600 KG</SelectItem>
<SelectItem value="800">800 KG</SelectItem>
<SelectItem value="1000">1000 KG</SelectItem>
</SelectContent>
</Select>
{errors.electricOpenerCapacity && (
<p className="text-xs text-red-500 mt-1">
{errors.electricOpenerCapacity.message}
</p>
)}
</div>
</>
)}
{/* 모터 전용 필드 */}
{selectedCategory1 === 'motor' && (
<div className="md:col-span-2 grid grid-cols-2 gap-4 p-4 bg-green-50 rounded-lg border border-green-200">
<div>
<Label> (kg) *</Label>
<Input type="number" placeholder="예: 1.5" step="0.1" />
</div>
<div>
<Label> (V) *</Label>
<Select
value={motorVoltage}
onValueChange={(value) => {
setMotorVoltage(value);
setValue('motorVoltage', value);
}}
>
<SelectTrigger className={errors.motorVoltage ? 'border-red-500' : ''}>
<SelectValue placeholder="전압을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="220">220V</SelectItem>
<SelectItem value="380">380V</SelectItem>
</SelectContent>
</Select>
{errors.motorVoltage && (
<p className="text-xs text-red-500 mt-1">
{errors.motorVoltage.message}
</p>
)}
</div>
</div>
)}
{/* 체인 전용 필드 */}
{selectedCategory1 === 'chain' && (
<div className="md:col-span-2 grid grid-cols-2 gap-4 p-4 bg-yellow-50 rounded-lg border border-yellow-200">
<div>
<Label> *</Label>
<Select
value={chainSpec}
onValueChange={(value) => {
setChainSpec(value);
setValue('chainSpec', value);
}}
>
<SelectTrigger className={errors.chainSpec ? 'border-red-500' : ''}>
<SelectValue placeholder="규격을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="40">40</SelectItem>
<SelectItem value="50">50</SelectItem>
<SelectItem value="60">60</SelectItem>
<SelectItem value="80">80</SelectItem>
</SelectContent>
</Select>
{errors.chainSpec && (
<p className="text-xs text-red-500 mt-1">
{errors.chainSpec.message}
</p>
)}
</div>
<div>
<Label> ( ) *</Label>
<Input type="number" placeholder="예: 100" />
</div>
</div>
)}
{/* 품목명 선택 후에만 단위, 비고 표시 */}
{selectedCategory1 && (
<>
<div>
<Label>
<span className="text-red-500">*</span>
</Label>
<Select
value={partUnit}
onValueChange={(value) => {
setPartUnit(value);
setValue('unit', value);
}}
>
<SelectTrigger>
<SelectValue placeholder="단위를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="EA">EA ()</SelectItem>
<SelectItem value="M">M ()</SelectItem>
<SelectItem value="mm">mm ()</SelectItem>
<SelectItem value="KG">KG ()</SelectItem>
<SelectItem value="L">L ()</SelectItem>
<SelectItem value="SET">SET ()</SelectItem>
<SelectItem value="BOX">BOX ()</SelectItem>
<SelectItem value="ROLL">ROLL ()</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label></Label>
<Input
placeholder="비고 사항을 입력하세요"
{...register('note')}
/>
</div>
{/* 품목코드 자동생성 */}
<div className="md:col-span-2">
<Label> ()</Label>
<Input
value=""
disabled
className="bg-muted text-muted-foreground"
placeholder="품목명과 규격이 입력되면 자동으로 생성됩니다"
/>
<p className="text-xs text-muted-foreground mt-1">
* '품목명-규격'
</p>
</div>
{/* 품목 상태 */}
<div className="md:col-span-2">
<Label> </Label>
<Select
value={partStatus}
onValueChange={(value) => {
setPartStatus(value);
setValue('isActive', value === 'true');
}}
>
<SelectTrigger>
<SelectValue placeholder="품목 상태" />
</SelectTrigger>
<SelectContent>
<SelectItem value="true"></SelectItem>
<SelectItem value="false"></SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground mt-1">
*
</p>
</div>
{/* 부품구성 (BOM) 필요 여부 */}
<div className="md:col-span-2 pt-6 mt-6 border-t">
<div className="flex items-center space-x-2">
<Checkbox
id="needsBOM-purchased"
checked={needsBOM}
onCheckedChange={(checked) => setNeedsBOM(checked as boolean)}
/>
<Label htmlFor="needsBOM-purchased" className="cursor-pointer">
(BOM)
</Label>
</div>
<p className="text-xs text-muted-foreground mt-1 ml-6">
*
</p>
</div>
</>
)}
</>
);
}

View File

@@ -0,0 +1,12 @@
/**
* 부품 유형별 폼 컴포넌트 export
*/
export { default as AssemblyPartForm } from './AssemblyPartForm';
export type { AssemblyPartFormProps } from './AssemblyPartForm';
export { default as BendingPartForm } from './BendingPartForm';
export type { BendingPartFormProps } from './BendingPartForm';
export { default as PurchasedPartForm } from './PurchasedPartForm';
export type { PurchasedPartFormProps } from './PurchasedPartForm';