256 lines
8.8 KiB
TypeScript
256 lines
8.8 KiB
TypeScript
import type { FormElementDto, FormElementSectionDto, SectionSpawnTriggerDto } from '~~/.api-client'
|
|
import { VisibilityConditionOperator, VisibilityConditionType } from '~~/.api-client'
|
|
|
|
export function useSectionSpawning() {
|
|
function processSpawnTriggers(
|
|
sections: FormElementSectionDto[],
|
|
updatedFormElements: FormElementDto[]
|
|
): FormElementSectionDto[] {
|
|
let resultSections = sections
|
|
|
|
for (const formElement of updatedFormElements) {
|
|
const triggers = formElement.sectionSpawnTriggers
|
|
if (!triggers || triggers.length === 0 || !formElement.reference) {
|
|
continue
|
|
}
|
|
|
|
const triggerValue = getFormElementValue(formElement)
|
|
|
|
// Process each trigger independently
|
|
for (const trigger of triggers) {
|
|
resultSections = processSingleTrigger(resultSections, formElement, trigger, triggerValue)
|
|
}
|
|
}
|
|
|
|
return resultSections
|
|
}
|
|
|
|
function processSingleTrigger(
|
|
sections: FormElementSectionDto[],
|
|
formElement: FormElementDto,
|
|
trigger: SectionSpawnTriggerDto,
|
|
triggerValue: string
|
|
): FormElementSectionDto[] {
|
|
let resultSections = sections
|
|
const shouldSpawn = shouldSpawnSection(trigger, triggerValue)
|
|
// Find existing spawned section for this specific trigger (by template reference)
|
|
const existingSpawnedSection = findSpawnedSectionForTrigger(
|
|
resultSections,
|
|
formElement.reference!,
|
|
trigger.templateReference
|
|
)
|
|
|
|
// Handle three spawn states:
|
|
// 1. Condition met but no section spawned yet → create new section
|
|
if (shouldSpawn && !existingSpawnedSection) {
|
|
resultSections = spawnNewSection(resultSections, formElement, trigger, triggerValue)
|
|
}
|
|
// 2. Condition no longer met but section exists → remove spawned section
|
|
else if (!shouldSpawn && existingSpawnedSection) {
|
|
resultSections = removeSpawnedSectionForTrigger(resultSections, formElement.reference!, trigger.templateReference)
|
|
}
|
|
// 3. Condition still met and section exists → update section titles if value changed
|
|
else if (shouldSpawn && existingSpawnedSection && triggerValue) {
|
|
resultSections = updateSpawnedSectionTitles(resultSections, formElement.reference!, trigger, triggerValue)
|
|
}
|
|
|
|
return resultSections
|
|
}
|
|
|
|
function spawnNewSection(
|
|
sections: FormElementSectionDto[],
|
|
element: FormElementDto,
|
|
trigger: SectionSpawnTriggerDto,
|
|
triggerValue: string
|
|
): FormElementSectionDto[] {
|
|
const templateSection = findTemplateSection(sections, trigger.templateReference)
|
|
if (!templateSection) {
|
|
return sections
|
|
}
|
|
|
|
const newSection = spawnSectionFromTemplate(templateSection, element.reference!, triggerValue)
|
|
|
|
// Find template index
|
|
const templateIndex = sections.findIndex(
|
|
(s) => s.isTemplate && s.templateReference === trigger.templateReference
|
|
)
|
|
|
|
if (templateIndex === -1) {
|
|
// Fallback: append if template not found (shouldn't happen)
|
|
return sections.concat(newSection as FormElementSectionDto)
|
|
}
|
|
|
|
// Find insertion position: after template and after any existing spawned sections from same template
|
|
let insertionIndex = templateIndex + 1
|
|
while (insertionIndex < sections.length) {
|
|
const section = sections[insertionIndex]!
|
|
if (section.isTemplate || section.templateReference !== trigger.templateReference) {
|
|
break
|
|
}
|
|
insertionIndex++
|
|
}
|
|
|
|
// Insert at calculated position
|
|
const result = [...sections]
|
|
result.splice(insertionIndex, 0, newSection as FormElementSectionDto)
|
|
return result
|
|
}
|
|
|
|
function updateSpawnedSectionTitles(
|
|
sections: FormElementSectionDto[],
|
|
elementReference: string,
|
|
trigger: SectionSpawnTriggerDto,
|
|
triggerValue: string
|
|
): FormElementSectionDto[] {
|
|
const template = findTemplateSection(sections, trigger.templateReference)
|
|
if (!template) {
|
|
return sections
|
|
}
|
|
|
|
const hasTitleTemplate = template.titleTemplate
|
|
const hasShortTitleTemplate = template.shortTitle?.includes('{{triggerValue}}')
|
|
const hasDescriptionTemplate = template.description?.includes('{{triggerValue}}')
|
|
|
|
if (!hasTitleTemplate && !hasShortTitleTemplate && !hasDescriptionTemplate) {
|
|
return sections
|
|
}
|
|
|
|
return sections.map((section) => {
|
|
if (section.spawnedFromElementReference === elementReference && !section.isTemplate) {
|
|
const sectionUpdate: Partial<FormElementSectionDto> = {}
|
|
|
|
if (hasTitleTemplate) {
|
|
sectionUpdate.title = interpolateTitle(template.titleTemplate!, triggerValue)
|
|
}
|
|
|
|
if (hasShortTitleTemplate && template.shortTitle) {
|
|
sectionUpdate.shortTitle = interpolateTitle(template.shortTitle, triggerValue)
|
|
}
|
|
|
|
if (hasDescriptionTemplate && template.description) {
|
|
sectionUpdate.description = interpolateTitle(template.description, triggerValue)
|
|
}
|
|
|
|
return { ...section, ...sectionUpdate }
|
|
}
|
|
return section
|
|
})
|
|
}
|
|
|
|
function findSpawnedSectionForTrigger(
|
|
sections: FormElementSectionDto[],
|
|
elementReference: string,
|
|
templateReference: string
|
|
): FormElementSectionDto | undefined {
|
|
return sections.find(
|
|
(section) =>
|
|
!section.isTemplate &&
|
|
section.spawnedFromElementReference === elementReference &&
|
|
section.templateReference === templateReference
|
|
)
|
|
}
|
|
|
|
function removeSpawnedSectionForTrigger(
|
|
sections: FormElementSectionDto[],
|
|
elementReference: string,
|
|
templateReference: string
|
|
): FormElementSectionDto[] {
|
|
return sections.filter(
|
|
(section) =>
|
|
section.isTemplate ||
|
|
section.spawnedFromElementReference !== elementReference ||
|
|
section.templateReference !== templateReference
|
|
)
|
|
}
|
|
|
|
function spawnSectionFromTemplate(
|
|
templateSection: FormElementSectionDto,
|
|
triggerElementReference: string,
|
|
triggerValue: string
|
|
): FormElementSectionDto {
|
|
const clonedSection = JSON.parse(JSON.stringify(templateSection)) as FormElementSectionDto
|
|
|
|
const title = templateSection.titleTemplate
|
|
? interpolateTitle(templateSection.titleTemplate, triggerValue)
|
|
: templateSection.title
|
|
|
|
const shortTitle = templateSection.shortTitle?.includes('{{triggerValue}}')
|
|
? interpolateTitle(templateSection.shortTitle, triggerValue)
|
|
: templateSection.shortTitle
|
|
|
|
const description = templateSection.description?.includes('{{triggerValue}}')
|
|
? interpolateTitle(templateSection.description, triggerValue)
|
|
: templateSection.description
|
|
|
|
return {
|
|
...clonedSection,
|
|
id: undefined,
|
|
applicationFormId: undefined,
|
|
title,
|
|
shortTitle,
|
|
description,
|
|
isTemplate: false,
|
|
spawnedFromElementReference: triggerElementReference,
|
|
formElementSubSections: clonedSection.formElementSubSections.map((subsection) => ({
|
|
...subsection,
|
|
id: undefined,
|
|
formElementSectionId: undefined,
|
|
formElements: subsection.formElements.map((element) => ({
|
|
...element,
|
|
id: undefined,
|
|
formElementSubSectionId: undefined
|
|
}))
|
|
}))
|
|
}
|
|
}
|
|
|
|
function shouldSpawnSection(trigger: SectionSpawnTriggerDto, triggerElementValue: string): boolean {
|
|
const operator = trigger.sectionSpawnOperator || VisibilityConditionOperator.Equals
|
|
const isConditionMet = evaluateCondition(triggerElementValue, trigger.sectionSpawnExpectedValue || '', operator)
|
|
|
|
return trigger.sectionSpawnConditionType === VisibilityConditionType.Show ? isConditionMet : !isConditionMet
|
|
}
|
|
|
|
function findTemplateSection(
|
|
sections: FormElementSectionDto[],
|
|
templateReference: string
|
|
): FormElementSectionDto | undefined {
|
|
return sections.find((section) => section.isTemplate && section.templateReference === templateReference)
|
|
}
|
|
|
|
function evaluateCondition(
|
|
actualValue: string,
|
|
expectedValue: string,
|
|
operator: VisibilityConditionOperator
|
|
): boolean {
|
|
switch (operator) {
|
|
case VisibilityConditionOperator.Equals:
|
|
return actualValue.toLowerCase() === expectedValue.toLowerCase()
|
|
case VisibilityConditionOperator.NotEquals:
|
|
return actualValue.toLowerCase() !== expectedValue.toLowerCase()
|
|
case VisibilityConditionOperator.IsEmpty:
|
|
return actualValue === ''
|
|
case VisibilityConditionOperator.IsNotEmpty:
|
|
return actualValue !== ''
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
function interpolateTitle(titleTemplate: string, triggerValue: string): string {
|
|
return titleTemplate.replace(/\{\{triggerValue\}\}/g, triggerValue)
|
|
}
|
|
|
|
function getFormElementValue(element: FormElementDto): string {
|
|
if (element.type === 'TEXTAREA' || element.type === 'TEXTFIELD') {
|
|
return element.options[0]?.value || ''
|
|
}
|
|
const selectedOption = element.options.find((option) => option.value === 'true')
|
|
return selectedOption?.label || ''
|
|
}
|
|
|
|
return {
|
|
processSpawnTriggers
|
|
}
|
|
}
|