refactor: 품목관리 시스템 리팩토링 및 Sales 페이지 추가
DynamicItemForm 개선: - 품목코드 자동생성 기능 추가 - 조건부 표시 로직 개선 - 불필요한 컴포넌트 정리 (DynamicField, DynamicSection 등) - 타입 시스템 단순화 새로운 기능: - Sales 페이지 마이그레이션 (견적관리, 거래처관리) - 공통 컴포넌트 추가 (atoms, molecules, organisms, templates) 문서화: - 구현 문서 및 참조 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -95,7 +95,8 @@ export function ConditionalDisplayUI({
|
||||
// 신규 ItemField 타입: id는 number
|
||||
const availableFields = selectedSectionForField?.fields?.filter(f => f.id !== editingFieldId) || [];
|
||||
// 신규 ItemSection 타입: section_type은 'BASIC' | 'BOM' | 'CUSTOM'
|
||||
const availableSections = selectedPage?.sections.filter(s => s.section_type !== 'BOM') || [];
|
||||
// 2025-12-03: BOM 섹션도 조건부 표시 대상으로 포함 (체크박스 → BOM 섹션 연결용)
|
||||
const availableSections = selectedPage?.sections || [];
|
||||
|
||||
return (
|
||||
<div className="border-t pt-4 space-y-3">
|
||||
|
||||
@@ -21,7 +21,8 @@ const INPUT_TYPE_OPTIONS = [
|
||||
interface DraggableFieldProps {
|
||||
field: ItemField;
|
||||
index: number;
|
||||
moveField: (dragIndex: number, hoverIndex: number) => void;
|
||||
// 2025-12-03: ID 기반으로 변경 (index는 stale 문제 발생)
|
||||
moveField: (dragFieldId: number, hoverFieldId: number) => void;
|
||||
onDelete: () => void;
|
||||
onEdit?: () => void;
|
||||
}
|
||||
@@ -30,8 +31,10 @@ export function DraggableField({ field, index, moveField, onDelete, onEdit }: Dr
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
const handleDragStart = (e: React.DragEvent) => {
|
||||
e.stopPropagation(); // 2025-12-03: 섹션 드래그 이벤트와 충돌 방지
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/plain', JSON.stringify({ index, id: field.id }));
|
||||
// 2025-12-03: 타입 구분 추가 (섹션/필드 드래그 구분)
|
||||
e.dataTransfer.setData('application/json', JSON.stringify({ type: 'field', id: field.id }));
|
||||
setIsDragging(true);
|
||||
};
|
||||
|
||||
@@ -41,18 +44,25 @@ export function DraggableField({ field, index, moveField, onDelete, onEdit }: Dr
|
||||
|
||||
const handleDragOver = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // 2025-12-03: 이벤트 버블링 방지
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
};
|
||||
|
||||
const handleDrop = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // 2025-12-03: 이벤트 버블링 방지
|
||||
try {
|
||||
const data = JSON.parse(e.dataTransfer.getData('text/plain'));
|
||||
if (data.index !== index) {
|
||||
moveField(data.index, index);
|
||||
const data = JSON.parse(e.dataTransfer.getData('application/json'));
|
||||
// 2025-12-03: 타입 체크 - 필드 드래그만 처리
|
||||
if (data.type !== 'field') {
|
||||
console.log('[DraggableField] 필드 드래그가 아님, 무시:', data);
|
||||
return;
|
||||
}
|
||||
if (data.id !== field.id) {
|
||||
moveField(data.id, field.id);
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore
|
||||
// Ignore - 다른 타입의 드래그 데이터
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ export function DraggableSection({
|
||||
|
||||
const handleDragStart = (e: React.DragEvent) => {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/plain', JSON.stringify({ index, id: section.id }));
|
||||
// 2025-12-03: 타입 구분 추가 (섹션/필드 드래그 구분)
|
||||
e.dataTransfer.setData('application/json', JSON.stringify({ type: 'section', index, id: section.id }));
|
||||
setIsDragging(true);
|
||||
};
|
||||
|
||||
@@ -58,12 +59,16 @@ export function DraggableSection({
|
||||
const handleDrop = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const data = JSON.parse(e.dataTransfer.getData('text/plain'));
|
||||
const data = JSON.parse(e.dataTransfer.getData('application/json'));
|
||||
// 2025-12-03: 타입 체크 - 섹션 드래그만 처리
|
||||
if (data.type !== 'section') {
|
||||
return;
|
||||
}
|
||||
if (data.index !== index) {
|
||||
moveSection(data.index, index);
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore
|
||||
// Ignore - 다른 타입의 드래그 데이터
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
const ITEM_TYPE_OPTIONS = [
|
||||
{ value: 'FG', label: '제품 (FG)' },
|
||||
{ value: 'PT', label: '부품 (PT)' },
|
||||
{ value: 'SM', label: '반제품 (SM)' },
|
||||
{ value: 'SM', label: '부자재 (SM)' },
|
||||
{ value: 'RM', label: '원자재 (RM)' },
|
||||
{ value: 'CS', label: '소모품 (CS)' },
|
||||
];
|
||||
|
||||
@@ -145,15 +145,15 @@ export function useFieldManagement(): UseFieldManagementReturn {
|
||||
}
|
||||
|
||||
// 조건부 표시 설정
|
||||
// 2025-12-02: ConditionalDisplayUI는 field/section 모두 newFieldConditionFields에 저장
|
||||
// - field 모드: fieldConditions[].targetFieldIds에 필드 ID 저장
|
||||
// - section 모드: fieldConditions[].targetSectionIds에 섹션 ID 저장
|
||||
const displayCondition: FieldDisplayCondition | undefined = newFieldConditionEnabled
|
||||
? {
|
||||
targetType: newFieldConditionTargetType,
|
||||
fieldConditions: newFieldConditionTargetType === 'field' && newFieldConditionFields.length > 0
|
||||
fieldConditions: newFieldConditionFields.length > 0
|
||||
? newFieldConditionFields
|
||||
: undefined,
|
||||
sectionIds: newFieldConditionTargetType === 'section' && newFieldConditionSections.length > 0
|
||||
? newFieldConditionSections
|
||||
: undefined
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ interface HierarchyTabProps {
|
||||
updateSection: (sectionId: number, updates: Partial<ItemSection>) => Promise<void>;
|
||||
deleteField: (pageId: string, sectionId: string, fieldId: string) => void; // 2025-11-27: 연결 해제로 변경 (삭제 아님, 항목 탭에 유지)
|
||||
handleEditField: (sectionId: string, field: any) => void;
|
||||
moveField: (sectionId: number, dragIndex: number, hoverIndex: number) => void;
|
||||
// 2025-12-03: ID 기반으로 변경 (index stale 문제 해결)
|
||||
moveField: (sectionId: number, dragFieldId: number, hoverFieldId: number) => void | Promise<void>;
|
||||
// 2025-11-26 추가: 섹션/필드 불러오기
|
||||
setIsImportSectionDialogOpen?: (open: boolean) => void;
|
||||
setIsImportFieldDialogOpen?: (open: boolean) => void;
|
||||
@@ -441,10 +442,10 @@ export function HierarchyTab({
|
||||
.sort((a, b) => (a.order_no ?? 0) - (b.order_no ?? 0))
|
||||
.map((field, fieldIndex) => (
|
||||
<DraggableField
|
||||
key={`field-${field.id}-${fieldIndex}`}
|
||||
key={field.id}
|
||||
field={field}
|
||||
index={fieldIndex}
|
||||
moveField={(dragIndex, hoverIndex) => moveField(section.id, dragIndex, hoverIndex)}
|
||||
moveField={(dragFieldId, hoverFieldId) => moveField(section.id, dragFieldId, hoverFieldId)}
|
||||
onDelete={() => {
|
||||
if (confirm('이 항목을 섹션에서 연결 해제하시겠습니까?\n(항목 데이터는 항목 탭에 유지됩니다)')) {
|
||||
deleteField(String(selectedPage.id), String(section.id), String(field.id));
|
||||
|
||||
Reference in New Issue
Block a user