feat(fullstack): Add logical AND and OR operators to seed files, add Sensitiviäts-Check
This commit is contained in:
@@ -78,6 +78,7 @@
|
||||
import type { FormElementDto, FormOptionDto, TableRowPresetDto } from '~~/.api-client'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
import { useTableCrossReferences } from '~/composables/useTableCrossReferences'
|
||||
import { useFormElementVisibility } from '~/composables/useFormElementVisibility'
|
||||
|
||||
const props = defineProps<{
|
||||
formOptions: FormOptionDto[]
|
||||
@@ -86,6 +87,8 @@ const props = defineProps<{
|
||||
tableRowPreset?: TableRowPresetDto
|
||||
}>()
|
||||
|
||||
const { isFormOptionVisible } = useFormElementVisibility()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:formOptions', value: FormOptionDto[]): void
|
||||
}>()
|
||||
@@ -131,16 +134,33 @@ interface DataColumn {
|
||||
colIndex: number
|
||||
}
|
||||
|
||||
// Filter columns based on visibility conditions
|
||||
interface VisibleColumn {
|
||||
option: FormOptionDto
|
||||
originalIndex: number
|
||||
}
|
||||
|
||||
const visibleColumns = computed<VisibleColumn[]>(() => {
|
||||
return props.formOptions
|
||||
.map((option, index) => ({ option, originalIndex: index }))
|
||||
.filter(({ option }) => {
|
||||
if (!option.visibilityConditions || !props.allFormElements) {
|
||||
return true
|
||||
}
|
||||
return isFormOptionVisible(option.visibilityConditions, props.allFormElements)
|
||||
})
|
||||
})
|
||||
|
||||
const dataColumns = computed<DataColumn[]>(() =>
|
||||
props.formOptions.map((_, index) => ({
|
||||
key: `col_${index}`,
|
||||
colIndex: index
|
||||
visibleColumns.value.map(({ originalIndex }) => ({
|
||||
key: `col_${originalIndex}`,
|
||||
colIndex: originalIndex
|
||||
}))
|
||||
)
|
||||
|
||||
const tableColumns = computed<TableColumn<TableRowData>[]>(() => {
|
||||
const columns: TableColumn<TableRowData>[] = props.formOptions.map((option, index) => ({
|
||||
accessorKey: `col_${index}`,
|
||||
const columns: TableColumn<TableRowData>[] = visibleColumns.value.map(({ option, originalIndex }) => ({
|
||||
accessorKey: `col_${originalIndex}`,
|
||||
header: option.label || ''
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import type { FormElementDto, FormElementVisibilityCondition, VisibilityConditionOperator } from '~~/.api-client'
|
||||
import type { FormElementDto, VisibilityConditionGroup, VisibilityConditionNode } from '~~/.api-client'
|
||||
import {
|
||||
VisibilityConditionOperator as VCOperator,
|
||||
VisibilityConditionType as VCType,
|
||||
VisibilityConditionNodeNodeTypeEnum as VCNodeTypeEnum,
|
||||
VisibilityConditionGroupOperatorEnum as VCGroupOperatorEnum,
|
||||
VisibilityConditionNodeGroupOperatorEnum as VCNodeGroupOperatorEnum,
|
||||
VisibilityConditionOperator as VCOperator,
|
||||
FormElementType
|
||||
} from '~~/.api-client'
|
||||
|
||||
export function useFormElementVisibility() {
|
||||
/**
|
||||
* Evaluates visibility for all form elements based on their visibility conditions.
|
||||
* Returns a map of element key (id or reference) to visibility status.
|
||||
*/
|
||||
function evaluateFormElementVisibility(allFormElements: FormElementDto[]): Map<string, boolean> {
|
||||
const formElementsByRef = buildFormElementsMap(allFormElements)
|
||||
const visibilityMap = new Map<string, boolean>()
|
||||
@@ -25,6 +24,22 @@ export function useFormElementVisibility() {
|
||||
return visibilityMap
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates visibility conditions for a FormOption (e.g., table column).
|
||||
* Unlike evaluateFormElementVisibility which works on FormElements,
|
||||
* this evaluates standalone condition groups for options/columns.
|
||||
*/
|
||||
function isFormOptionVisible(
|
||||
conditions: VisibilityConditionGroup | undefined,
|
||||
allFormElements: FormElementDto[]
|
||||
): boolean {
|
||||
if (!conditions || !conditions.conditions || conditions.conditions.length === 0) {
|
||||
return true
|
||||
}
|
||||
const formElementsByRef = buildFormElementsMap(allFormElements)
|
||||
return evaluateGroup(conditions, formElementsByRef)
|
||||
}
|
||||
|
||||
function buildFormElementsMap(formElements: FormElementDto[]): Map<string, FormElementDto> {
|
||||
const map = new Map<string, FormElementDto>()
|
||||
formElements.forEach((element) => {
|
||||
@@ -35,96 +50,104 @@ export function useFormElementVisibility() {
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates if an element is visible based on its visibility conditions.
|
||||
* Multiple conditions use AND logic - all conditions must be met for the element to be visible.
|
||||
*/
|
||||
function isElementVisible(element: FormElementDto, formElementsByRef: Map<string, FormElementDto>): boolean {
|
||||
const conditions = element.visibilityConditions
|
||||
if (!conditions || conditions.length === 0) {
|
||||
const group = element.visibilityConditions
|
||||
if (!group || !group.conditions || group.conditions.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
// All conditions must be met (AND logic)
|
||||
return conditions.every((condition) => evaluateSingleCondition(condition, formElementsByRef))
|
||||
return evaluateGroup(group, formElementsByRef)
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates a single visibility condition against the form state.
|
||||
*/
|
||||
function evaluateSingleCondition(
|
||||
condition: FormElementVisibilityCondition,
|
||||
function evaluateGroup(group: VisibilityConditionGroup, formElementsByRef: Map<string, FormElementDto>): boolean {
|
||||
if (!group.conditions || group.conditions.length === 0) {
|
||||
return true
|
||||
}
|
||||
const results = group.conditions.map((c) => evaluateNode(c, formElementsByRef))
|
||||
return group.operator === VCGroupOperatorEnum.And ? results.every(Boolean) : results.some(Boolean)
|
||||
}
|
||||
|
||||
function evaluateNode(node: VisibilityConditionNode, formElementsByRef: Map<string, FormElementDto>): boolean {
|
||||
if (node.nodeType === VCNodeTypeEnum.Group) {
|
||||
return evaluateNodeGroup(node, formElementsByRef)
|
||||
}
|
||||
return evaluateLeafCondition(node, formElementsByRef)
|
||||
}
|
||||
|
||||
function evaluateNodeGroup(node: VisibilityConditionNode, formElementsByRef: Map<string, FormElementDto>): boolean {
|
||||
if (!node.conditions || node.conditions.length === 0) {
|
||||
return true
|
||||
}
|
||||
const results = node.conditions.map((c) => evaluateNode(c, formElementsByRef))
|
||||
return node.groupOperator === VCNodeGroupOperatorEnum.And ? results.every(Boolean) : results.some(Boolean)
|
||||
}
|
||||
|
||||
function evaluateLeafCondition(
|
||||
leaf: VisibilityConditionNode,
|
||||
formElementsByRef: Map<string, FormElementDto>
|
||||
): boolean {
|
||||
const sourceElement = formElementsByRef.get(condition.sourceFormElementReference)
|
||||
if (!leaf.sourceFormElementReference) {
|
||||
return false
|
||||
}
|
||||
const sourceElement = formElementsByRef.get(leaf.sourceFormElementReference)
|
||||
if (!sourceElement) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Special handling for CHECKBOX with multiple options
|
||||
if (sourceElement.type === FormElementType.Checkbox && sourceElement.options.length > 1) {
|
||||
const operator = condition.formElementOperator || VCOperator.Equals
|
||||
const conditionMet = evaluateCheckboxCondition(sourceElement, condition.formElementExpectedValue || '', operator)
|
||||
return condition.formElementConditionType === VCType.Show ? conditionMet : !conditionMet
|
||||
const operator = leaf.formElementOperator || VCOperator.Equals
|
||||
const conditionMet = evaluateCheckboxCondition(sourceElement, leaf.formElementExpectedValue || '', operator)
|
||||
return leaf.formElementConditionType === VCType.Hide ? !conditionMet : conditionMet
|
||||
}
|
||||
|
||||
const sourceValue = getFormElementValue(sourceElement)
|
||||
const operator = condition.formElementOperator || VCOperator.Equals
|
||||
const conditionMet = evaluateCondition(sourceValue, condition.formElementExpectedValue || '', operator)
|
||||
const operator = leaf.formElementOperator || VCOperator.Equals
|
||||
const conditionMet = evaluateCondition(sourceValue, leaf.formElementExpectedValue || '', operator)
|
||||
|
||||
return condition.formElementConditionType === VCType.Show ? conditionMet : !conditionMet
|
||||
return leaf.formElementConditionType === VCType.Hide ? !conditionMet : conditionMet
|
||||
}
|
||||
|
||||
function getFormElementValue(element: FormElementDto): string {
|
||||
// For CHECKBOX with a single option, return the value directly
|
||||
if (element.type === FormElementType.Checkbox && element.options.length === 1) {
|
||||
return element.options[0]?.value || ''
|
||||
}
|
||||
|
||||
// For other element types (RADIOBUTTON, SELECT, etc.), find the selected option and return its label
|
||||
const selectedOption = element.options.find((option) => option.value === 'true')
|
||||
return selectedOption?.label || ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates visibility condition for CHECKBOX with multiple options.
|
||||
* Checks if ANY of the selected checkboxes matches the expected value.
|
||||
*/
|
||||
function evaluateCheckboxCondition(
|
||||
element: FormElementDto,
|
||||
expectedValue: string,
|
||||
operator: VisibilityConditionOperator
|
||||
): boolean {
|
||||
function evaluateCheckboxCondition(element: FormElementDto, expectedValue: string, operator: string): boolean {
|
||||
const selectedLabels = element.options.filter((option) => option.value === 'true').map((option) => option.label)
|
||||
|
||||
switch (operator) {
|
||||
case VCOperator.Equals:
|
||||
// Check if any selected checkbox label matches the expected value
|
||||
return selectedLabels.some((label) => label.toLowerCase() === expectedValue.toLowerCase())
|
||||
case VCOperator.NotEquals:
|
||||
// Check if no selected checkbox label matches the expected value
|
||||
return !selectedLabels.some((label) => label.toLowerCase() === expectedValue.toLowerCase())
|
||||
case VCOperator.Contains:
|
||||
return selectedLabels.some((label) => label.toLowerCase() === expectedValue.toLowerCase())
|
||||
case VCOperator.NotContains:
|
||||
return !selectedLabels.some((label) => label.toLowerCase() === expectedValue.toLowerCase())
|
||||
case VCOperator.IsEmpty:
|
||||
// Check if no checkboxes are selected
|
||||
return selectedLabels.length === 0
|
||||
case VCOperator.IsNotEmpty:
|
||||
// Check if at least one checkbox is selected
|
||||
return selectedLabels.length > 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateCondition(
|
||||
actualValue: string,
|
||||
expectedValue: string,
|
||||
operator: VisibilityConditionOperator
|
||||
): boolean {
|
||||
function evaluateCondition(actualValue: string, expectedValue: string, operator: string): boolean {
|
||||
switch (operator) {
|
||||
case VCOperator.Equals:
|
||||
return actualValue.toLowerCase() === expectedValue.toLowerCase()
|
||||
case VCOperator.NotEquals:
|
||||
return actualValue.toLowerCase() !== expectedValue.toLowerCase()
|
||||
case VCOperator.Contains:
|
||||
return actualValue.toLowerCase().includes(expectedValue.toLowerCase())
|
||||
case VCOperator.NotContains:
|
||||
return !actualValue.toLowerCase().includes(expectedValue.toLowerCase())
|
||||
case VCOperator.IsEmpty:
|
||||
return actualValue === ''
|
||||
case VCOperator.IsNotEmpty:
|
||||
@@ -135,6 +158,7 @@ export function useFormElementVisibility() {
|
||||
}
|
||||
|
||||
return {
|
||||
evaluateFormElementVisibility
|
||||
evaluateFormElementVisibility,
|
||||
isFormOptionVisible
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user