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:
354
src/components/items/ItemForm/forms/MaterialForm.tsx
Normal file
354
src/components/items/ItemForm/forms/MaterialForm.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
273
src/components/items/ItemForm/forms/PartForm.tsx
Normal file
273
src/components/items/ItemForm/forms/PartForm.tsx
Normal 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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
337
src/components/items/ItemForm/forms/ProductForm.tsx
Normal file
337
src/components/items/ItemForm/forms/ProductForm.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
src/components/items/ItemForm/forms/index.ts
Normal file
7
src/components/items/ItemForm/forms/index.ts
Normal 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';
|
||||
336
src/components/items/ItemForm/forms/parts/AssemblyPartForm.tsx
Normal file
336
src/components/items/ItemForm/forms/parts/AssemblyPartForm.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
302
src/components/items/ItemForm/forms/parts/BendingPartForm.tsx
Normal file
302
src/components/items/ItemForm/forms/parts/BendingPartForm.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
318
src/components/items/ItemForm/forms/parts/PurchasedPartForm.tsx
Normal file
318
src/components/items/ItemForm/forms/parts/PurchasedPartForm.tsx
Normal 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>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
12
src/components/items/ItemForm/forms/parts/index.ts
Normal file
12
src/components/items/ItemForm/forms/parts/index.ts
Normal 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';
|
||||
Reference in New Issue
Block a user