make: RepeaterFieldGroup 컴포넌트
- 하위 데이터 조회 연동 방식 개선 - 필드 정의 레벨에서 subDataSource 설정 추가 - 필드별 숨김(isHidden) 옵션 추가 - 기존 fieldMappings 방식 제거, 필드별 연동으로 변경 _repeaterFieldsConfig 메타데이터로 설정 전달 : "이 필드들의 하위 조회 결과에서 값 가져와서 추가로 저장해줘"라는 주문서 역할
This commit is contained in:
parent
b62a0b7e3b
commit
585febfb52
|
|
@ -908,6 +908,10 @@ export const RepeaterInput: React.FC<RepeaterInputProps> = ({
|
||||||
// 하위 데이터 조회 설정이 있으면 연결 컬럼 찾기
|
// 하위 데이터 조회 설정이 있으면 연결 컬럼 찾기
|
||||||
const linkColumn = subDataLookup?.lookup?.linkColumn;
|
const linkColumn = subDataLookup?.lookup?.linkColumn;
|
||||||
|
|
||||||
|
// hidden이 아닌 필드만 표시
|
||||||
|
// isHidden이 true이거나 displayMode가 hidden인 필드는 제외 (하위 호환성 유지)
|
||||||
|
const visibleFields = fields.filter((f) => !f.isHidden && f.displayMode !== "hidden");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-card">
|
<div className="bg-card">
|
||||||
<Table>
|
<Table>
|
||||||
|
|
@ -919,7 +923,7 @@ export const RepeaterInput: React.FC<RepeaterInputProps> = ({
|
||||||
{allowReorder && (
|
{allowReorder && (
|
||||||
<TableHead className="h-10 w-10 px-2.5 py-2 text-center text-sm font-semibold"></TableHead>
|
<TableHead className="h-10 w-10 px-2.5 py-2 text-center text-sm font-semibold"></TableHead>
|
||||||
)}
|
)}
|
||||||
{fields.map((field) => (
|
{visibleFields.map((field) => (
|
||||||
<TableHead key={field.name} className="h-10 px-2.5 py-2 text-sm font-semibold">
|
<TableHead key={field.name} className="h-10 px-2.5 py-2 text-sm font-semibold">
|
||||||
{field.label}
|
{field.label}
|
||||||
{field.required && <span className="text-destructive ml-1">*</span>}
|
{field.required && <span className="text-destructive ml-1">*</span>}
|
||||||
|
|
@ -958,8 +962,8 @@ export const RepeaterInput: React.FC<RepeaterInputProps> = ({
|
||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 필드들 */}
|
{/* 필드들 (hidden 제외) */}
|
||||||
{fields.map((field) => (
|
{visibleFields.map((field) => (
|
||||||
<TableCell key={field.name} className="h-12 px-2.5 py-2">
|
<TableCell key={field.name} className="h-12 px-2.5 py-2">
|
||||||
{renderField(field, itemIndex, item[field.name])}
|
{renderField(field, itemIndex, item[field.name])}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
@ -987,7 +991,7 @@ export const RepeaterInput: React.FC<RepeaterInputProps> = ({
|
||||||
<TableRow className="bg-gray-50/50">
|
<TableRow className="bg-gray-50/50">
|
||||||
<TableCell
|
<TableCell
|
||||||
colSpan={
|
colSpan={
|
||||||
fields.length + (showIndex ? 1 : 0) + (allowReorder && !readonly && !disabled ? 1 : 0) + 1
|
visibleFields.length + (showIndex ? 1 : 0) + (allowReorder && !readonly && !disabled ? 1 : 0) + 1
|
||||||
}
|
}
|
||||||
className="px-2.5 py-2"
|
className="px-2.5 py-2"
|
||||||
>
|
>
|
||||||
|
|
@ -1017,6 +1021,10 @@ export const RepeaterInput: React.FC<RepeaterInputProps> = ({
|
||||||
// 하위 데이터 조회 설정이 있으면 연결 컬럼 찾기
|
// 하위 데이터 조회 설정이 있으면 연결 컬럼 찾기
|
||||||
const linkColumn = subDataLookup?.lookup?.linkColumn;
|
const linkColumn = subDataLookup?.lookup?.linkColumn;
|
||||||
|
|
||||||
|
// hidden이 아닌 필드만 표시
|
||||||
|
// isHidden이 true이거나 displayMode가 hidden인 필드는 제외 (하위 호환성 유지)
|
||||||
|
const visibleFields = fields.filter((f) => !f.isHidden && f.displayMode !== "hidden");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{items.map((item, itemIndex) => {
|
{items.map((item, itemIndex) => {
|
||||||
|
|
@ -1084,7 +1092,7 @@ export const RepeaterInput: React.FC<RepeaterInputProps> = ({
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className={getFieldsLayoutClass()}>
|
<div className={getFieldsLayoutClass()}>
|
||||||
{fields.map((field) => (
|
{visibleFields.map((field) => (
|
||||||
<div key={field.name} className="space-y-1" style={{ width: field.width }}>
|
<div key={field.name} className="space-y-1" style={{ width: field.width }}>
|
||||||
<label className="text-foreground text-sm font-medium">
|
<label className="text-foreground text-sm font-medium">
|
||||||
{field.label}
|
{field.label}
|
||||||
|
|
|
||||||
|
|
@ -360,32 +360,25 @@ export const RepeaterConfigPanel: React.FC<RepeaterConfigPanelProps> = ({
|
||||||
const handleDisplayColumnToggleWithOrder = (columnName: string, checked: boolean) => {
|
const handleDisplayColumnToggleWithOrder = (columnName: string, checked: boolean) => {
|
||||||
const currentColumns = config.subDataLookup?.lookup?.displayColumns || [];
|
const currentColumns = config.subDataLookup?.lookup?.displayColumns || [];
|
||||||
const currentOrder = config.subDataLookup?.lookup?.columnOrder || [];
|
const currentOrder = config.subDataLookup?.lookup?.columnOrder || [];
|
||||||
const currentMappings = config.subDataLookup?.lookup?.fieldMappings || [];
|
|
||||||
|
|
||||||
let newColumns: string[];
|
let newColumns: string[];
|
||||||
let newOrder: string[];
|
let newOrder: string[];
|
||||||
let newMappings: { sourceColumn: string; targetField: string }[];
|
|
||||||
|
|
||||||
if (checked) {
|
if (checked) {
|
||||||
newColumns = [...currentColumns, columnName];
|
newColumns = [...currentColumns, columnName];
|
||||||
newOrder = [...currentOrder, columnName];
|
newOrder = [...currentOrder, columnName];
|
||||||
// 기본 매핑 추가: 동일한 컬럼명이 targetTable에 있으면 자동 매핑, 없으면 빈 문자열
|
|
||||||
const targetColumn = tableColumns.find((c) => c.columnName === columnName);
|
|
||||||
newMappings = [...currentMappings, { sourceColumn: columnName, targetField: targetColumn ? columnName : "" }];
|
|
||||||
} else {
|
} else {
|
||||||
newColumns = currentColumns.filter((c) => c !== columnName);
|
newColumns = currentColumns.filter((c) => c !== columnName);
|
||||||
newOrder = currentOrder.filter((c) => c !== columnName);
|
newOrder = currentOrder.filter((c) => c !== columnName);
|
||||||
newMappings = currentMappings.filter((m) => m.sourceColumn !== columnName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// displayColumns, columnOrder, fieldMappings 함께 업데이트
|
// displayColumns, columnOrder 함께 업데이트
|
||||||
const newConfig = { ...config.subDataLookup } as SubDataLookupConfig;
|
const newConfig = { ...config.subDataLookup } as SubDataLookupConfig;
|
||||||
if (!newConfig.lookup) {
|
if (!newConfig.lookup) {
|
||||||
newConfig.lookup = { tableName: "", linkColumn: "", displayColumns: [] };
|
newConfig.lookup = { tableName: "", linkColumn: "", displayColumns: [] };
|
||||||
}
|
}
|
||||||
newConfig.lookup.displayColumns = newColumns;
|
newConfig.lookup.displayColumns = newColumns;
|
||||||
newConfig.lookup.columnOrder = newOrder;
|
newConfig.lookup.columnOrder = newOrder;
|
||||||
newConfig.lookup.fieldMappings = newMappings;
|
|
||||||
|
|
||||||
onChange({
|
onChange({
|
||||||
...config,
|
...config,
|
||||||
|
|
@ -393,28 +386,6 @@ export const RepeaterConfigPanel: React.FC<RepeaterConfigPanelProps> = ({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 필드 매핑 변경 핸들러
|
|
||||||
const handleFieldMappingChange = (sourceColumn: string, targetField: string) => {
|
|
||||||
const currentMappings = config.subDataLookup?.lookup?.fieldMappings || [];
|
|
||||||
const existingIndex = currentMappings.findIndex((m) => m.sourceColumn === sourceColumn);
|
|
||||||
|
|
||||||
let newMappings: { sourceColumn: string; targetField: string }[];
|
|
||||||
if (existingIndex >= 0) {
|
|
||||||
newMappings = [...currentMappings];
|
|
||||||
newMappings[existingIndex] = { sourceColumn, targetField };
|
|
||||||
} else {
|
|
||||||
newMappings = [...currentMappings, { sourceColumn, targetField }];
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubDataLookupChange("lookup.fieldMappings", newMappings);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 특정 컬럼의 현재 매핑된 타겟 필드 가져오기
|
|
||||||
const getFieldMapping = (sourceColumn: string): string => {
|
|
||||||
const mappings = config.subDataLookup?.lookup?.fieldMappings || [];
|
|
||||||
const mapping = mappings.find((m) => m.sourceColumn === sourceColumn);
|
|
||||||
return mapping?.targetField || "";
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|
@ -711,7 +682,6 @@ export const RepeaterConfigPanel: React.FC<RepeaterConfigPanelProps> = ({
|
||||||
{getOrderedDisplayColumns().map((colName, index) => {
|
{getOrderedDisplayColumns().map((colName, index) => {
|
||||||
const col = subDataTableColumns.find((c) => c.columnName === colName);
|
const col = subDataTableColumns.find((c) => c.columnName === colName);
|
||||||
const currentLabel = config.subDataLookup?.lookup?.columnLabels?.[colName] || "";
|
const currentLabel = config.subDataLookup?.lookup?.columnLabels?.[colName] || "";
|
||||||
const currentMapping = getFieldMapping(colName);
|
|
||||||
const orderedColumns = getOrderedDisplayColumns();
|
const orderedColumns = getOrderedDisplayColumns();
|
||||||
const isFirst = index === 0;
|
const isFirst = index === 0;
|
||||||
const isLast = index === orderedColumns.length - 1;
|
const isLast = index === orderedColumns.length - 1;
|
||||||
|
|
@ -765,37 +735,13 @@ export const RepeaterConfigPanel: React.FC<RepeaterConfigPanelProps> = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 하단: 저장 컬럼 선택 */}
|
|
||||||
<div className="mt-1.5 flex items-center gap-2">
|
|
||||||
<span className="text-[10px] text-gray-500 whitespace-nowrap">저장 컬럼:</span>
|
|
||||||
<Select
|
|
||||||
value={currentMapping || "__none__"}
|
|
||||||
onValueChange={(v) => handleFieldMappingChange(colName, v === "__none__" ? "" : v)}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-6 flex-1 text-xs">
|
|
||||||
<SelectValue placeholder="선택안함" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="__none__" className="text-xs">
|
|
||||||
선택안함
|
|
||||||
</SelectItem>
|
|
||||||
{tableColumns.map((targetCol) => (
|
|
||||||
<SelectItem key={targetCol.columnName} value={targetCol.columnName} className="text-xs">
|
|
||||||
{targetCol.columnLabel || targetCol.columnName} ({targetCol.columnName})
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{config.targetTable && (
|
|
||||||
<p className="text-[10px] text-purple-500">
|
<p className="text-[10px] text-purple-500">
|
||||||
* 저장 대상: {config.targetTable}
|
* 저장 설정은 필드 정의에서 "하위 데이터 조회에서 값 가져오기"로 설정하세요
|
||||||
</p>
|
</p>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -1545,6 +1491,7 @@ export const RepeaterConfigPanel: React.FC<RepeaterConfigPanelProps> = ({
|
||||||
|
|
||||||
{/* 카테고리 타입이 아닐 때만 표시 모드 선택 */}
|
{/* 카테고리 타입이 아닐 때만 표시 모드 선택 */}
|
||||||
{field.type !== "category" && (
|
{field.type !== "category" && (
|
||||||
|
<div className="space-y-3">
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label className="text-xs">표시 모드</Label>
|
<Label className="text-xs">표시 모드</Label>
|
||||||
|
|
@ -1575,6 +1522,76 @@ export const RepeaterConfigPanel: React.FC<RepeaterConfigPanelProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 숨김 체크박스 */}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id={`hidden-${index}`}
|
||||||
|
checked={field.isHidden ?? false}
|
||||||
|
onCheckedChange={(checked) => updateField(index, { isHidden: checked as boolean })}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={`hidden-${index}`} className="cursor-pointer text-xs font-normal">
|
||||||
|
숨김 (테이블에 표시 안 함)
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 하위 데이터 조회에서 값 가져오기 */}
|
||||||
|
{config.subDataLookup?.enabled && (
|
||||||
|
<div className="space-y-2 rounded border border-purple-200 bg-purple-50 p-2">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id={`subdata-${index}`}
|
||||||
|
checked={field.subDataSource?.enabled ?? false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
updateField(index, {
|
||||||
|
subDataSource: {
|
||||||
|
enabled: checked as boolean,
|
||||||
|
sourceColumn: field.subDataSource?.sourceColumn || "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={`subdata-${index}`} className="cursor-pointer text-xs font-normal text-purple-700">
|
||||||
|
하위 데이터 조회에서 값 가져오기
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{field.subDataSource?.enabled && (
|
||||||
|
<div className="ml-5 space-y-1">
|
||||||
|
<Label className="text-[10px] text-purple-600">소스 컬럼</Label>
|
||||||
|
<Select
|
||||||
|
value={field.subDataSource?.sourceColumn || ""}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
updateField(index, {
|
||||||
|
subDataSource: {
|
||||||
|
enabled: true,
|
||||||
|
sourceColumn: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="h-7 text-xs">
|
||||||
|
<SelectValue placeholder="컬럼 선택" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{(config.subDataLookup?.lookup?.displayColumns || []).map((colName) => {
|
||||||
|
const label = config.subDataLookup?.lookup?.columnLabels?.[colName] || colName;
|
||||||
|
return (
|
||||||
|
<SelectItem key={colName} value={colName} className="text-xs">
|
||||||
|
{label} ({colName})
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-[10px] text-purple-500">
|
||||||
|
재고 조회 결과에서 이 컬럼의 값을 가져옵니다
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 카테고리 타입일 때는 필수만 표시 */}
|
{/* 카테고리 타입일 때는 필수만 표시 */}
|
||||||
|
|
|
||||||
|
|
@ -287,12 +287,18 @@ const RepeaterFieldGroupComponent: React.FC<ComponentRendererProps> = (props) =>
|
||||||
if (onChange && items.length > 0) {
|
if (onChange && items.length > 0) {
|
||||||
// 🆕 RepeaterFieldGroup이 관리하는 필드 목록 추출
|
// 🆕 RepeaterFieldGroup이 관리하는 필드 목록 추출
|
||||||
const repeaterFieldNames = (configRef.current.fields || []).map((f: any) => f.name);
|
const repeaterFieldNames = (configRef.current.fields || []).map((f: any) => f.name);
|
||||||
|
// 🆕 subDataSource 설정이 있는 필드 목록 (하위 데이터 조회 연동)
|
||||||
|
const fieldsConfig = (configRef.current.fields || []).map((f: any) => ({
|
||||||
|
name: f.name,
|
||||||
|
subDataSource: f.subDataSource,
|
||||||
|
}));
|
||||||
const dataWithMeta = items.map((item: any) => ({
|
const dataWithMeta = items.map((item: any) => ({
|
||||||
...item,
|
...item,
|
||||||
_targetTable: targetTable,
|
_targetTable: targetTable,
|
||||||
_originalItemIds: itemIds, // 🆕 원본 ID 목록도 함께 전달
|
_originalItemIds: itemIds, // 🆕 원본 ID 목록도 함께 전달
|
||||||
_existingRecord: !!item.id, // 🆕 기존 레코드 플래그 (id가 있으면 기존 레코드)
|
_existingRecord: !!item.id, // 🆕 기존 레코드 플래그 (id가 있으면 기존 레코드)
|
||||||
_repeaterFields: repeaterFieldNames, // 🆕 품목 고유 필드 목록
|
_repeaterFields: repeaterFieldNames, // 🆕 품목 고유 필드 목록
|
||||||
|
_repeaterFieldsConfig: fieldsConfig, // 🆕 필드 설정 (subDataSource 등)
|
||||||
}));
|
}));
|
||||||
onChange(dataWithMeta);
|
onChange(dataWithMeta);
|
||||||
}
|
}
|
||||||
|
|
@ -393,11 +399,17 @@ const RepeaterFieldGroupComponent: React.FC<ComponentRendererProps> = (props) =>
|
||||||
if (items.length > 0) {
|
if (items.length > 0) {
|
||||||
// 🆕 RepeaterFieldGroup이 관리하는 필드 목록 추출
|
// 🆕 RepeaterFieldGroup이 관리하는 필드 목록 추출
|
||||||
const repeaterFieldNames = (configRef.current.fields || []).map((f: any) => f.name);
|
const repeaterFieldNames = (configRef.current.fields || []).map((f: any) => f.name);
|
||||||
|
// 🆕 subDataSource 설정이 있는 필드 목록
|
||||||
|
const fieldsConfig = (configRef.current.fields || []).map((f: any) => ({
|
||||||
|
name: f.name,
|
||||||
|
subDataSource: f.subDataSource,
|
||||||
|
}));
|
||||||
const dataWithMeta = items.map((item: any) => ({
|
const dataWithMeta = items.map((item: any) => ({
|
||||||
...item,
|
...item,
|
||||||
_targetTable: effectiveTargetTable,
|
_targetTable: effectiveTargetTable,
|
||||||
_existingRecord: !!item.id,
|
_existingRecord: !!item.id,
|
||||||
_repeaterFields: repeaterFieldNames, // 🆕 품목 고유 필드 목록
|
_repeaterFields: repeaterFieldNames, // 🆕 품목 고유 필드 목록
|
||||||
|
_repeaterFieldsConfig: fieldsConfig, // 🆕 필드 설정 (subDataSource 등)
|
||||||
}));
|
}));
|
||||||
onChange(dataWithMeta);
|
onChange(dataWithMeta);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -681,6 +693,11 @@ const RepeaterFieldGroupComponent: React.FC<ComponentRendererProps> = (props) =>
|
||||||
(newValue: any[]) => {
|
(newValue: any[]) => {
|
||||||
// 🆕 RepeaterFieldGroup이 관리하는 필드 목록 추출
|
// 🆕 RepeaterFieldGroup이 관리하는 필드 목록 추출
|
||||||
const repeaterFieldNames = (configRef.current.fields || []).map((f: any) => f.name);
|
const repeaterFieldNames = (configRef.current.fields || []).map((f: any) => f.name);
|
||||||
|
// 🆕 subDataSource 설정이 있는 필드 목록
|
||||||
|
const fieldsConfig = (configRef.current.fields || []).map((f: any) => ({
|
||||||
|
name: f.name,
|
||||||
|
subDataSource: f.subDataSource,
|
||||||
|
}));
|
||||||
|
|
||||||
// 🆕 모든 항목에 메타데이터 추가
|
// 🆕 모든 항목에 메타데이터 추가
|
||||||
let valueWithMeta = newValue.map((item: any) => ({
|
let valueWithMeta = newValue.map((item: any) => ({
|
||||||
|
|
@ -688,6 +705,7 @@ const RepeaterFieldGroupComponent: React.FC<ComponentRendererProps> = (props) =>
|
||||||
_targetTable: effectiveTargetTable || targetTable,
|
_targetTable: effectiveTargetTable || targetTable,
|
||||||
_existingRecord: !!item.id,
|
_existingRecord: !!item.id,
|
||||||
_repeaterFields: repeaterFieldNames, // 🆕 품목 고유 필드 목록
|
_repeaterFields: repeaterFieldNames, // 🆕 품목 고유 필드 목록
|
||||||
|
_repeaterFieldsConfig: fieldsConfig, // 🆕 필드 설정 (subDataSource 등)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 🆕 분할 패널에서 우측인 경우, FK 값 추가
|
// 🆕 분할 패널에서 우측인 경우, FK 값 추가
|
||||||
|
|
|
||||||
|
|
@ -803,7 +803,7 @@ export class ButtonActionExecutor {
|
||||||
|
|
||||||
for (const item of parsedData) {
|
for (const item of parsedData) {
|
||||||
// 메타 필드 제거
|
// 메타 필드 제거
|
||||||
const { _targetTable, _isNewItem, _existingRecord, _originalItemIds, _deletedItemIds, _repeaterFields, ...itemData } = item;
|
const { _targetTable, _isNewItem, _existingRecord, _originalItemIds, _deletedItemIds, _repeaterFields, _subDataSelection, _subDataMaxValue, ...itemData } = item;
|
||||||
|
|
||||||
// 🔧 품목 고유 필드만 추출 (RepeaterFieldGroup 설정 기반)
|
// 🔧 품목 고유 필드만 추출 (RepeaterFieldGroup 설정 기반)
|
||||||
const itemOnlyData: Record<string, any> = {};
|
const itemOnlyData: Record<string, any> = {};
|
||||||
|
|
@ -813,6 +813,42 @@ export class ButtonActionExecutor {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🆕 하위 데이터 선택에서 값 추출 (subDataSource 설정 기반)
|
||||||
|
// 필드 정의에서 subDataSource.enabled가 true이고 sourceColumn이 설정된 필드만 처리
|
||||||
|
if (_subDataSelection && typeof _subDataSelection === 'object') {
|
||||||
|
// _repeaterFieldsConfig에서 subDataSource 설정 확인
|
||||||
|
const fieldsConfig = item._repeaterFieldsConfig as Array<{
|
||||||
|
name: string;
|
||||||
|
subDataSource?: { enabled: boolean; sourceColumn: string };
|
||||||
|
}> | undefined;
|
||||||
|
|
||||||
|
if (fieldsConfig && Array.isArray(fieldsConfig)) {
|
||||||
|
fieldsConfig.forEach((fieldConfig) => {
|
||||||
|
if (fieldConfig.subDataSource?.enabled && fieldConfig.subDataSource?.sourceColumn) {
|
||||||
|
const targetField = fieldConfig.name; // 필드명 = 저장할 컬럼명
|
||||||
|
const sourceColumn = fieldConfig.subDataSource.sourceColumn;
|
||||||
|
const sourceValue = _subDataSelection[sourceColumn];
|
||||||
|
|
||||||
|
if (sourceValue !== undefined && sourceValue !== null) {
|
||||||
|
itemOnlyData[targetField] = sourceValue;
|
||||||
|
console.log(`📋 [handleSave] 하위 데이터 값 매핑: ${sourceColumn} → ${targetField} = ${sourceValue}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 하위 호환성: fieldsConfig가 없으면 기존 방식 사용
|
||||||
|
Object.keys(_subDataSelection).forEach((subDataKey) => {
|
||||||
|
if (itemOnlyData[subDataKey] === undefined || itemOnlyData[subDataKey] === null || itemOnlyData[subDataKey] === '') {
|
||||||
|
const subDataValue = _subDataSelection[subDataKey];
|
||||||
|
if (subDataValue !== undefined && subDataValue !== null) {
|
||||||
|
itemOnlyData[subDataKey] = subDataValue;
|
||||||
|
console.log(`📋 [handleSave] 하위 데이터 선택 값 추가 (레거시): ${subDataKey} = ${subDataValue}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 🔧 마스터 정보 + 품목 고유 정보 병합
|
// 🔧 마스터 정보 + 품목 고유 정보 병합
|
||||||
// masterFields: 상단 폼에서 수정한 최신 마스터 정보
|
// masterFields: 상단 폼에서 수정한 최신 마스터 정보
|
||||||
// itemOnlyData: 품목 고유 필드만 (품번, 품명, 수량 등)
|
// itemOnlyData: 품목 고유 필드만 (품번, 품명, 수량 등)
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,19 @@ export interface CalculationFormula {
|
||||||
* 필드 표시 모드
|
* 필드 표시 모드
|
||||||
* - input: 입력 필드로 표시 (편집 가능)
|
* - input: 입력 필드로 표시 (편집 가능)
|
||||||
* - readonly: 읽기 전용 텍스트로 표시
|
* - readonly: 읽기 전용 텍스트로 표시
|
||||||
|
* - hidden: 숨김 (UI에 표시되지 않지만 데이터에 포함됨)
|
||||||
* - (카테고리 타입은 자동으로 배지로 표시됨)
|
* - (카테고리 타입은 자동으로 배지로 표시됨)
|
||||||
*/
|
*/
|
||||||
export type RepeaterFieldDisplayMode = "input" | "readonly";
|
export type RepeaterFieldDisplayMode = "input" | "readonly" | "hidden";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 하위 데이터 조회 소스 설정
|
||||||
|
* 필드 값을 하위 데이터 조회 결과에서 가져올 때 사용
|
||||||
|
*/
|
||||||
|
export interface SubDataSourceConfig {
|
||||||
|
enabled: boolean; // 활성화 여부
|
||||||
|
sourceColumn: string; // 하위 데이터 조회 테이블의 소스 컬럼 (예: lot_number)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 반복 그룹 내 개별 필드 정의
|
* 반복 그룹 내 개별 필드 정의
|
||||||
|
|
@ -60,6 +70,8 @@ export interface RepeaterFieldDefinition {
|
||||||
options?: Array<{ label: string; value: string }>; // select용
|
options?: Array<{ label: string; value: string }>; // select용
|
||||||
width?: string; // 필드 너비 (예: "200px", "50%")
|
width?: string; // 필드 너비 (예: "200px", "50%")
|
||||||
displayMode?: RepeaterFieldDisplayMode; // 표시 모드: input(입력), readonly(읽기전용)
|
displayMode?: RepeaterFieldDisplayMode; // 표시 모드: input(입력), readonly(읽기전용)
|
||||||
|
isHidden?: boolean; // 숨김 여부 (true면 테이블에 표시 안 함, 데이터는 저장)
|
||||||
|
subDataSource?: SubDataSourceConfig; // 하위 데이터 조회에서 값 가져오기 설정
|
||||||
categoryCode?: string; // category 타입일 때 사용할 카테고리 코드
|
categoryCode?: string; // category 타입일 때 사용할 카테고리 코드
|
||||||
formula?: CalculationFormula; // 계산식 (type이 "calculated"일 때 사용)
|
formula?: CalculationFormula; // 계산식 (type이 "calculated"일 때 사용)
|
||||||
numberFormat?: {
|
numberFormat?: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue