270 lines
8.8 KiB
TypeScript
270 lines
8.8 KiB
TypeScript
import type {
|
|
FormElementDto,
|
|
FormOptionDto,
|
|
TableColumnConfigDto,
|
|
TableColumnFilterDto,
|
|
TableRowPresetDto
|
|
} from '~~/.api-client'
|
|
import { VisibilityConditionOperator as VCOperator } from '~~/.api-client'
|
|
|
|
export function useTableCrossReferences() {
|
|
// Get available values for a column that references another table's column
|
|
function getReferencedColumnValues(
|
|
columnConfig: TableColumnConfigDto | undefined,
|
|
allFormElements: FormElementDto[]
|
|
): string[] {
|
|
if (!columnConfig?.sourceTableReference || columnConfig.sourceColumnIndex === undefined) {
|
|
return []
|
|
}
|
|
|
|
const sourceTable = findTableElement(columnConfig.sourceTableReference, allFormElements)
|
|
if (!sourceTable) {
|
|
return []
|
|
}
|
|
|
|
const sourceColumn = sourceTable.options[columnConfig.sourceColumnIndex]
|
|
if (!sourceColumn) {
|
|
return []
|
|
}
|
|
|
|
const columnValues = parseColumnValues(sourceColumn.value)
|
|
|
|
// Apply filter if present
|
|
if (columnConfig.filterCondition) {
|
|
return filterColumnValues(columnValues, columnConfig.filterCondition, sourceTable)
|
|
}
|
|
|
|
return columnValues.filter((v) => v.trim() !== '')
|
|
}
|
|
|
|
// Get filtered values based on constraints from another table
|
|
// Used for cases like "Permission-ID can only use permissions allowed for the selected role"
|
|
function getConstrainedColumnValues(
|
|
columnConfig: TableColumnConfigDto | undefined,
|
|
currentRowData: Record<string, string>,
|
|
constraintTableReference: string,
|
|
constraintKeyColumnIndex: number,
|
|
constraintValueColumnIndex: number,
|
|
allFormElements: FormElementDto[],
|
|
currentRowKeyColumnIndex?: number
|
|
): string[] {
|
|
if (!columnConfig?.sourceTableReference) {
|
|
return []
|
|
}
|
|
|
|
const constraintTable = findTableElement(constraintTableReference, allFormElements)
|
|
if (!constraintTable) {
|
|
// No constraint found, return all values from source table column
|
|
return getReferencedColumnValues(columnConfig, allFormElements)
|
|
}
|
|
|
|
const lookupColumnIndex = currentRowKeyColumnIndex ?? constraintKeyColumnIndex
|
|
const keyValue = currentRowData[`col_${lookupColumnIndex}`]
|
|
if (!keyValue) {
|
|
// No key value to look up, return all values from source table column
|
|
return getReferencedColumnValues(columnConfig, allFormElements)
|
|
}
|
|
|
|
const allowedValuesRaw = getAllowedValuesFromConstraintTable(
|
|
constraintTable,
|
|
keyValue,
|
|
constraintKeyColumnIndex,
|
|
constraintValueColumnIndex
|
|
)
|
|
const allowedValues = allowedValuesRaw.flatMap((v) => (typeof v === 'boolean' ? String(v) : v))
|
|
|
|
// If no allowed values found, fall back to all values from source table
|
|
if (allowedValues.length === 0) {
|
|
return getReferencedColumnValues(columnConfig, allFormElements)
|
|
}
|
|
|
|
return allowedValues
|
|
}
|
|
|
|
// Apply row presets from a source table based on filter conditions
|
|
function applyRowPresets(
|
|
tableRowPreset: TableRowPresetDto | undefined,
|
|
targetOptions: FormOptionDto[],
|
|
allFormElements: FormElementDto[]
|
|
): FormOptionDto[] {
|
|
if (!tableRowPreset?.sourceTableReference) {
|
|
return targetOptions
|
|
}
|
|
|
|
const sourceTable = findTableElement(tableRowPreset.sourceTableReference, allFormElements)
|
|
if (!sourceTable) {
|
|
return targetOptions
|
|
}
|
|
|
|
// Get source table data
|
|
const sourceData = parseTableData(sourceTable.options)
|
|
|
|
// Filter rows based on filter condition
|
|
const filteredRows = tableRowPreset.filterCondition
|
|
? filterTableRows(sourceData, tableRowPreset.filterCondition, sourceTable.options)
|
|
: sourceData
|
|
|
|
// Apply column mappings to create preset rows in target
|
|
const columnMappings = tableRowPreset.columnMappings || []
|
|
const presetRowCount = filteredRows.length
|
|
|
|
return targetOptions.map((option, targetColIndex) => {
|
|
const mapping = columnMappings.find((m) => m.targetColumnIndex === targetColIndex)
|
|
|
|
// For mapped columns, use values from source
|
|
if (mapping && mapping.sourceColumnIndex !== undefined) {
|
|
const sourceColIndex = mapping.sourceColumnIndex
|
|
const presetValues = filteredRows.map((row) => String(row[sourceColIndex] ?? ''))
|
|
return {
|
|
...option,
|
|
value: JSON.stringify(presetValues)
|
|
}
|
|
}
|
|
|
|
// For non-mapped columns, ensure we have the right number of rows
|
|
const existingValues = parseColumnValues(option.value)
|
|
const isCheckboxColumn = option.columnConfig?.isCheckbox === true
|
|
|
|
// Pad or trim to match preset row count
|
|
const adjustedValues: (string | boolean)[] = []
|
|
for (let i = 0; i < presetRowCount; i++) {
|
|
if (i < existingValues.length && existingValues[i] !== undefined) {
|
|
adjustedValues.push(existingValues[i]!)
|
|
} else {
|
|
// Initialize new rows with appropriate default
|
|
adjustedValues.push(isCheckboxColumn ? false : '')
|
|
}
|
|
}
|
|
|
|
return {
|
|
...option,
|
|
value: JSON.stringify(adjustedValues)
|
|
}
|
|
})
|
|
}
|
|
|
|
function findTableElement(reference: string, allFormElements: FormElementDto[]): FormElementDto | undefined {
|
|
return allFormElements.find((el) => el.reference === reference && el.type === 'TABLE')
|
|
}
|
|
|
|
function parseColumnValues(jsonValue: string | undefined): string[] {
|
|
if (!jsonValue) return []
|
|
try {
|
|
const parsed = JSON.parse(jsonValue)
|
|
return Array.isArray(parsed) ? parsed : []
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
function parseTableData(options: FormOptionDto[]): (string | boolean)[][] {
|
|
const columnData = options.map((opt) => parseColumnValuesWithTypes(opt.value))
|
|
const rowCount = Math.max(...columnData.map((col) => col.length), 0)
|
|
|
|
const rows: (string | boolean)[][] = []
|
|
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
|
const row = columnData.map((col) => col[rowIndex] ?? '')
|
|
rows.push(row)
|
|
}
|
|
return rows
|
|
}
|
|
|
|
function parseColumnValuesWithTypes(jsonValue: string | undefined): (string | boolean)[] {
|
|
if (!jsonValue) return []
|
|
try {
|
|
const parsed = JSON.parse(jsonValue)
|
|
return Array.isArray(parsed) ? parsed : []
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
function filterColumnValues(
|
|
values: string[],
|
|
filterCondition: TableColumnFilterDto,
|
|
sourceTable: FormElementDto
|
|
): string[] {
|
|
if (filterCondition.sourceColumnIndex === undefined) {
|
|
return values
|
|
}
|
|
|
|
const filterColumn = sourceTable.options[filterCondition.sourceColumnIndex]
|
|
if (!filterColumn) {
|
|
return values
|
|
}
|
|
|
|
const filterColumnValues = parseColumnValues(filterColumn.value)
|
|
|
|
return values.filter((_, index) => {
|
|
const filterValue = filterColumnValues[index] || ''
|
|
return evaluateFilterCondition(filterValue, filterCondition)
|
|
})
|
|
}
|
|
|
|
function filterTableRows(
|
|
rows: (string | boolean)[][],
|
|
filterCondition: TableColumnFilterDto,
|
|
_options: FormOptionDto[]
|
|
): (string | boolean)[][] {
|
|
if (filterCondition.sourceColumnIndex === undefined) {
|
|
return rows
|
|
}
|
|
|
|
return rows.filter((row) => {
|
|
const filterValue = row[filterCondition.sourceColumnIndex!] ?? ''
|
|
return evaluateFilterCondition(filterValue, filterCondition)
|
|
})
|
|
}
|
|
|
|
function evaluateFilterCondition(actualValue: string | boolean, filterCondition: TableColumnFilterDto): boolean {
|
|
const expectedValue = filterCondition.expectedValue || ''
|
|
const operator = filterCondition.operator || VCOperator.Equals
|
|
|
|
// Handle boolean values (from checkbox columns)
|
|
const normalizedActual = typeof actualValue === 'boolean' ? String(actualValue) : actualValue
|
|
|
|
switch (operator) {
|
|
case VCOperator.Equals:
|
|
return normalizedActual.toLowerCase() === expectedValue.toLowerCase()
|
|
case VCOperator.NotEquals:
|
|
return normalizedActual.toLowerCase() !== expectedValue.toLowerCase()
|
|
case VCOperator.IsEmpty:
|
|
return normalizedActual.trim() === ''
|
|
case VCOperator.IsNotEmpty:
|
|
return normalizedActual.trim() !== ''
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
function getAllowedValuesFromConstraintTable(
|
|
constraintTable: FormElementDto,
|
|
keyValue: string,
|
|
keyColumnIndex: number,
|
|
valueColumnIndex: number
|
|
): (string | boolean | string[])[] {
|
|
const tableData = parseTableData(constraintTable.options)
|
|
const allowedValues: (string | boolean | string[])[] = []
|
|
|
|
tableData.forEach((row) => {
|
|
const keyCell = row[keyColumnIndex]
|
|
const keyCellStr = Array.isArray(keyCell) ? keyCell[0] : typeof keyCell === 'boolean' ? String(keyCell) : keyCell
|
|
|
|
if (keyCellStr?.toLowerCase() === keyValue.toLowerCase()) {
|
|
const value = row[valueColumnIndex]
|
|
if (value !== undefined && !allowedValues.includes(value)) {
|
|
allowedValues.push(value)
|
|
}
|
|
}
|
|
})
|
|
|
|
return allowedValues
|
|
}
|
|
|
|
return {
|
|
getReferencedColumnValues,
|
|
getConstrainedColumnValues,
|
|
applyRowPresets
|
|
}
|
|
}
|