Merge branch 'ycshin-node' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node
This commit is contained in:
@@ -13,7 +13,6 @@ import { dynamicFormApi, DynamicFormData } from "@/lib/api/dynamicForm";
|
||||
import { getTableColumns, ColumnTypeInfo } from "@/lib/api/tableManagement";
|
||||
import { ComponentData } from "@/lib/types/screen";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
|
||||
interface SaveModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
@@ -124,9 +123,7 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
||||
// 테이블 타입관리의 NOT NULL 설정 기반으로 필수 여부 판단
|
||||
const isColumnRequired = (columnName: string): boolean => {
|
||||
if (!columnName || tableColumnsInfo.length === 0) return false;
|
||||
const colInfo = tableColumnsInfo.find(
|
||||
(c) => c.columnName.toLowerCase() === columnName.toLowerCase()
|
||||
);
|
||||
const colInfo = tableColumnsInfo.find((c) => c.columnName.toLowerCase() === columnName.toLowerCase());
|
||||
if (!colInfo) return false;
|
||||
// is_nullable가 "NO"이면 필수
|
||||
return colInfo.isNullable === "NO" || colInfo.isNullable === "N";
|
||||
@@ -141,8 +138,8 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
||||
const label = component.label || component.style?.label || columnName;
|
||||
|
||||
// 기존 required 속성 (화면 디자이너에서 수동 설정한 것)
|
||||
const manualRequired =
|
||||
component.required === true ||
|
||||
const manualRequired =
|
||||
component.required === true ||
|
||||
component.style?.required === true ||
|
||||
component.componentConfig?.required === true;
|
||||
|
||||
@@ -176,13 +173,6 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 필수 항목 검증
|
||||
const validation = validateRequiredFields();
|
||||
if (!validation.isValid) {
|
||||
toast.error(`필수 항목을 입력해주세요: ${validation.missingFields.join(", ")}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsSaving(true);
|
||||
|
||||
@@ -210,7 +200,7 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
||||
// 🆕 자동으로 작성자 정보 추가 (user.userId가 확실히 있음)
|
||||
const writerValue = user.userId;
|
||||
const companyCodeValue = user.companyCode || "";
|
||||
|
||||
|
||||
console.log("👤 현재 사용자 정보:", {
|
||||
userId: user.userId,
|
||||
userName: userName,
|
||||
@@ -255,11 +245,11 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
||||
|
||||
if (result.success) {
|
||||
const masterRecordId = result.data?.id || dataToSave.id;
|
||||
|
||||
|
||||
// 🆕 리피터 데이터 저장 이벤트 발생 (V2Repeater 컴포넌트가 리스닝)
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("repeaterSave", {
|
||||
detail: {
|
||||
detail: {
|
||||
parentId: masterRecordId,
|
||||
masterRecordId,
|
||||
mainFormData: dataToSave,
|
||||
@@ -268,7 +258,7 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
||||
}),
|
||||
);
|
||||
console.log("📋 [SaveModal] repeaterSave 이벤트 발생:", { masterRecordId, tableName });
|
||||
|
||||
|
||||
// ✅ 저장 성공
|
||||
toast.success(initialData ? "수정되었습니다!" : "저장되었습니다!");
|
||||
|
||||
@@ -300,29 +290,29 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
||||
const calculateDynamicSize = () => {
|
||||
if (!components.length) return { width: 800, height: 600 };
|
||||
|
||||
const maxX = Math.max(...components.map((c) => {
|
||||
const x = c.position?.x || 0;
|
||||
const width = typeof c.size?.width === 'number'
|
||||
? c.size.width
|
||||
: parseInt(String(c.size?.width || 200), 10);
|
||||
return x + width;
|
||||
}));
|
||||
|
||||
const maxY = Math.max(...components.map((c) => {
|
||||
const y = c.position?.y || 0;
|
||||
const height = typeof c.size?.height === 'number'
|
||||
? c.size.height
|
||||
: parseInt(String(c.size?.height || 40), 10);
|
||||
return y + height;
|
||||
}));
|
||||
const maxX = Math.max(
|
||||
...components.map((c) => {
|
||||
const x = c.position?.x || 0;
|
||||
const width = typeof c.size?.width === "number" ? c.size.width : parseInt(String(c.size?.width || 200), 10);
|
||||
return x + width;
|
||||
}),
|
||||
);
|
||||
|
||||
const maxY = Math.max(
|
||||
...components.map((c) => {
|
||||
const y = c.position?.y || 0;
|
||||
const height = typeof c.size?.height === "number" ? c.size.height : parseInt(String(c.size?.height || 40), 10);
|
||||
return y + height;
|
||||
}),
|
||||
);
|
||||
|
||||
// 컨텐츠 영역 크기 (화면관리 설정 크기)
|
||||
const contentWidth = Math.max(maxX, 400);
|
||||
const contentHeight = Math.max(maxY, 300);
|
||||
|
||||
|
||||
// 실제 모달 크기 = 컨텐츠 + 헤더
|
||||
const headerHeight = 60; // DialogHeader
|
||||
|
||||
|
||||
return {
|
||||
width: contentWidth,
|
||||
height: contentHeight + headerHeight, // 헤더 높이 포함
|
||||
@@ -340,9 +330,9 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
||||
minWidth: "400px",
|
||||
minHeight: "300px",
|
||||
}}
|
||||
className="gap-0 p-0 max-w-none"
|
||||
className="max-w-none gap-0 p-0"
|
||||
>
|
||||
<DialogHeader className="border-b px-6 py-4 flex-shrink-0">
|
||||
<DialogHeader className="flex-shrink-0 border-b px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<DialogTitle className="text-lg font-semibold">{initialData ? "데이터 수정" : "데이터 등록"}</DialogTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -366,7 +356,7 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="overflow-auto p-6 flex-1">
|
||||
<div className="flex-1 overflow-auto p-6">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="text-muted-foreground h-8 w-8 animate-spin" />
|
||||
@@ -384,90 +374,92 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
||||
<div className="relative" style={{ width: `${dynamicSize.width}px`, height: `${dynamicSize.height}px` }}>
|
||||
{components.map((component, index) => {
|
||||
// ✅ 격자 시스템 잔재 제거: size의 픽셀 값만 사용
|
||||
const widthPx = typeof component.size?.width === 'number'
|
||||
? component.size.width
|
||||
: parseInt(String(component.size?.width || 200), 10);
|
||||
const heightPx = typeof component.size?.height === 'number'
|
||||
? component.size.height
|
||||
: parseInt(String(component.size?.height || 40), 10);
|
||||
const widthPx =
|
||||
typeof component.size?.width === "number"
|
||||
? component.size.width
|
||||
: parseInt(String(component.size?.width || 200), 10);
|
||||
const heightPx =
|
||||
typeof component.size?.height === "number"
|
||||
? component.size.height
|
||||
: parseInt(String(component.size?.height || 40), 10);
|
||||
|
||||
// 디버깅: 실제 크기 확인
|
||||
if (index === 0) {
|
||||
console.log('🔍 SaveModal 컴포넌트 크기:', {
|
||||
console.log("🔍 SaveModal 컴포넌트 크기:", {
|
||||
componentId: component.id,
|
||||
'size.width (원본)': component.size?.width,
|
||||
'size.width 타입': typeof component.size?.width,
|
||||
'widthPx (계산)': widthPx,
|
||||
'style.width': component.style?.width,
|
||||
"size.width (원본)": component.size?.width,
|
||||
"size.width 타입": typeof component.size?.width,
|
||||
"widthPx (계산)": widthPx,
|
||||
"style.width": component.style?.width,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={component.id}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: component.position?.y || 0,
|
||||
left: component.position?.x || 0,
|
||||
width: `${widthPx}px`, // ✅ 픽셀 단위 강제
|
||||
height: `${heightPx}px`, // ✅ 픽셀 단위 강제
|
||||
zIndex: component.position?.z || 1000 + index,
|
||||
}}
|
||||
>
|
||||
{component.type === "widget" ? (
|
||||
<InteractiveScreenViewer
|
||||
component={component}
|
||||
allComponents={components}
|
||||
formData={formData}
|
||||
onFormDataChange={(fieldName, value) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[fieldName]: value,
|
||||
}));
|
||||
}}
|
||||
hideLabel={false}
|
||||
menuObjid={menuObjid}
|
||||
tableColumns={tableColumnsInfo as any}
|
||||
/>
|
||||
) : (
|
||||
<DynamicComponentRenderer
|
||||
component={{
|
||||
...component,
|
||||
style: {
|
||||
...component.style,
|
||||
labelDisplay: true,
|
||||
},
|
||||
}}
|
||||
screenId={screenId}
|
||||
tableName={screenData.tableName}
|
||||
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달 (카테고리 스코프용)
|
||||
userId={user?.userId} // ✅ 사용자 ID 전달
|
||||
userName={user?.userName} // ✅ 사용자 이름 전달
|
||||
companyCode={user?.companyCode} // ✅ 회사 코드 전달
|
||||
formData={formData}
|
||||
originalData={originalData}
|
||||
onFormDataChange={(fieldName, value) => {
|
||||
console.log("📝 SaveModal - formData 변경:", {
|
||||
fieldName,
|
||||
value,
|
||||
componentType: component.type,
|
||||
componentId: component.id,
|
||||
});
|
||||
setFormData((prev) => {
|
||||
const newData = {
|
||||
<div
|
||||
key={component.id}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: component.position?.y || 0,
|
||||
left: component.position?.x || 0,
|
||||
width: `${widthPx}px`, // ✅ 픽셀 단위 강제
|
||||
height: `${heightPx}px`, // ✅ 픽셀 단위 강제
|
||||
zIndex: component.position?.z || 1000 + index,
|
||||
}}
|
||||
>
|
||||
{component.type === "widget" ? (
|
||||
<InteractiveScreenViewer
|
||||
component={component}
|
||||
allComponents={components}
|
||||
formData={formData}
|
||||
onFormDataChange={(fieldName, value) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[fieldName]: value,
|
||||
};
|
||||
console.log("📦 새 formData:", newData);
|
||||
return newData;
|
||||
});
|
||||
}}
|
||||
mode="edit"
|
||||
isInModal={true}
|
||||
isInteractive={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}));
|
||||
}}
|
||||
hideLabel={false}
|
||||
menuObjid={menuObjid}
|
||||
tableColumns={tableColumnsInfo as any}
|
||||
/>
|
||||
) : (
|
||||
<DynamicComponentRenderer
|
||||
component={{
|
||||
...component,
|
||||
style: {
|
||||
...component.style,
|
||||
labelDisplay: true,
|
||||
},
|
||||
}}
|
||||
screenId={screenId}
|
||||
tableName={screenData.tableName}
|
||||
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달 (카테고리 스코프용)
|
||||
userId={user?.userId} // ✅ 사용자 ID 전달
|
||||
userName={user?.userName} // ✅ 사용자 이름 전달
|
||||
companyCode={user?.companyCode} // ✅ 회사 코드 전달
|
||||
formData={formData}
|
||||
originalData={originalData}
|
||||
onFormDataChange={(fieldName, value) => {
|
||||
console.log("📝 SaveModal - formData 변경:", {
|
||||
fieldName,
|
||||
value,
|
||||
componentType: component.type,
|
||||
componentId: component.id,
|
||||
});
|
||||
setFormData((prev) => {
|
||||
const newData = {
|
||||
...prev,
|
||||
[fieldName]: value,
|
||||
};
|
||||
console.log("📦 새 formData:", newData);
|
||||
return newData;
|
||||
});
|
||||
}}
|
||||
mode="edit"
|
||||
isInModal={true}
|
||||
isInteractive={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user