Merge branch 'ksh'

This commit is contained in:
SeongHyun Kim
2025-11-26 11:04:04 +09:00
28 changed files with 220 additions and 525 deletions

View File

@@ -64,6 +64,9 @@ export function OrderRegistrationModal({
// 선택된 품목 목록
const [selectedItems, setSelectedItems] = useState<any[]>([]);
// 납기일 일괄 적용 플래그 (딱 한 번만 실행)
const [isDeliveryDateApplied, setIsDeliveryDateApplied] = useState(false);
// 저장 중
const [isSaving, setIsSaving] = useState(false);
@@ -158,6 +161,45 @@ export function OrderRegistrationModal({
hsCode: "",
});
setSelectedItems([]);
setIsDeliveryDateApplied(false); // 플래그 초기화
};
// 품목 목록 변경 핸들러 (납기일 일괄 적용 로직 포함)
const handleItemsChange = (newItems: any[]) => {
// 1⃣ 플래그가 이미 true면 그냥 업데이트만 (일괄 적용 완료 상태)
if (isDeliveryDateApplied) {
setSelectedItems(newItems);
return;
}
// 2⃣ 품목이 없으면 그냥 업데이트
if (newItems.length === 0) {
setSelectedItems(newItems);
return;
}
// 3⃣ 현재 상태: 납기일이 있는 행과 없는 행 개수 체크
const itemsWithDate = newItems.filter((item) => item.delivery_date);
const itemsWithoutDate = newItems.filter((item) => !item.delivery_date);
// 4⃣ 조건: 정확히 1개만 날짜가 있고, 나머지는 모두 비어있을 때 일괄 적용
if (itemsWithDate.length === 1 && itemsWithoutDate.length > 0) {
// 5⃣ 전체 일괄 적용
const selectedDate = itemsWithDate[0].delivery_date;
const updatedItems = newItems.map((item) => ({
...item,
delivery_date: selectedDate, // 모든 행에 동일한 납기일 적용
}));
setSelectedItems(updatedItems);
setIsDeliveryDateApplied(true); // 플래그 활성화 (다음부터는 일괄 적용 안 함)
console.log("✅ 납기일 일괄 적용 완료:", selectedDate);
console.log(` - 대상: ${itemsWithoutDate.length}개 행에 ${selectedDate} 적용`);
} else {
// 그냥 업데이트
setSelectedItems(newItems);
}
};
// 전체 금액 계산
@@ -338,7 +380,7 @@ export function OrderRegistrationModal({
<Label className="text-xs sm:text-sm"> </Label>
<OrderItemRepeaterTable
value={selectedItems}
onChange={setSelectedItems}
onChange={handleItemsChange}
/>
</div>

View File

@@ -316,6 +316,33 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
screenId: modalState.screenId,
});
// 🆕 날짜 필드 정규화 함수 (YYYY-MM-DD 형식으로 변환)
const normalizeDateField = (value: any): string | null => {
if (!value) return null;
// ISO 8601 형식 (2025-11-26T00:00:00.000Z) 또는 Date 객체
if (value instanceof Date || typeof value === "string") {
try {
const date = new Date(value);
if (isNaN(date.getTime())) return null;
// YYYY-MM-DD 형식으로 변환
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
} catch (error) {
console.warn("날짜 변환 실패:", value, error);
return null;
}
}
return null;
};
// 날짜 필드 목록
const dateFields = ["item_due_date", "delivery_date", "due_date", "order_date"];
let insertedCount = 0;
let updatedCount = 0;
let deletedCount = 0;
@@ -333,6 +360,17 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
delete insertData.id; // id는 자동 생성되므로 제거
// 🆕 날짜 필드 정규화 (YYYY-MM-DD 형식으로 변환)
dateFields.forEach((fieldName) => {
if (insertData[fieldName]) {
const normalizedDate = normalizeDateField(insertData[fieldName]);
if (normalizedDate) {
insertData[fieldName] = normalizedDate;
console.log(`📅 [날짜 정규화] ${fieldName}: ${currentData[fieldName]}${normalizedDate}`);
}
}
});
// 🆕 groupByColumns의 값을 강제로 포함 (order_no 등)
if (modalState.groupByColumns && modalState.groupByColumns.length > 0) {
modalState.groupByColumns.forEach((colName) => {
@@ -348,23 +386,32 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
// 🆕 공통 필드 추가 (거래처, 담당자, 납품처, 메모 등)
// formData에서 품목별 필드가 아닌 공통 필드를 복사
const commonFields = [
'partner_id', // 거래처
'manager_id', // 담당자
'delivery_partner_id', // 납품처
'delivery_address', // 납품장소
'memo', // 메모
'order_date', // 주문일
'due_date', // 납기일
'shipping_method', // 배송방법
'status', // 상태
'sales_type', // 영업유형
"partner_id", // 거래처
"manager_id", // 담당자
"delivery_partner_id", // 납품처
"delivery_address", // 납품장소
"memo", // 메모
"order_date", // 주문일
"due_date", // 납기일
"shipping_method", // 배송방법
"status", // 상태
"sales_type", // 영업유형
];
commonFields.forEach((fieldName) => {
// formData에 값이 있으면 추가
if (formData[fieldName] !== undefined && formData[fieldName] !== null) {
insertData[fieldName] = formData[fieldName];
console.log(`🔗 [공통 필드] ${fieldName} 값 추가:`, formData[fieldName]);
// 날짜 필드인 경우 정규화
if (dateFields.includes(fieldName)) {
const normalizedDate = normalizeDateField(formData[fieldName]);
if (normalizedDate) {
insertData[fieldName] = normalizedDate;
console.log(`🔗 [공통 필드 - 날짜] ${fieldName} 값 추가:`, normalizedDate);
}
} else {
insertData[fieldName] = formData[fieldName];
console.log(`🔗 [공통 필드] ${fieldName} 값 추가:`, formData[fieldName]);
}
}
});
@@ -404,8 +451,15 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
}
// 🆕 값 정규화 함수 (타입 통일)
const normalizeValue = (val: any): any => {
const normalizeValue = (val: any, fieldName?: string): any => {
if (val === null || val === undefined || val === "") return null;
// 날짜 필드인 경우 YYYY-MM-DD 형식으로 정규화
if (fieldName && dateFields.includes(fieldName)) {
const normalizedDate = normalizeDateField(val);
return normalizedDate;
}
if (typeof val === "string" && !isNaN(Number(val))) {
// 숫자로 변환 가능한 문자열은 숫자로
return Number(val);
@@ -422,13 +476,14 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
}
// 🆕 타입 정규화 후 비교
const currentValue = normalizeValue(currentData[key]);
const originalValue = normalizeValue(originalItemData[key]);
const currentValue = normalizeValue(currentData[key], key);
const originalValue = normalizeValue(originalItemData[key], key);
// 값이 변경된 경우만 포함
if (currentValue !== originalValue) {
console.log(`🔍 [품목 수정 감지] ${key}: ${originalValue}${currentValue}`);
changedData[key] = currentData[key]; // 원본 값 사용 (문자열 그대로)
// 날짜 필드는 정규화된 값 사용, 나머지는 원본 값 사용
changedData[key] = dateFields.includes(key) ? currentValue : currentData[key];
}
});

View File

@@ -122,10 +122,6 @@ const ResizableDialogContent = React.forwardRef<
// 1순위: userStyle에서 크기 추출 (화면관리에서 지정한 크기 - 항상 초기값으로 사용)
if (userStyle) {
console.log("🔍 userStyle 감지:", userStyle);
console.log("🔍 userStyle.width 타입:", typeof userStyle.width, "값:", userStyle.width);
console.log("🔍 userStyle.height 타입:", typeof userStyle.height, "값:", userStyle.height);
const styleWidth = typeof userStyle.width === 'string'
? parseInt(userStyle.width)
: userStyle.width;
@@ -133,31 +129,15 @@ const ResizableDialogContent = React.forwardRef<
? parseInt(userStyle.height)
: userStyle.height;
console.log("📏 파싱된 크기:", {
styleWidth,
styleHeight,
"styleWidth truthy?": !!styleWidth,
"styleHeight truthy?": !!styleHeight,
minWidth,
maxWidth,
minHeight,
maxHeight
});
if (styleWidth && styleHeight) {
const finalSize = {
width: Math.max(minWidth, Math.min(maxWidth, styleWidth)),
height: Math.max(minHeight, Math.min(maxHeight, styleHeight)),
};
console.log("✅ userStyle 크기 사용:", finalSize);
return finalSize;
} else {
console.log("❌ styleWidth 또는 styleHeight가 falsy:", { styleWidth, styleHeight });
}
}
console.log("⚠️ userStyle 없음, defaultWidth/defaultHeight 사용:", { defaultWidth, defaultHeight });
// 2순위: 현재 렌더링된 크기 사용 (주석처리 - 모달이 열린 후 늘어나는 현상 방지)
// if (contentRef.current) {
// const rect = contentRef.current.getBoundingClientRect();
@@ -209,7 +189,6 @@ const ResizableDialogContent = React.forwardRef<
// 사용자가 리사이징한 크기 우선
setSize({ width: savedSize.width, height: savedSize.height });
setUserResized(true);
console.log("✅ 사용자 리사이징 크기 적용:", savedSize);
} else if (userStyle && userStyle.width && userStyle.height) {
// 화면관리에서 설정한 크기
const styleWidth = typeof userStyle.width === 'string'
@@ -224,7 +203,6 @@ const ResizableDialogContent = React.forwardRef<
width: Math.max(minWidth, Math.min(maxWidth, styleWidth)),
height: Math.max(minHeight, Math.min(maxHeight, styleHeight)),
};
console.log("🔄 userStyle 크기 적용:", newSize);
setSize(newSize);
}
}