Files
jormun-admin/templates/table/item_form.html.twig
2026-03-08 04:16:14 -04:00

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 %}
&nbsp;·&nbsp; 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 %}