feat: enhance schema type handling with new formats and UI components
This commit is contained in:
@@ -18,7 +18,7 @@ _EDITOR_CONTEXT = {
|
|||||||
"patterns": [p.value for p in ReconPatterns],
|
"patterns": [p.value for p in ReconPatterns],
|
||||||
"statuses": [s.value for s in ReconConfigStatus],
|
"statuses": [s.value for s in ReconConfigStatus],
|
||||||
"frequencies": ["Ad Hoc", "Intra Day", "Daily", "Weekly", "Monthly", "Quarterly"],
|
"frequencies": ["Ad Hoc", "Intra Day", "Daily", "Weekly", "Monthly", "Quarterly"],
|
||||||
"schema_types": ["str", "int", "float", "date('%Y-%m-%d')", "datetime('%Y-%m-%d %H:%M:%S')", "bool"],
|
"schema_types": ["str", "int", "float", "date", "datetime", "decimal", "bool"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -959,6 +959,34 @@ a.job-bar:hover { filter: brightness(1.1); box-shadow: 0 0 0 1px rgba(255,255,25
|
|||||||
padding: 5px 8px;
|
padding: 5px 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.schema-type-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.schema-type-cell .schema-type-select {
|
||||||
|
width: auto;
|
||||||
|
min-width: 80px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.schema-fmt-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.schema-fmt-input {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
.schema-dec-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.schema-dec-prec,
|
||||||
|
.schema-dec-scale {
|
||||||
|
width: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Field mapping */
|
/* Field mapping */
|
||||||
.mapping-header {
|
.mapping-header {
|
||||||
|
|||||||
@@ -290,6 +290,40 @@
|
|||||||
// ── Schema type options ─────────────────────────────────────────────────────
|
// ── Schema type options ─────────────────────────────────────────────────────
|
||||||
const SCHEMA_TYPES = {{ schema_types | tojson }};
|
const SCHEMA_TYPES = {{ schema_types | tojson }};
|
||||||
const IS_NEW = {{ 'true' if is_new else 'false' }};
|
const IS_NEW = {{ 'true' if is_new else 'false' }};
|
||||||
|
|
||||||
|
const DATE_FORMATS = [
|
||||||
|
'%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y', '%d-%m-%Y',
|
||||||
|
'%Y%m%d', '%d %b %Y', '%d %B %Y', '%b %d, %Y',
|
||||||
|
];
|
||||||
|
const DATETIME_FORMATS = [
|
||||||
|
'%Y-%m-%d %H:%M:%S', '%Y-%m-%dT%H:%M:%S',
|
||||||
|
'%d/%m/%Y %H:%M:%S', '%d/%m/%Y %H:%M',
|
||||||
|
'%Y-%m-%d %H:%M', '%Y%m%d%H%M%S', '%d %b %Y %H:%M:%S',
|
||||||
|
];
|
||||||
|
|
||||||
|
function parseSchemaType(typeStr) {
|
||||||
|
if (!typeStr) return { base: 'str' };
|
||||||
|
let m;
|
||||||
|
if ((m = typeStr.match(/^date\('([^']+)'\)$/))) return { base: 'date', format: m[1] };
|
||||||
|
if ((m = typeStr.match(/^datetime\('([^']+)'\)$/))) return { base: 'datetime', format: m[1] };
|
||||||
|
if ((m = typeStr.match(/^decimal\((\d+),\s*(\d+)\)$/))) return { base: 'decimal', precision: +m[1], scale: +m[2] };
|
||||||
|
return { base: typeStr };
|
||||||
|
}
|
||||||
|
|
||||||
|
function composeSchemaType(row) {
|
||||||
|
const base = row.querySelector('.schema-type-select').value;
|
||||||
|
if (base === 'date' || base === 'datetime') {
|
||||||
|
const fmt = row.querySelector('.schema-fmt-input')?.value.trim()
|
||||||
|
|| (base === 'date' ? '%Y-%m-%d' : '%Y-%m-%d %H:%M:%S');
|
||||||
|
return `${base}('${fmt}')`;
|
||||||
|
}
|
||||||
|
if (base === 'decimal') {
|
||||||
|
const prec = parseInt(row.querySelector('.schema-dec-prec')?.value) || 18;
|
||||||
|
const scale = parseInt(row.querySelector('.schema-dec-scale')?.value) || 2;
|
||||||
|
return `decimal(${prec}, ${scale})`;
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
const CONFIG_REF = {{ (reference | tojson) if not is_new else 'null' }};
|
const CONFIG_REF = {{ (reference | tojson) if not is_new else 'null' }};
|
||||||
|
|
||||||
// ── Build a syscfg card from a data object ──────────────────────────────────
|
// ── Build a syscfg card from a data object ──────────────────────────────────
|
||||||
@@ -412,20 +446,76 @@ function addSchemaRow(card, colName, colType, isIndex) {
|
|||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'schema-row';
|
row.className = 'schema-row';
|
||||||
|
|
||||||
|
const parsed = parseSchemaType(colType);
|
||||||
|
|
||||||
const colInput = document.createElement('input');
|
const colInput = document.createElement('input');
|
||||||
colInput.type = 'text';
|
colInput.type = 'text';
|
||||||
colInput.placeholder = 'column_name';
|
colInput.placeholder = 'column_name';
|
||||||
colInput.value = colName || '';
|
colInput.value = colName || '';
|
||||||
colInput.addEventListener('input', () => updateSchemaCount(card));
|
colInput.addEventListener('input', () => updateSchemaCount(card));
|
||||||
|
|
||||||
|
// ── Type cell ────────────────────────────────────────────────
|
||||||
|
const typeCell = document.createElement('div');
|
||||||
|
typeCell.className = 'schema-type-cell';
|
||||||
|
|
||||||
const typeSelect = document.createElement('select');
|
const typeSelect = document.createElement('select');
|
||||||
|
typeSelect.className = 'schema-type-select';
|
||||||
SCHEMA_TYPES.forEach(t => {
|
SCHEMA_TYPES.forEach(t => {
|
||||||
const opt = document.createElement('option');
|
const opt = document.createElement('option');
|
||||||
opt.value = t; opt.textContent = t;
|
opt.value = t; opt.textContent = t;
|
||||||
if (t === (colType || 'str')) opt.selected = true;
|
if (t === parsed.base) opt.selected = true;
|
||||||
typeSelect.appendChild(opt);
|
typeSelect.appendChild(opt);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Date / datetime format combobox
|
||||||
|
const fmtId = `fmt-${Math.random().toString(36).slice(2)}`;
|
||||||
|
const fmtWrapper = document.createElement('span');
|
||||||
|
fmtWrapper.className = 'schema-fmt-wrapper';
|
||||||
|
const fmtInput = document.createElement('input');
|
||||||
|
fmtInput.type = 'text';
|
||||||
|
fmtInput.className = 'schema-fmt-input';
|
||||||
|
fmtInput.setAttribute('list', fmtId);
|
||||||
|
if (parsed.format) fmtInput.value = parsed.format;
|
||||||
|
const fmtList = document.createElement('datalist');
|
||||||
|
fmtList.id = fmtId;
|
||||||
|
fmtWrapper.append(fmtInput, fmtList);
|
||||||
|
|
||||||
|
// Decimal precision + scale inputs
|
||||||
|
const decWrapper = document.createElement('span');
|
||||||
|
decWrapper.className = 'schema-dec-wrapper';
|
||||||
|
const precInput = document.createElement('input');
|
||||||
|
precInput.type = 'number';
|
||||||
|
precInput.className = 'schema-dec-prec';
|
||||||
|
precInput.placeholder = 'prec';
|
||||||
|
precInput.min = 1; precInput.max = 38;
|
||||||
|
if (parsed.precision != null) precInput.value = parsed.precision;
|
||||||
|
const scaleInput = document.createElement('input');
|
||||||
|
scaleInput.type = 'number';
|
||||||
|
scaleInput.className = 'schema-dec-scale';
|
||||||
|
scaleInput.placeholder = 'scale';
|
||||||
|
scaleInput.min = 0; scaleInput.max = 38;
|
||||||
|
if (parsed.scale != null) scaleInput.value = parsed.scale;
|
||||||
|
decWrapper.append(precInput, scaleInput);
|
||||||
|
|
||||||
|
function syncExtras() {
|
||||||
|
const base = typeSelect.value;
|
||||||
|
const isDate = base === 'date', isDt = base === 'datetime';
|
||||||
|
fmtWrapper.hidden = !(isDate || isDt);
|
||||||
|
decWrapper.hidden = base !== 'decimal';
|
||||||
|
if (isDate || isDt) {
|
||||||
|
fmtList.innerHTML = '';
|
||||||
|
(isDate ? DATE_FORMATS : DATETIME_FORMATS).forEach(f => {
|
||||||
|
const o = document.createElement('option'); o.value = f; fmtList.appendChild(o);
|
||||||
|
});
|
||||||
|
if (!fmtInput.value) fmtInput.value = isDate ? '%Y-%m-%d' : '%Y-%m-%d %H:%M:%S';
|
||||||
|
fmtInput.placeholder = isDate ? '%Y-%m-%d' : '%Y-%m-%d %H:%M:%S';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
typeSelect.addEventListener('change', syncExtras);
|
||||||
|
syncExtras();
|
||||||
|
|
||||||
|
typeCell.append(typeSelect, fmtWrapper, decWrapper);
|
||||||
|
|
||||||
const idxCheck = document.createElement('input');
|
const idxCheck = document.createElement('input');
|
||||||
idxCheck.type = 'checkbox';
|
idxCheck.type = 'checkbox';
|
||||||
idxCheck.className = 'idx-check';
|
idxCheck.className = 'idx-check';
|
||||||
@@ -438,7 +528,7 @@ function addSchemaRow(card, colName, colType, isIndex) {
|
|||||||
removeBtn.textContent = '×';
|
removeBtn.textContent = '×';
|
||||||
removeBtn.addEventListener('click', () => { row.remove(); updateSchemaCount(card); });
|
removeBtn.addEventListener('click', () => { row.remove(); updateSchemaCount(card); });
|
||||||
|
|
||||||
row.append(colInput, typeSelect, idxCheck, removeBtn);
|
row.append(colInput, typeCell, idxCheck, removeBtn);
|
||||||
container.appendChild(row);
|
container.appendChild(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,7 +564,7 @@ function readSyscfgCard(card) {
|
|||||||
const index_fields = [];
|
const index_fields = [];
|
||||||
card.querySelectorAll('.schema-rows .schema-row').forEach(row => {
|
card.querySelectorAll('.schema-rows .schema-row').forEach(row => {
|
||||||
const name = row.querySelector('input[type="text"]').value.trim();
|
const name = row.querySelector('input[type="text"]').value.trim();
|
||||||
const type = row.querySelector('select').value;
|
const type = composeSchemaType(row);
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
schema[name] = type;
|
schema[name] = type;
|
||||||
if (row.querySelector('.idx-check')?.checked) index_fields.push(name);
|
if (row.querySelector('.idx-check')?.checked) index_fields.push(name);
|
||||||
|
|||||||
Reference in New Issue
Block a user