146 lines
6.3 KiB
Twig
146 lines
6.3 KiB
Twig
{% extends 'base.html.twig' %}
|
|
{% import '_sidebar_tables.html.twig' as macros %}
|
|
|
|
{% block title %}{{ tableName }} — {{ mode == 'new' ? 'Insert Item' : 'Edit Item' }} · Jormun Admin{% endblock %}
|
|
|
|
{% block breadcrumbs %}
|
|
<a href="{{ path('dashboard') }}"><i class="bi bi-house-fill"></i> Home</a>
|
|
<span class="sep">/</span>
|
|
<a href="{{ path('table_browse', {name: tableName}) }}">{{ tableName }}</a>
|
|
<span class="sep">/</span>
|
|
<span class="current">{{ mode == 'new' ? 'Insert Item' : 'Edit Item' }}</span>
|
|
{% endblock %}
|
|
|
|
{% block sidebar_tables %}
|
|
{{ macros.sidebar_table_links(tables, tableName) }}
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<nav class="op-tabs">
|
|
<a href="{{ path('table_browse', {name: tableName}) }}" class="op-tab">
|
|
<i class="bi bi-grid-3x3-gap"></i> Browse
|
|
</a>
|
|
<a href="{{ path('table_structure', {name: tableName}) }}" class="op-tab">
|
|
<i class="bi bi-diagram-3"></i> Structure
|
|
</a>
|
|
<a href="{{ path('table_query', {name: tableName}) }}" class="op-tab">
|
|
<i class="bi bi-search"></i> Query
|
|
</a>
|
|
<a href="{{ path('table_item_new', {name: tableName}) }}" class="op-tab {% if mode == 'new' %}active{% endif %}">
|
|
<i class="bi bi-plus-circle"></i> Insert
|
|
</a>
|
|
</nav>
|
|
|
|
<div class="row justify-content-center">
|
|
<div class="col-12 col-lg-8 col-xl-7">
|
|
<div class="content-card">
|
|
<div class="content-card-header">
|
|
<h6>
|
|
<i class="bi bi-{% if mode == 'new' %}plus-circle{% else %}pencil{% endif %} me-1" style="color:var(--jormun-teal);"></i>
|
|
{{ mode == 'new' ? 'Insert New Item' : 'Edit Item' }}
|
|
</h6>
|
|
<span style="font-size:0.72rem;color:#64748b;">{{ tableName }}</span>
|
|
</div>
|
|
|
|
<div style="padding:1.25rem;" x-data="itemForm()">
|
|
|
|
{% if error %}
|
|
<div style="background:#fef2f2;border:1px solid #fca5a5;color:#991b1b;border-radius:6px;padding:0.65rem 0.85rem;margin-bottom:1rem;font-size:0.78rem;">
|
|
<i class="bi bi-exclamation-triangle-fill me-1"></i>{{ error }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Key schema hint #}
|
|
<div style="background:var(--jormun-teal-xlight);border:1px solid var(--jormun-teal-light);border-radius:6px;padding:0.6rem 0.85rem;margin-bottom:1rem;font-size:0.75rem;color:#374151;">
|
|
<i class="bi bi-info-circle" style="color:var(--jormun-teal);"></i>
|
|
Required keys:
|
|
<strong>{{ keySchema.HASH.name ?? '?' }}</strong> ({{ keySchema.HASH.type ?? 'S' }})
|
|
{% if keySchema.RANGE is defined %}
|
|
+ <strong>{{ keySchema.RANGE.name }}</strong> ({{ keySchema.RANGE.type }})
|
|
{% endif %}
|
|
· Item is a JSON object. Additional attributes are free-form.
|
|
</div>
|
|
|
|
<form method="POST" @submit.prevent="submitForm">
|
|
{# JSON editor #}
|
|
<div class="mb-3">
|
|
<div class="d-flex align-items-center justify-content-between mb-1">
|
|
<label style="font-size:0.72rem;font-weight:500;text-transform:uppercase;letter-spacing:0.04em;color:#374151;">
|
|
Item JSON
|
|
</label>
|
|
<div class="d-flex gap-2">
|
|
<button type="button" @click="formatJson()" class="btn-jormun-outline" style="font-size:0.7rem;padding:0.2rem 0.5rem;">
|
|
<i class="bi bi-braces"></i> Format
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<textarea
|
|
name="item_json"
|
|
class="json-editor"
|
|
x-model="jsonText"
|
|
@input="validateJson()"
|
|
spellcheck="false"
|
|
placeholder='{
|
|
"{{ keySchema.HASH.name ?? 'id' }}": "my-value"{% if keySchema.RANGE is defined %},
|
|
"{{ keySchema.RANGE.name }}": "sort-value"{% endif %}
|
|
}'
|
|
></textarea>
|
|
<div x-show="jsonError" x-text="jsonError" style="color:#ef4444;font-size:0.72rem;margin-top:0.3rem;"></div>
|
|
<div x-show="!jsonError && jsonText.length > 2" style="color:var(--jormun-teal);font-size:0.72rem;margin-top:0.3rem;">
|
|
<i class="bi bi-check-circle"></i> Valid JSON
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" class="btn-jormun" :disabled="!!jsonError">
|
|
<i class="bi bi-{% if mode == 'new' %}plus-lg{% else %}check-lg{% endif %}"></i>
|
|
{{ mode == 'new' ? 'Insert Item' : 'Save Changes' }}
|
|
</button>
|
|
<a href="{{ path('table_browse', {name: tableName}) }}" class="btn-jormun-outline">
|
|
Cancel
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
|
|
<script>
|
|
document.addEventListener('alpine:init', () => {
|
|
Alpine.data('itemForm', () => ({
|
|
jsonText: JSON.stringify({{ item ? item|json_encode|raw : '{}' }}, null, 2),
|
|
jsonError: '',
|
|
validateJson() {
|
|
try {
|
|
if (this.jsonText.trim()) JSON.parse(this.jsonText);
|
|
this.jsonError = '';
|
|
} catch(e) {
|
|
this.jsonError = 'JSON error: ' + e.message;
|
|
}
|
|
},
|
|
formatJson() {
|
|
try {
|
|
const parsed = JSON.parse(this.jsonText);
|
|
this.jsonText = JSON.stringify(parsed, null, 2);
|
|
this.jsonError = '';
|
|
} catch(e) {
|
|
this.jsonError = 'Cannot format: ' + e.message;
|
|
}
|
|
},
|
|
submitForm() {
|
|
try {
|
|
JSON.parse(this.jsonText);
|
|
this.$el.submit();
|
|
} catch(e) {
|
|
this.jsonError = 'Fix JSON errors before submitting.';
|
|
}
|
|
}
|
|
}));
|
|
});
|
|
</script>
|
|
{% endblock %}
|