fix(scaffold): Domain Category 덮어쓰기 방지 및 하위 필드(itemFields) 편집 팝업 추가
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 6m20s
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 6m20s
This commit is contained in:
@@ -1203,6 +1203,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sub Field Editor Modal -->
|
||||
<div class="modal fade" id="subFieldEditorModal" tabindex="-1" aria-hidden="true" style="z-index: 1060;">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">하위 필드(itemFields) 편집</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:15%">Name</th>
|
||||
<th style="width:15%">Type</th>
|
||||
<th style="width:20%">Details</th>
|
||||
<th style="width:20%">Description</th>
|
||||
<th style="width:15%">Example</th>
|
||||
<th style="width:10%">Required</th>
|
||||
<th style="width:5%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="subFieldEditorBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button type="button" class="btn-action mt-2" onclick="addSubFieldEditorRow()">+ 하위 필드 추가</button>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn-secondary-action" data-bs-dismiss="modal">취소</button>
|
||||
<button type="button" class="btn-action" onclick="applySubFieldEditor()">적용</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Tool Modal -->
|
||||
<div class="modal fade" id="editToolModal" tabindex="-1" aria-labelledby="editToolModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
@@ -1726,6 +1761,81 @@
|
||||
]
|
||||
}
|
||||
};
|
||||
let currentSubFieldTargetInput = null;
|
||||
let subFieldEditorModalInstance = null;
|
||||
|
||||
function openSubFieldEditor(inputEl) {
|
||||
currentSubFieldTargetInput = inputEl;
|
||||
let fields = [];
|
||||
if (inputEl.value.trim()) {
|
||||
try { fields = JSON.parse(inputEl.value); } catch(e) {}
|
||||
}
|
||||
document.getElementById('subFieldEditorBody').replaceChildren();
|
||||
(fields.length ? fields : [{}]).forEach(addSubFieldEditorRow);
|
||||
if (!subFieldEditorModalInstance) {
|
||||
subFieldEditorModalInstance = new bootstrap.Modal(document.getElementById('subFieldEditorModal'));
|
||||
}
|
||||
subFieldEditorModalInstance.show();
|
||||
}
|
||||
|
||||
function addSubFieldEditorRow(field = {}) {
|
||||
const row = document.createElement('tr');
|
||||
const makeInput = (val, ph) => {
|
||||
const el = document.createElement('input');
|
||||
el.type = 'text'; el.className = 'form-control form-control-sm';
|
||||
el.value = val || ''; el.placeholder = ph;
|
||||
return el;
|
||||
};
|
||||
const n = makeInput(field.name, 'name'); n.dataset.field = 'name';
|
||||
const d = makeInput(field.description, 'desc'); d.dataset.field = 'description';
|
||||
const e = makeInput(field.example, 'ex'); e.dataset.field = 'example';
|
||||
const t = document.createElement('select');
|
||||
t.className = 'form-select form-select-sm'; t.dataset.field = 'type';
|
||||
['String', 'Integer', 'Long', 'Double', 'Boolean', 'BigDecimal', 'Enum'].forEach(type => {
|
||||
t.add(new Option(type, type, false, (field.type || 'String') === type));
|
||||
});
|
||||
const det = makeInput(field.type === 'Enum' ? (field.enumValues||[]).join(',') : '', 'Enum csv');
|
||||
det.dataset.field = 'details';
|
||||
t.addEventListener('change', () => {
|
||||
if (t.value === 'Enum') det.placeholder = 'Enum csv';
|
||||
else det.value = '';
|
||||
});
|
||||
const req = document.createElement('select');
|
||||
req.className = 'form-select form-select-sm'; req.dataset.field = 'required';
|
||||
req.add(new Option('Required', 'true', false, field.required === true || field.required === 'true'));
|
||||
req.add(new Option('Optional', 'false', false, !(field.required === true || field.required === 'true')));
|
||||
|
||||
[n, t, det, d, e, req].forEach(ctrl => {
|
||||
const td = document.createElement('td'); td.appendChild(ctrl); row.appendChild(td);
|
||||
});
|
||||
const del = document.createElement('button');
|
||||
del.type = 'button'; del.className = 'btn-secondary-action'; del.textContent = 'X';
|
||||
del.style.padding = '0.1rem 0.4rem';
|
||||
del.onclick = () => row.remove();
|
||||
const delTd = document.createElement('td'); delTd.appendChild(del); row.appendChild(delTd);
|
||||
document.getElementById('subFieldEditorBody').appendChild(row);
|
||||
}
|
||||
|
||||
function applySubFieldEditor() {
|
||||
const fields = [...document.querySelectorAll('#subFieldEditorBody tr')].map(row => {
|
||||
const type = row.querySelector('[data-field="type"]').value;
|
||||
const details = row.querySelector('[data-field="details"]').value.trim();
|
||||
return {
|
||||
name: row.querySelector('[data-field="name"]').value.trim(),
|
||||
type,
|
||||
description: row.querySelector('[data-field="description"]').value.trim(),
|
||||
example: row.querySelector('[data-field="example"]').value.trim(),
|
||||
required: row.querySelector('[data-field="required"]').value === 'true',
|
||||
enumValues: type === 'Enum' ? details.split(',').map(s=>s.trim()).filter(Boolean) : [],
|
||||
itemType: null, itemFields: []
|
||||
};
|
||||
}).filter(f => f.name);
|
||||
currentSubFieldTargetInput.value = JSON.stringify(fields);
|
||||
currentSubFieldTargetInput.dispatchEvent(new Event('input'));
|
||||
currentSubFieldTargetInput.dispatchEvent(new Event('change'));
|
||||
subFieldEditorModalInstance.hide();
|
||||
}
|
||||
|
||||
let fieldEditorTargetId = null;
|
||||
let fieldEditorModalInstance = null;
|
||||
|
||||
@@ -1804,14 +1914,32 @@
|
||||
requiredSelect.add(new Option('Required', 'true', false, field.required === true || field.required === 'true'));
|
||||
requiredSelect.add(new Option('Optional', 'false', false, !(field.required === true || field.required === 'true')));
|
||||
|
||||
const itemFieldsContainer = document.createElement('div');
|
||||
itemFieldsContainer.className = 'd-flex gap-1';
|
||||
|
||||
const itemFieldsInput = makeInput(field.type === 'List' && field.itemType === 'Object'
|
||||
? JSON.stringify(field.itemFields || []) : '', 'Object fields JSON e.g. [{"name":"date","type":"String"}]');
|
||||
? JSON.stringify(field.itemFields || []) : '', 'JSON 직접입력 또는 우측 편집 클릭');
|
||||
itemFieldsInput.dataset.field = 'itemFields';
|
||||
itemFieldsInput.style.flex = '1';
|
||||
|
||||
const editItemBtn = document.createElement('button');
|
||||
editItemBtn.type = 'button';
|
||||
editItemBtn.className = 'btn-secondary-action';
|
||||
editItemBtn.textContent = '편집';
|
||||
editItemBtn.style.padding = '0.2rem 0.5rem';
|
||||
editItemBtn.onclick = () => {
|
||||
if (typeof openSubFieldEditor === 'function') openSubFieldEditor(itemFieldsInput);
|
||||
};
|
||||
|
||||
itemFieldsContainer.appendChild(itemFieldsInput);
|
||||
itemFieldsContainer.appendChild(editItemBtn);
|
||||
|
||||
const updateObjectListFieldsState = () => {
|
||||
const objectList = typeSelect.value === 'List' && detailsInput.value === 'Object';
|
||||
itemFieldsInput.disabled = !objectList;
|
||||
editItemBtn.disabled = !objectList;
|
||||
itemFieldsInput.placeholder = objectList
|
||||
? 'Object fields JSON e.g. [{"name":"date","type":"String"}]'
|
||||
? 'JSON 직접입력 또는 우측 편집 클릭'
|
||||
: 'Select List > Object to enter item fields';
|
||||
if (!objectList) itemFieldsInput.value = '';
|
||||
};
|
||||
@@ -1838,7 +1966,7 @@
|
||||
control.addEventListener('change', updateFieldEditorPreview);
|
||||
});
|
||||
|
||||
[nameInput, typeSelect, detailsInput, itemFieldsInput, descriptionInput, exampleInput, requiredSelect].forEach(control => {
|
||||
[nameInput, typeSelect, detailsInput, itemFieldsContainer, descriptionInput, exampleInput, requiredSelect].forEach(control => {
|
||||
const cell = document.createElement('td');
|
||||
cell.appendChild(control);
|
||||
row.appendChild(cell);
|
||||
@@ -1866,9 +1994,12 @@
|
||||
let itemFields = [];
|
||||
if (type === 'List' && details === 'Object') {
|
||||
const itemFieldsText = row.querySelector('[data-field="itemFields"]').value.trim();
|
||||
if (!itemFieldsText) throw new Error('List Object는 항목 필드를 입력해야 합니다.');
|
||||
itemFields = JSON.parse(itemFieldsText);
|
||||
if (!Array.isArray(itemFields) || itemFields.length === 0) throw new Error('List Object 항목 필드는 JSON 배열이어야 합니다.');
|
||||
try {
|
||||
itemFields = itemFieldsText ? JSON.parse(itemFieldsText) : [];
|
||||
if (!Array.isArray(itemFields)) itemFields = [];
|
||||
} catch (e) {
|
||||
itemFields = [];
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: row.querySelector('[data-field="name"]').value.trim(), type,
|
||||
@@ -1958,7 +2089,10 @@
|
||||
form.elements.baseName.value = result.baseName || '';
|
||||
form.elements.title.value = result.title || '';
|
||||
form.elements.description.value = result.description || '';
|
||||
form.elements.categoryKey.value = result.categoryKey || '';
|
||||
if (!form.elements.categoryKey.value) {
|
||||
form.elements.categoryKey.value = result.categoryKey || '';
|
||||
if (typeof loadUseCasesForSelection === 'function') loadUseCasesForSelection();
|
||||
}
|
||||
form.elements.routingType.value = result.routingType || 'HTTP';
|
||||
form.elements.httpApiName.value = result.httpApiName || '';
|
||||
form.elements.functionDescription.value = result.functionDescription || '';
|
||||
|
||||
Reference in New Issue
Block a user