Field Progress
Intro
Field progress contains all the values inputed by Flowcode users in the Elements. The architectural decisions for this model forms the basis for the data & structural integrity of the Flowcode app.
Field progress attributes
organisation_id: number The ID of the related organisationcatalogue_id: number The ID of the related organisation catalogueelement_id: number The ID of the related elementfield_id: number The ID of the related fieldvalue: mixed The actual field value that is stored, singularly per field. That can be used to show progress, in exports, building graphics and more.complete: boolean Whether or not the user has marked the field as complete.approved: boolean Whether or not the field has been approved (Currenly unused but added for possible future use-cases of approval).parent_id: number If the field is nested, the ID of the parent fieldparent_index: number If the parent field is part of a group repeater, the index of the parent field in the repeatercurrent_index: number If the field is part of group repeater, the index of the field in the repeater
Field progress persistance
Field progress persistence is handled in the CatalogueElementFormField.vue component. This component both collects existing and stores new field progress records.
Collecting unique field progress records
Collection of field progress records is handled via axios API using the progress() method in the ApiElementFieldController. The key data in the request are:
organisation_idcatalogue_idelement_idfield_idparent_idparent_indexcurrent_index
Storing field progress records
Collection of field progress records is handled via an Inertia POST using the store() method in the CatalogueElementFieldProgressController. The key data in the request are:
organisation_idcatalogue_idelement_idfield_idvaluecompleteapprovedparent_idparent_indexcurrent_index
Handling group fields
Group fields require specific data to map the correct field progress data to the CatalogueElementFormField.vue component.
- The
FieldGroupWrapper.vuecomponent handles passing the relevant parent_id values to the children fields - The
FieldGroupRepeaterWrapper.vuecomponent handles passing the relevant parent_id & index values to the children fields
Determining existing nested counts
On the front-end, a field group repeater field type makes an axios API call to fetch the indices of existing items. This is crucial for correctly displaying the repeater items after potential deletions, ensuring no empty slots appear for deleted items.
This is handled by the getNestedIndices() method in the ApiElementFieldController class (previously nestedCount). Instead of returning a count based on the maximum index, it returns an array of the distinct current_index values for progress records that are associated with the repeater field (parent_id) and have not been soft-deleted (deleted_at is null).
This ensures that the frontend (FieldGroupRepeaterWrapper.vue) only renders groups for indices that actually exist in the database.
public function getNestedIndices(Organisation $organisation, Catalogue $catalogue, Element $element, Field $field, Request $request): JsonResponse
{
$parent_index = $request->query('parent_index');
// Determine context (individual/department)
$individual = null;
$department = null;
if ($catalogue->progress_type === CatalogueProgressTypes::INDIVIDUAL) {
$individual = (new CurrentIndividualHelper)->get();
} elseif ($catalogue->progress_type === CatalogueProgressTypes::DEPARTMENT) {
$department = (new CurrentDepartmentHelper)->get();
}
// Find distinct current_index values for progress records directly associated
// with this repeater field as the parent_id.
$indices = FieldProgress::where('organisation_id', $organisation->id)
->where('element_id', $element->id)
->where('parent_id', $field->id) // Progress linked to the repeater field itself
->where('parent_index', $parent_index) // Correctly handle nesting context
->when($department, fn($q) => $q->where('department_id', $department->id))
->when($individual, fn($q) => $q->where('individual_id', $individual->id))
->whereNotNull('current_index') // Only care about records that are part of the repeater sequence
->whereNull('deleted_at') // Explicitly exclude soft-deleted records
->distinct()
->orderBy('current_index', 'asc')
->pluck('current_index');
// Return the array of existing indices (e.g., [0, 1, 3, 4] if 2 was deleted)
return response()->json($indices);
}
Deleting Repeater Item Progress
When a user removes a repeater item in the UI (FieldGroupRepeaterWrapper.vue), a DELETE request is sent to the deleteRepeaterItemProgress method in the web controller (CatalogueElementFieldProgressController).
This method identifies the correct FieldProgress records based on organisation_id, element_id, parent_id (the repeater field's ID), parent_index (if nested), and the specific current_index of the item being removed.
It then performs a soft delete (->delete()) on these records, setting the deleted_at timestamp. The getNestedIndices API method (described above) respects this by filtering out records where deleted_at is not null, ensuring deleted items don't reappear on page refresh.
Display of field progress to admins on the front-end
From time to time admins will want to modify fields. Before they do it is helpful for them to understand the extent of any progress already made in the field by users, as changes to the structure or settings of a field could result in data loss for the user.
Admins can view detail about a field's progress by clicking on the progress badge for the field From the Elements Page.

This will launch the Field Progress Modal where detail of the organisations, values, and completion statuses will be shown:

Key parts of this feature
As a developer involved in the maintenance and/or extension of this feature it will be helpful to understand the main parts involved:
viewFieldProgress() method of ElementTabFieldController.php
Accepts three parameters: an Element object, a string representing the active tab, and a Field object. It retrieves the parent field of the given field, fetches progress records related to the field including related organisation and element data, and returns an router.js modal with the field progress details. The method also provides a list of selectable field types depending on whether the parent field has its own parent or not. The modal's base route is set to 'admin.elements.tab' with the given element ID and active tab.
viewProgressModal() method of ElementTabFieldList.vue
In the Elements Page of the Admin Area this method is called when the progress badge for the field is clicked by users. If the field is not of type field-group or field-group-repeater it will load the field progress modal. We don't want the modal to show for field types field-group or field-group-repeater due to their parent nature, we only want it to show for their children fields (should they have saved progress).
ViewFieldProgressModal.vue, FieldProgressTable.vue, FieldProgressTableRow.vue
Our standard Vue components for the modal, and the table and table rows displayed within the modal.
Note the formattedValue computed property (and related functions it calls) in FieldProgressTableRow.vue. As the progress values are stored as strings in the database some formatting is necessary to avoid admins having to visually parse unfriendly formatting such as array syntax for checkbox fields and unstructured table data for table fields. Formatting is also performed on progress values for date and file-upload fields for similar reasons.