feat(frontend): Add table modal
This commit is contained in:
@@ -1,73 +1,82 @@
|
||||
<template>
|
||||
<div class="space-y-3">
|
||||
<UTable :data="tableData" :columns="tableColumns" class="w-full" :ui="{ td: 'p-2' }">
|
||||
<template v-for="col in dataColumns" :key="col.key" #[`${col.key}-cell`]="slotProps">
|
||||
<!-- Column with cross-reference -->
|
||||
<USelectMenu
|
||||
v-if="hasColumnReference(col.colIndex) && !isColumnReadOnly(col.colIndex)"
|
||||
:model-value="getCellValueForSelect(slotProps.row as TableRow<TableRowData>, col.key, col.colIndex)"
|
||||
:items="getColumnOptions(col.colIndex, (slotProps.row as TableRow<TableRowData>).original)"
|
||||
:disabled="disabled"
|
||||
:placeholder="$t('applicationForms.formElements.table.selectValue')"
|
||||
:multiple="isColumnMultipleAllowed(col.colIndex)"
|
||||
class="w-full min-w-32"
|
||||
@update:model-value="
|
||||
(val: string | string[]) =>
|
||||
updateCellValue((slotProps.row as TableRow<TableRowData>).index, col.key, col.colIndex, val)
|
||||
"
|
||||
/>
|
||||
<!-- Read-only column -->
|
||||
<span v-else-if="isColumnReadOnly(col.colIndex)" class="text-muted px-2 py-1">
|
||||
{{ formatCellDisplay(slotProps.row as any, col.key, col.colIndex) }}
|
||||
</span>
|
||||
<!-- Checkbox column -->
|
||||
<div v-else-if="isColumnCheckbox(col.colIndex)" class="flex justify-center">
|
||||
<UCheckbox
|
||||
:model-value="getCellValueForCheckbox(slotProps.row as TableRow<TableRowData>, col.key)"
|
||||
:disabled="disabled"
|
||||
@update:model-value="
|
||||
(val: boolean | 'indeterminate') =>
|
||||
updateCheckboxCell((slotProps.row as TableRow<TableRowData>).index, col.colIndex, val === true)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<!-- Regular text input -->
|
||||
<UInput
|
||||
v-else
|
||||
:model-value="getCellValue(slotProps.row as TableRow<TableRowData>, col.key)"
|
||||
:disabled="disabled"
|
||||
class="w-full min-w-32"
|
||||
@update:model-value="
|
||||
(val: string | number) => updateCell((slotProps.row as TableRow<TableRowData>).index, col.key, String(val))
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="canModifyRows" #actions-cell="{ row }">
|
||||
<div class="flex justify-end">
|
||||
<UTooltip :text="$t('applicationForms.formElements.table.enlargeTable')">
|
||||
<UButton
|
||||
v-if="!disabled"
|
||||
icon="i-lucide-trash-2"
|
||||
color="error"
|
||||
icon="i-lucide-maximize-2"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
:aria-label="$t('applicationForms.formElements.table.removeRow')"
|
||||
@click="removeRow(row.index)"
|
||||
:aria-label="$t('applicationForms.formElements.table.enlargeTable')"
|
||||
@click="fullscreenOpen = true"
|
||||
/>
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
<div v-if="tableData.length === 0" class="text-center text-dimmed py-4">
|
||||
{{ $t('applicationForms.formElements.table.noData') }}
|
||||
</UTooltip>
|
||||
</div>
|
||||
|
||||
<UButton v-if="!disabled && canModifyRows" variant="outline" size="sm" leading-icon="i-lucide-plus" @click="addRow">
|
||||
{{ $t('applicationForms.formElements.table.addRow') }}
|
||||
</UButton>
|
||||
<!-- Regular table -->
|
||||
<TheTableContent
|
||||
:table-data="tableData"
|
||||
:table-columns="tableColumns"
|
||||
:data-columns="dataColumns"
|
||||
:form-options="formOptions"
|
||||
:disabled="disabled"
|
||||
:can-modify-rows="canModifyRows"
|
||||
:get-column-options="getColumnOptions"
|
||||
@update:cell="updateCell"
|
||||
@update:cell-value="updateCellValue"
|
||||
@update:checkbox-cell="updateCheckboxCell"
|
||||
@add-row="addRow"
|
||||
@remove-row="removeRow"
|
||||
/>
|
||||
|
||||
<!-- Fullscreen Modal -->
|
||||
<UModal
|
||||
v-model:open="fullscreenOpen"
|
||||
:title="$t('applicationForms.formElements.table.enlargeTable')"
|
||||
class="min-w-3/4"
|
||||
>
|
||||
<template #content>
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- Modal header -->
|
||||
<div class="flex items-center justify-between p-4 border-b border-default">
|
||||
<h2 class="text-lg font-semibold">{{ $t('applicationForms.formElements.table.editTable') }}</h2>
|
||||
<UButton
|
||||
icon="i-lucide-x"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
:aria-label="$t('common.close')"
|
||||
@click="fullscreenOpen = false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Modal body with full-width table -->
|
||||
<div class="flex-1 overflow-auto p-4">
|
||||
<TheTableContent
|
||||
:table-data="tableData"
|
||||
:table-columns="tableColumns"
|
||||
:data-columns="dataColumns"
|
||||
:form-options="formOptions"
|
||||
:disabled="disabled"
|
||||
:can-modify-rows="canModifyRows"
|
||||
:get-column-options="getColumnOptions"
|
||||
add-row-button-class="mt-4"
|
||||
@update:cell="updateCell"
|
||||
@update:cell-value="updateCellValue"
|
||||
@update:checkbox-cell="updateCheckboxCell"
|
||||
@add-row="addRow"
|
||||
@remove-row="removeRow"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormElementDto, FormOptionDto, TableRowPresetDto } from '~~/.api-client'
|
||||
import type { TableColumn, TableRow } from '@nuxt/ui'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
import { useTableCrossReferences } from '~/composables/useTableCrossReferences'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -83,6 +92,8 @@ const emit = defineEmits<{
|
||||
|
||||
const { getReferencedColumnValues, getConstrainedColumnValues, applyRowPresets } = useTableCrossReferences()
|
||||
|
||||
const fullscreenOpen = ref(false)
|
||||
|
||||
const canModifyRows = computed(() => {
|
||||
if (!props.tableRowPreset) return true
|
||||
return props.tableRowPreset.canAddRows !== false
|
||||
@@ -172,7 +183,7 @@ const tableData = computed<TableRowData[]>(() => {
|
||||
const rows: TableRowData[] = []
|
||||
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
||||
const row: TableRowData = {}
|
||||
props.formOptions.forEach((option, colIndex) => {
|
||||
props.formOptions.forEach((_, colIndex) => {
|
||||
const cellValue = columnData[colIndex]?.[rowIndex]
|
||||
if (isColumnMultipleAllowed(colIndex)) {
|
||||
row[`col_${colIndex}`] = Array.isArray(cellValue) ? cellValue : []
|
||||
@@ -188,16 +199,6 @@ const tableData = computed<TableRowData[]>(() => {
|
||||
return rows
|
||||
})
|
||||
|
||||
function hasColumnReference(colIndex: number): boolean {
|
||||
const option = props.formOptions[colIndex]
|
||||
return !!option?.columnConfig?.sourceTableReference
|
||||
}
|
||||
|
||||
function isColumnReadOnly(colIndex: number): boolean {
|
||||
const option = props.formOptions[colIndex]
|
||||
return option?.columnConfig?.isReadOnly === true
|
||||
}
|
||||
|
||||
function isColumnMultipleAllowed(colIndex: number): boolean {
|
||||
const option = props.formOptions[colIndex]
|
||||
return option?.columnConfig?.isMultipleAllowed === true
|
||||
@@ -239,32 +240,6 @@ function getColumnOptions(colIndex: number, currentRowData?: TableRowData): stri
|
||||
return getReferencedColumnValues(columnConfig, props.allFormElements)
|
||||
}
|
||||
|
||||
function getCellValue(row: TableRow<TableRowData>, columnKey: string): string {
|
||||
const value = row.original[columnKey]
|
||||
return typeof value === 'string' ? value : ''
|
||||
}
|
||||
|
||||
function getCellValueForSelect(row: TableRow<TableRowData>, columnKey: string, colIndex: number): string | string[] {
|
||||
const value = row.original[columnKey]
|
||||
if (isColumnMultipleAllowed(colIndex)) {
|
||||
return Array.isArray(value) ? value : []
|
||||
}
|
||||
return typeof value === 'string' ? value : ''
|
||||
}
|
||||
|
||||
function getCellValueForCheckbox(row: TableRow<TableRowData>, columnKey: string): boolean {
|
||||
const value = row.original[columnKey]
|
||||
return value === true
|
||||
}
|
||||
|
||||
function formatCellDisplay(row: TableRow<TableRowData>, columnKey: string, colIndex: number): string {
|
||||
const value = row.original[columnKey]
|
||||
if (isColumnMultipleAllowed(colIndex) && Array.isArray(value)) {
|
||||
return value.length > 0 ? value.join(', ') : '-'
|
||||
}
|
||||
return (typeof value === 'string' ? value : '') || '-'
|
||||
}
|
||||
|
||||
function updateCell(rowIndex: number, columnKey: string, value: string) {
|
||||
const colIndex = parseInt(columnKey.replace('col_', ''), 10)
|
||||
|
||||
@@ -290,7 +265,7 @@ function updateCell(rowIndex: number, columnKey: string, value: string) {
|
||||
emit('update:formOptions', updatedOptions)
|
||||
}
|
||||
|
||||
function updateCellValue(rowIndex: number, columnKey: string, colIndex: number, value: string | string[]) {
|
||||
function updateCellValue(rowIndex: number, _columnKey: string, colIndex: number, value: string | string[]) {
|
||||
const updatedOptions = props.formOptions.map((option, index) => {
|
||||
if (index !== colIndex) return option
|
||||
|
||||
|
||||
160
legalconsenthub/app/components/formelements/TheTableContent.vue
Normal file
160
legalconsenthub/app/components/formelements/TheTableContent.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div>
|
||||
<UTable :data="tableData" :columns="tableColumns" class="w-full" :ui="{ td: 'p-2' }">
|
||||
<template v-for="col in dataColumns" :key="col.key" #[`${col.key}-cell`]="slotProps">
|
||||
<!-- Column with cross-reference -->
|
||||
<USelectMenu
|
||||
v-if="hasColumnReference(col.colIndex)"
|
||||
:model-value="getCellValueForSelect(slotProps.row as TableRow<TableRowData>, col.key, col.colIndex)"
|
||||
:items="getColumnOptions(col.colIndex, (slotProps.row as TableRow<TableRowData>).original)"
|
||||
:disabled="disabled"
|
||||
:placeholder="$t('applicationForms.formElements.table.selectValue')"
|
||||
:multiple="isColumnMultipleAllowed(col.colIndex)"
|
||||
class="w-full min-w-32"
|
||||
@update:model-value="
|
||||
(val: string | string[]) =>
|
||||
$emit('update:cellValue', (slotProps.row as TableRow<TableRowData>).index, col.key, col.colIndex, val)
|
||||
"
|
||||
/>
|
||||
<!-- Read-only column -->
|
||||
<span v-else-if="isColumnReadOnly(col.colIndex)" class="text-muted px-2 py-1">
|
||||
{{ formatCellDisplay(slotProps.row as any, col.key, col.colIndex) }}
|
||||
</span>
|
||||
<!-- Checkbox column -->
|
||||
<div v-else-if="isColumnCheckbox(col.colIndex)" class="flex justify-center">
|
||||
<UCheckbox
|
||||
:model-value="getCellValueForCheckbox(slotProps.row as TableRow<TableRowData>, col.key)"
|
||||
:disabled="disabled"
|
||||
@update:model-value="
|
||||
(val: boolean | 'indeterminate') =>
|
||||
$emit(
|
||||
'update:checkboxCell',
|
||||
(slotProps.row as TableRow<TableRowData>).index,
|
||||
col.colIndex,
|
||||
val === true
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<!-- Regular text input with auto-resizing textarea -->
|
||||
<UTextarea
|
||||
v-else
|
||||
:model-value="getCellValue(slotProps.row as TableRow<TableRowData>, col.key)"
|
||||
:disabled="disabled"
|
||||
:rows="1"
|
||||
autoresize
|
||||
:maxrows="0"
|
||||
class="w-full min-w-32"
|
||||
@update:model-value="
|
||||
(val: string | number) =>
|
||||
$emit('update:cell', (slotProps.row as TableRow<TableRowData>).index, col.key, String(val))
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="canModifyRows" #actions-cell="{ row }">
|
||||
<UButton
|
||||
v-if="!disabled"
|
||||
icon="i-lucide-trash-2"
|
||||
color="error"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
:aria-label="$t('applicationForms.formElements.table.removeRow')"
|
||||
@click="$emit('removeRow', row.index)"
|
||||
/>
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
<div v-if="tableData.length === 0" class="text-center text-dimmed py-4">
|
||||
{{ $t('applicationForms.formElements.table.noData') }}
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
v-if="!disabled && canModifyRows"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
leading-icon="i-lucide-plus"
|
||||
:class="addRowButtonClass"
|
||||
@click="$emit('addRow')"
|
||||
>
|
||||
{{ $t('applicationForms.formElements.table.addRow') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormOptionDto } from '~~/.api-client'
|
||||
import type { TableColumn, TableRow } from '@nuxt/ui'
|
||||
|
||||
type CellValue = string | string[] | boolean
|
||||
type TableRowData = Record<string, CellValue>
|
||||
|
||||
interface DataColumn {
|
||||
key: string
|
||||
colIndex: number
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
tableData: TableRowData[]
|
||||
tableColumns: TableColumn<TableRowData>[]
|
||||
dataColumns: DataColumn[]
|
||||
formOptions: FormOptionDto[]
|
||||
disabled?: boolean
|
||||
canModifyRows: boolean
|
||||
addRowButtonClass?: string
|
||||
getColumnOptions: (colIndex: number, currentRowData?: TableRowData) => string[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: 'update:cell', rowIndex: number, columnKey: string, value: string): void
|
||||
(e: 'update:cellValue', rowIndex: number, columnKey: string, colIndex: number, value: string | string[]): void
|
||||
(e: 'update:checkboxCell', rowIndex: number, colIndex: number, value: boolean): void
|
||||
(e: 'addRow'): void
|
||||
(e: 'removeRow', rowIndex: number): void
|
||||
}>()
|
||||
|
||||
function hasColumnReference(colIndex: number): boolean {
|
||||
const option = props.formOptions[colIndex]
|
||||
return !!option?.columnConfig?.sourceTableReference && !isColumnReadOnly(colIndex)
|
||||
}
|
||||
|
||||
function isColumnReadOnly(colIndex: number): boolean {
|
||||
const option = props.formOptions[colIndex]
|
||||
return option?.columnConfig?.isReadOnly === true
|
||||
}
|
||||
|
||||
function isColumnMultipleAllowed(colIndex: number): boolean {
|
||||
const option = props.formOptions[colIndex]
|
||||
return option?.columnConfig?.isMultipleAllowed === true
|
||||
}
|
||||
|
||||
function isColumnCheckbox(colIndex: number): boolean {
|
||||
const option = props.formOptions[colIndex]
|
||||
return option?.columnConfig?.isCheckbox === true
|
||||
}
|
||||
|
||||
function getCellValue(row: TableRow<TableRowData>, columnKey: string): string {
|
||||
const value = row.original[columnKey]
|
||||
return typeof value === 'string' ? value : ''
|
||||
}
|
||||
|
||||
function getCellValueForSelect(row: TableRow<TableRowData>, columnKey: string, colIndex: number): string | string[] {
|
||||
const value = row.original[columnKey]
|
||||
if (isColumnMultipleAllowed(colIndex)) {
|
||||
return Array.isArray(value) ? value : []
|
||||
}
|
||||
return typeof value === 'string' ? value : ''
|
||||
}
|
||||
|
||||
function getCellValueForCheckbox(row: TableRow<TableRowData>, columnKey: string): boolean {
|
||||
const value = row.original[columnKey]
|
||||
return value === true
|
||||
}
|
||||
|
||||
function formatCellDisplay(row: TableRow<TableRowData>, columnKey: string, colIndex: number): string {
|
||||
const value = row.original[columnKey]
|
||||
if (isColumnMultipleAllowed(colIndex) && Array.isArray(value)) {
|
||||
return value.length > 0 ? value.join(', ') : '-'
|
||||
}
|
||||
return (typeof value === 'string' ? value : '') || '-'
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user