feat: add scaffold field editor
Some checks failed
Deploy to OCIWP / deploy (push) Has been cancelled

This commit is contained in:
jade
2026-08-11 22:18:45 +09:00
parent 65040bdc8a
commit 11f94cf721

View File

@@ -506,6 +506,7 @@
<div class="d-flex justify-content-between align-items-center mb-1">
<label class="form-label mb-0">Input Fields (JSON)</label>
<div class="d-flex gap-1">
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="openFieldEditor('inputFields')">필드 편집</button>
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="loadFieldExample('inputFields')">예제 넣기</button>
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="copyFieldJson('inputFields')">복사</button>
</div>
@@ -516,6 +517,7 @@
<div class="d-flex justify-content-between align-items-center mb-1">
<label class="form-label mb-0">Output Fields (JSON)</label>
<div class="d-flex gap-1">
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="openFieldEditor('outputFields')">필드 편집</button>
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="loadFieldExample('outputFields')">예제 넣기</button>
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="copyFieldJson('outputFields')">복사</button>
</div>
@@ -615,6 +617,41 @@
<div id="resultBox"></div>
</div>
<!-- Field Editor Modal -->
<div class="modal fade" id="fieldEditorModal" tabindex="-1" aria-labelledby="fieldEditorModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="fieldEditorModalLabel">필드 편집</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p class="input-hint mt-0 mb-3">행을 추가해 필드를 입력하면 저장할 때 JSON으로 자동 변환됩니다.</p>
<div class="table-responsive">
<table class="table align-middle" id="fieldEditorTable">
<thead>
<tr>
<th style="min-width: 150px;">Name</th>
<th style="min-width: 125px;">Type</th>
<th style="min-width: 210px;">Description</th>
<th style="min-width: 180px;">Example</th>
<th style="min-width: 125px;">Required</th>
<th style="width: 1%;">삭제</th>
</tr>
</thead>
<tbody id="fieldEditorBody"></tbody>
</table>
</div>
<button type="button" class="btn-secondary-action" onclick="addFieldEditorRow()">+ 필드 추가</button>
</div>
<div class="modal-footer">
<button type="button" class="btn-secondary-action" data-bs-dismiss="modal" style="padding: 0.5rem 1rem; font-size: 0.875rem;">취소</button>
<button type="button" class="btn-action" onclick="applyFieldEditor()">JSON에 적용</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">
@@ -746,6 +783,112 @@
}
}
const fieldTypes = ['String', 'Integer', 'Long', 'Double', 'Boolean', 'BigDecimal'];
let fieldEditorTargetId = null;
let fieldEditorModalInstance = null;
function openFieldEditor(targetId) {
const textarea = document.getElementById(targetId);
let fields = [];
if (textarea.value.trim()) {
try {
fields = JSON.parse(textarea.value);
if (!Array.isArray(fields)) throw new Error('not an array');
} catch (error) {
alert('현재 JSON 형식이 올바르지 않습니다. JSON을 수정한 후 다시 열어주세요.');
return;
}
}
fieldEditorTargetId = targetId;
document.getElementById('fieldEditorModalLabel').textContent = targetId === 'inputFields'
? 'Input Fields 편집'
: 'Output Fields 편집';
const body = document.getElementById('fieldEditorBody');
body.replaceChildren();
(fields.length ? fields : [{}]).forEach(addFieldEditorRow);
if (!fieldEditorModalInstance) {
fieldEditorModalInstance = new bootstrap.Modal(document.getElementById('fieldEditorModal'));
}
fieldEditorModalInstance.show();
}
function addFieldEditorRow(field = {}) {
const row = document.createElement('tr');
const makeInput = (value, placeholder) => {
const input = document.createElement('input');
input.type = 'text';
input.className = 'form-control form-control-sm';
input.value = value || '';
input.placeholder = placeholder;
return input;
};
const nameInput = makeInput(field.name, 'e.g. employeeId');
nameInput.dataset.field = 'name';
const descriptionInput = makeInput(field.description, 'e.g. Employee ID');
descriptionInput.dataset.field = 'description';
const exampleInput = makeInput(field.example, 'e.g. EMP10001');
exampleInput.dataset.field = 'example';
const typeSelect = document.createElement('select');
typeSelect.className = 'form-select form-select-sm';
typeSelect.dataset.field = 'type';
fieldTypes.forEach(type => {
const option = new Option(type, type, false, (field.type || 'String') === type);
typeSelect.add(option);
});
const requiredSelect = document.createElement('select');
requiredSelect.className = 'form-select form-select-sm';
requiredSelect.dataset.field = 'required';
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')));
[nameInput, typeSelect, descriptionInput, exampleInput, requiredSelect].forEach(control => {
const cell = document.createElement('td');
cell.appendChild(control);
row.appendChild(cell);
});
const deleteCell = document.createElement('td');
const deleteButton = document.createElement('button');
deleteButton.type = 'button';
deleteButton.className = 'btn-secondary-action';
deleteButton.textContent = '삭제';
deleteButton.addEventListener('click', () => row.remove());
deleteCell.appendChild(deleteButton);
row.appendChild(deleteCell);
document.getElementById('fieldEditorBody').appendChild(row);
}
function applyFieldEditor() {
const rows = [...document.querySelectorAll('#fieldEditorBody tr')];
const names = new Set();
const fields = [];
for (const row of rows) {
const name = row.querySelector('[data-field="name"]').value.trim();
if (!name) continue;
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {
alert(`필드명 "${name}"은(는) Java 필드명 형식으로 입력해주세요.`);
return;
}
if (names.has(name)) {
alert(`필드명 "${name}"이(가) 중복되었습니다.`);
return;
}
names.add(name);
fields.push({
name,
type: row.querySelector('[data-field="type"]').value,
description: row.querySelector('[data-field="description"]').value.trim(),
example: row.querySelector('[data-field="example"]').value.trim(),
required: row.querySelector('[data-field="required"]').value === 'true'
});
}
document.getElementById(fieldEditorTargetId).value = JSON.stringify(fields, null, 2);
fieldEditorModalInstance.hide();
}
function loadToolList() {
const tbody = document.getElementById('toolListBody');
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-5 text-muted">Loading data...</td></tr>';