[Feature] Adds timeseries display (#313)

Adds a way to input/display timeseries data to the additional information

Reviewed-on: enviPath/enviPy#313
Reviewed-by: jebus <lorsbach@envipath.com>
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
This commit is contained in:
2026-02-04 01:01:06 +13:00
committed by jebus
parent d80dfb5ee3
commit dc18b73e08
23 changed files with 1772 additions and 411 deletions

View File

@ -18,7 +18,9 @@
const names = Object.keys(this.schemas);
// Remove duplicates, exclude existing types, and sort alphabetically by display title
const unique = [...new Set(names)];
const available = unique.filter(name => !this.existingTypes.includes(name));
const available = unique.filter(name =>
!this.existingTypes.includes(name) || this.schemas[name]?.schema?.['x-repeatable']
);
return available.sort((a, b) => {
const titleA = (this.schemas[a]?.schema?.['x-title'] || a).toLowerCase();
const titleB = (this.schemas[b]?.schema?.['x-title'] || b).toLowerCase();
@ -32,6 +34,9 @@
// Reset formData when type changes and increment key to force re-render
this.formData = null;
this.formRenderKey++;
// Clear previous errors
this.error = null;
Alpine.store('validationErrors').clearErrors(); // No context - clears all
});
// Load schemas and existing items
@ -63,6 +68,7 @@
this.selectedType = '';
this.error = null;
this.formData = null;
Alpine.store('validationErrors').clearErrors(); // No context - clears all
},
setFormData(data) {
@ -96,11 +102,12 @@
window.location.reload();
} catch (err) {
if (err.isValidationError && err.fieldErrors) {
window.dispatchEvent(new CustomEvent('set-field-errors', {
detail: err.fieldErrors
}));
// No context for add modal - simple flat errors
Alpine.store('validationErrors').setErrors(err.fieldErrors);
this.error = err.message || 'Please correct the errors in the form';
} else {
this.error = err.message || 'An error occurred. Please try again.';
}
this.error = err.message;
} finally {
this.isSubmitting = false;
}
@ -155,7 +162,7 @@
<template x-for="name in sortedSchemaNames" :key="name">
<option
:value="name"
x-text="(schemas[name].schema && schemas[name].schema['x-title']) || name"
x-text="(schemas[name].schema && (schemas[name].schema['x-title'] || schemas[name].schema.title)) || name"
></option>
</template>
</select>
@ -169,6 +176,7 @@
x-data="schemaRenderer({
rjsf: schemas[selectedType],
mode: 'edit'
// No context - single form, backward compatible
})"
x-init="await init(); $dispatch('form-data-ready', data)"
>

View File

@ -18,10 +18,10 @@
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));
this.items = items;
} catch (err) {
this.error = err.message;
} finally {
@ -33,6 +33,7 @@
this.isSubmitting = false;
this.error = null;
this.modifiedUuids.clear();
Alpine.store('validationErrors').clearErrors(); // Clear all contexts
},
updateItemData(uuid, data) {
@ -74,18 +75,15 @@
} 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
}
}));
}
this.error = err.message || 'Please correct the errors in the form';
// Backend returns errors keyed by UUID, each with field-level error arrays
// Set errors for each item with its UUID as context
Object.entries(err.fieldErrors).forEach(([uuid, fieldErrors]) => {
Alpine.store('validationErrors').setErrors(fieldErrors, uuid);
});
} else {
this.error = err.message;
this.error = err.message || 'An error occurred. Please try again.';
}
} finally {
this.isSubmitting = false;
@ -133,7 +131,8 @@
x-data="schemaRenderer({
rjsf: schemas[item.type.toLowerCase()],
data: item.data,
mode: 'edit'
mode: 'edit',
context: item.uuid // Pass item UUID as context for error scoping
})"
x-init="await init(); $watch('data', (value) => { $dispatch('update-item-data', { uuid: item.uuid, data: value }) }, { deep: true })"
>