feat(fullstack): Add per-row visibility, add read-only cells, update seeds

This commit is contained in:
2026-03-01 09:00:07 +01:00
parent f72923f945
commit fea2ca8a3b
10 changed files with 283 additions and 106 deletions

View File

@@ -22,6 +22,8 @@
:disabled="disabled"
:can-modify-rows="canModifyRows"
:get-column-options="getColumnOptions"
:read-only-column-indices="readOnlyColumnIndices"
:is-cell-visible="isCellVisible"
@update:cell="updateCell"
@update:cell-value="updateCellValue"
@update:checkbox-cell="updateCheckboxCell"
@@ -60,6 +62,8 @@
:disabled="disabled"
:can-modify-rows="canModifyRows"
:get-column-options="getColumnOptions"
:read-only-column-indices="readOnlyColumnIndices"
:is-cell-visible="isCellVisible"
add-row-button-class="mt-4"
@update:cell="updateCell"
@update:cell-value="updateCellValue"
@@ -151,6 +155,44 @@ const visibleColumns = computed<VisibleColumn[]>(() => {
})
})
const readOnlyColumnIndices = computed<Set<number>>(() => {
if (!props.allFormElements) return new Set()
return new Set(
props.formOptions
.map((option, index) => ({ option, index }))
.filter(({ option }) => {
const conditions = option.columnConfig?.readOnlyConditions
return conditions && isFormOptionVisible(conditions, props.allFormElements!)
})
.map(({ index }) => index)
)
})
// When columns become read-only, reset their values to the configured default
watch(
readOnlyColumnIndices,
(currentSet, previousSet) => {
const newlyReadOnlyIndices = [...currentSet].filter((i) => !previousSet?.has(i))
if (newlyReadOnlyIndices.length === 0) return
const updatedOptions = props.formOptions.map((option, colIndex) => {
if (!newlyReadOnlyIndices.includes(colIndex)) return option
const columnValues = parseColumnValues(option.value)
const defaultValue = isColumnCheckbox(colIndex) ? false : (option.columnConfig?.readOnlyDefaultValue ?? '')
const newValue = JSON.stringify(columnValues.map(() => defaultValue))
return newValue !== option.value ? { ...option, value: newValue } : option
})
if (updatedOptions.some((opt, i) => opt !== props.formOptions[i])) {
emit('update:formOptions', updatedOptions)
}
},
{ immediate: true }
)
const dataColumns = computed<DataColumn[]>(() =>
visibleColumns.value.map(({ originalIndex }) => ({
key: `col_${originalIndex}`,
@@ -179,23 +221,16 @@ const tableData = computed<TableRowData[]>(() => {
if (props.formOptions.length === 0) return []
const columnData: CellValue[][] = props.formOptions.map((option, colIndex) => {
try {
const parsed = JSON.parse(option.value || '[]')
if (!Array.isArray(parsed)) return []
const parsed = parseColumnValues(option.value)
// For multi-select columns, each cell value is already an array
// For checkbox columns, each cell value is a boolean
// For single-select columns, each cell value is a string
if (isColumnMultipleAllowed(colIndex)) {
return parsed.map((val: CellValue) => (Array.isArray(val) ? val : []))
}
if (isColumnCheckbox(colIndex)) {
return parsed.map((val: CellValue) => val === true)
}
return parsed
} catch {
return []
// Normalize cell values based on column type
if (isColumnMultipleAllowed(colIndex)) {
return parsed.map((val) => (Array.isArray(val) ? val : []))
}
if (isColumnCheckbox(colIndex)) {
return parsed.map((val) => val === true)
}
return parsed
})
const rowCount = Math.max(...columnData.map((col) => col.length), 0)
@@ -266,14 +301,7 @@ function updateCell(rowIndex: number, columnKey: string, value: string) {
const updatedOptions = props.formOptions.map((option, index) => {
if (index !== colIndex) return option
let columnValues: CellValue[]
try {
columnValues = JSON.parse(option.value || '[]')
if (!Array.isArray(columnValues)) columnValues = []
} catch {
columnValues = []
}
const columnValues = parseColumnValues(option.value)
while (columnValues.length <= rowIndex) {
columnValues.push('')
}
@@ -289,14 +317,7 @@ function updateCellValue(rowIndex: number, _columnKey: string, colIndex: number,
const updatedOptions = props.formOptions.map((option, index) => {
if (index !== colIndex) return option
let columnValues: CellValue[]
try {
columnValues = JSON.parse(option.value || '[]')
if (!Array.isArray(columnValues)) columnValues = []
} catch {
columnValues = []
}
const columnValues = parseColumnValues(option.value)
const isMultiple = isColumnMultipleAllowed(colIndex)
while (columnValues.length <= rowIndex) {
columnValues.push(isMultiple ? [] : '')
@@ -313,14 +334,7 @@ function updateCheckboxCell(rowIndex: number, colIndex: number, value: boolean)
const updatedOptions = props.formOptions.map((option, index) => {
if (index !== colIndex) return option
let columnValues: CellValue[]
try {
columnValues = JSON.parse(option.value || '[]')
if (!Array.isArray(columnValues)) columnValues = []
} catch {
columnValues = []
}
const columnValues = parseColumnValues(option.value)
while (columnValues.length <= rowIndex) {
columnValues.push(false)
}
@@ -334,24 +348,18 @@ function updateCheckboxCell(rowIndex: number, colIndex: number, value: boolean)
function addRow() {
const updatedOptions = props.formOptions.map((option, colIndex) => {
let columnValues: CellValue[]
try {
columnValues = JSON.parse(option.value || '[]')
if (!Array.isArray(columnValues)) columnValues = []
} catch {
columnValues = []
}
const columnValues = parseColumnValues(option.value)
// For multi-select columns, initialize with empty array
// For checkbox columns, initialize with false
// Otherwise empty string
let emptyValue: CellValue = ''
if (isColumnMultipleAllowed(colIndex)) {
emptyValue = []
// Determine initial value based on column type
let initialValue: CellValue = ''
if (readOnlyColumnIndices.value.has(colIndex)) {
initialValue = isColumnCheckbox(colIndex) ? false : (option.columnConfig?.readOnlyDefaultValue ?? '')
} else if (isColumnMultipleAllowed(colIndex)) {
initialValue = []
} else if (isColumnCheckbox(colIndex)) {
emptyValue = false
initialValue = false
}
columnValues.push(emptyValue)
columnValues.push(initialValue)
return { ...option, value: JSON.stringify(columnValues) }
})
@@ -361,19 +369,44 @@ function addRow() {
function removeRow(rowIndex: number) {
const updatedOptions = props.formOptions.map((option) => {
let columnValues: CellValue[]
try {
columnValues = JSON.parse(option.value || '[]')
if (!Array.isArray(columnValues)) columnValues = []
} catch {
columnValues = []
}
const columnValues = parseColumnValues(option.value)
columnValues.splice(rowIndex, 1)
return { ...option, value: JSON.stringify(columnValues) }
})
emit('update:formOptions', updatedOptions)
}
function isCellVisible(colIndex: number, rowData: TableRowData): boolean {
const option = props.formOptions[colIndex]
const rowVisibility = option?.columnConfig?.rowVisibilityCondition
if (!rowVisibility) return true
const { sourceColumnIndex, expectedValues, operator } = rowVisibility
const sourceKey = `col_${sourceColumnIndex}`
const cellValue = rowData[sourceKey]
let sourceValues: string[] = []
if (Array.isArray(cellValue)) {
sourceValues = cellValue
} else if (typeof cellValue === 'string' && cellValue) {
sourceValues = cellValue.split(',').map((v) => v.trim())
}
if (operator === 'CONTAINS' || operator === 'EQUALS') {
return (expectedValues ?? []).some((expected) =>
sourceValues.some((v) => v.toLowerCase() === expected.toLowerCase())
)
}
return true
}
function parseColumnValues(value: string | undefined): CellValue[] {
try {
const parsed = JSON.parse(value || '[]')
return Array.isArray(parsed) ? parsed : []
} catch {
return []
}
}
</script>