Files
enviPy-bayer/templates/modals/objects/update_scenario_additional_information_modal.html
Tobias O d80dfb5ee3 [Feature] Dynamic additional information rendering in frontend (#282)
This implements a version of #274, relying on Pydantics built in JSON schema and JSON rendering.
Requires additional UI tagging in the ai model repo but will remove HTML tags.

Example scenario with filled information: 5882df9c-dae1-4d80-a40e-db4724271456/scenario/3a4d395a-6a6d-4154-8ce3-ced667fceec0

Reviewed-on: enviPath/enviPy#282
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
2026-01-31 00:44:03 +13:00

187 lines
5.5 KiB
HTML

{% load static %}
<dialog
id="update_scenario_additional_information_modal"
class="modal"
x-data="{
isSubmitting: false,
items: [],
schemas: {},
loading: false,
error: null,
originalItems: [], // Store original data to detect changes
modifiedUuids: new Set(), // Track which items were modified
async init() {
try {
this.loading = true;
const scenarioUuid = '{{ scenario.uuid }}';
const { items, schemas } =
await window.AdditionalInformationApi.loadSchemasAndItems(scenarioUuid);
this.items = items;
this.schemas = schemas;
// Store deep copy of original items for comparison
this.originalItems = JSON.parse(JSON.stringify(items));
} catch (err) {
this.error = err.message;
} finally {
this.loading = false;
}
},
reset() {
this.isSubmitting = false;
this.error = null;
this.modifiedUuids.clear();
},
updateItemData(uuid, data) {
// Update the item's data in the items array
const item = this.items.find(i => i.uuid === uuid);
if (item) {
item.data = data;
// Mark this item as modified
this.modifiedUuids.add(uuid);
}
},
async submit() {
if (this.items.length === 0) {
this.error = 'No data to update';
return;
}
// Filter to only items that were actually modified
const modifiedItems = this.items.filter(item => this.modifiedUuids.has(item.uuid));
if (modifiedItems.length === 0) {
this.error = 'No changes to save';
return;
}
this.isSubmitting = true;
this.error = null;
try {
const scenarioUuid = '{{ scenario.uuid }}';
// Use the unified API client for sequential, safe updates - only modified items
await window.AdditionalInformationApi.updateItems(scenarioUuid, modifiedItems);
// Close modal and reload page
document.getElementById('update_scenario_additional_information_modal').close();
window.location.reload();
} catch (err) {
// Handle validation errors with field-level details
if (err.isValidationError && err.fieldErrors) {
this.error = err.message;
// Dispatch event to set field errors in the specific form
if (err.itemUuid) {
window.dispatchEvent(new CustomEvent('set-field-errors-for-item', {
detail: {
uuid: err.itemUuid,
fieldErrors: err.fieldErrors
}
}));
}
} else {
this.error = err.message;
}
} finally {
this.isSubmitting = false;
}
}
}"
@close="reset()"
@update-item-data.window="updateItemData($event.detail.uuid, $event.detail.data)"
>
<div class="modal-box max-h-[90vh] max-w-4xl overflow-y-auto">
<!-- Header -->
<h3 class="text-lg font-bold">Update Additional Information</h3>
<!-- Close button (X) -->
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<!-- Loading state -->
{% include "components/modals/loading_state.html" with loading_var="loading" %}
<!-- Error state -->
{% include "components/modals/error_state.html" with error_var="error" %}
<!-- Items list -->
<template x-if="!loading">
<div class="space-y-4">
<template x-if="items.length === 0">
<p class="text-base-content/60">
No additional information to update.
</p>
</template>
<template x-for="(item, index) in items" :key="item.uuid">
<div class="card bg-base-200 shadow-sm">
<div class="card-body p-4">
<div
x-data="schemaRenderer({
rjsf: schemas[item.type.toLowerCase()],
data: item.data,
mode: 'edit'
})"
x-init="await init(); $watch('data', (value) => { $dispatch('update-item-data', { uuid: item.uuid, data: value }) }, { deep: true })"
>
{% include "components/schema_form.html" %}
</div>
</div>
</div>
</template>
</div>
</template>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit()"
:disabled="isSubmitting || loading || items.length === 0 || modifiedUuids.size === 0"
>
<span x-show="!isSubmitting">
<template x-if="modifiedUuids.size > 0">
<span x-text="`Update (${modifiedUuids.size})`"></span>
</template>
<template x-if="modifiedUuids.size === 0">
<span>No Changes</span>
</template>
</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Updating...</span>
</button>
</div>
</div>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>