WIP - fixes to spreadsheet view
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import type { CellValueChangedEvent, ColDef } from 'ag-grid-community'
|
||||
import type { CellValueChangedEvent, ColDef, GridApi, GridReadyEvent } from 'ag-grid-community'
|
||||
import { AgGridVue } from 'ag-grid-vue3'
|
||||
import 'ag-grid-community/styles/ag-grid.css'
|
||||
import 'ag-grid-community/styles/ag-theme-alpine.css'
|
||||
@@ -29,6 +29,9 @@ interface Props {
|
||||
baseUrl?: string
|
||||
totalCount?: number
|
||||
searchSummary?: string
|
||||
draftEdits?: Record<string, Record<string, any>>
|
||||
cellErrors?: Record<string, Record<string, string | boolean>>
|
||||
savingDrafts?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -37,6 +40,9 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
selectable: false,
|
||||
baseUrl: '/runtime/objects',
|
||||
searchSummary: '',
|
||||
draftEdits: () => ({}),
|
||||
cellErrors: () => ({}),
|
||||
savingDrafts: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -53,6 +59,8 @@ const emit = defineEmits<{
|
||||
'load-more': [page: number, pageSize: number]
|
||||
'view-change': [mode: 'list' | 'spreadsheet']
|
||||
'cell-edit': [payload: { row: any; field: FieldConfig; newValue: any; oldValue: any }]
|
||||
'save-drafts': []
|
||||
'discard-drafts': []
|
||||
}>()
|
||||
|
||||
// State
|
||||
@@ -64,6 +72,7 @@ const sortDirection = ref<'asc' | 'desc'>('asc')
|
||||
const currentPage = ref(1)
|
||||
const bulkAction = ref('delete')
|
||||
const viewMode = ref<'list' | 'spreadsheet'>('list')
|
||||
const gridApi = ref<GridApi | null>(null)
|
||||
|
||||
// Computed
|
||||
const visibleFields = computed(() =>
|
||||
@@ -102,6 +111,8 @@ const showLoadMore = computed(() => (
|
||||
Boolean(props.totalCount) &&
|
||||
props.data.length < totalRecords.value
|
||||
))
|
||||
const draftRowCount = computed(() => Object.keys(props.draftEdits).length)
|
||||
const draftCellCount = computed(() => Object.values(props.draftEdits).reduce((sum, row) => sum + Object.keys(row).length, 0))
|
||||
|
||||
const allSelected = computed({
|
||||
get: () => props.data.length > 0 && selectedRowIds.value.length === props.data.length,
|
||||
@@ -224,6 +235,46 @@ const formatFieldValue = (field: FieldConfig, value: any, record?: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
const hasOwnProperty = (target: Record<string, any> | undefined, key: string) =>
|
||||
!!target && Object.prototype.hasOwnProperty.call(target, key)
|
||||
|
||||
const isDraftCell = (params: any) => {
|
||||
const rowId = normalizeId(params?.data?.id)
|
||||
const field = String(params?.colDef?.field || '')
|
||||
return hasOwnProperty(props.draftEdits[rowId], field)
|
||||
}
|
||||
|
||||
const isErrorCell = (params: any) => {
|
||||
const rowId = normalizeId(params?.data?.id)
|
||||
const field = String(params?.colDef?.field || '')
|
||||
return hasOwnProperty(props.cellErrors[rowId], field)
|
||||
}
|
||||
|
||||
const getCellClasses = (params: any) => {
|
||||
const classes: string[] = []
|
||||
if (isDraftCell(params)) classes.push('cell-draft')
|
||||
if (isErrorCell(params)) classes.push('cell-error')
|
||||
return classes
|
||||
}
|
||||
|
||||
const getCellStyle = (params: any) => {
|
||||
const isDraft = isDraftCell(params)
|
||||
const isError = isErrorCell(params)
|
||||
if (!isDraft && !isError) return null
|
||||
const style: Record<string, string> = {}
|
||||
if (isDraft) {
|
||||
style.backgroundColor = 'hsl(var(--accent) / 0.45)'
|
||||
style.outline = '2px solid hsl(var(--accent))'
|
||||
style.outlineOffset = '-2px'
|
||||
}
|
||||
if (isError) {
|
||||
style.backgroundColor = 'hsl(var(--destructive) / 0.28)'
|
||||
style.outline = '2px solid hsl(var(--destructive))'
|
||||
style.outlineOffset = '-2px'
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
const columnDefs = computed<ColDef[]>(() =>
|
||||
visibleFields.value.map(field => ({
|
||||
field: field.apiName,
|
||||
@@ -231,6 +282,8 @@ const columnDefs = computed<ColDef[]>(() =>
|
||||
sortable: field.sortable !== false,
|
||||
editable: isEditableField(field),
|
||||
valueFormatter: params => formatFieldValue(field, params.value, params.data),
|
||||
cellClass: params => getCellClasses(params),
|
||||
cellStyle: params => getCellStyle(params),
|
||||
cellEditor:
|
||||
field.type === FieldType.SELECT
|
||||
? 'agSelectCellEditor'
|
||||
@@ -268,6 +321,10 @@ const handleCellValueChanged = (event: CellValueChangedEvent) => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleGridReady = (event: GridReadyEvent) => {
|
||||
gridApi.value = event.api
|
||||
}
|
||||
|
||||
const goToPage = (page: number) => {
|
||||
const nextPage = Math.min(Math.max(page, 1), availablePages.value)
|
||||
if (nextPage !== currentPage.value) {
|
||||
@@ -309,6 +366,16 @@ watch(
|
||||
emit('view-change', mode)
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => [props.draftEdits, props.cellErrors],
|
||||
() => {
|
||||
if (gridApi.value) {
|
||||
gridApi.value.refreshCells({ force: true })
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -341,6 +408,26 @@ watch(
|
||||
<SelectItem value="spreadsheet">Spreadsheet view</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<template v-if="viewMode === 'spreadsheet'">
|
||||
<Badge v-if="draftRowCount > 0" variant="secondary" class="px-3 py-1">
|
||||
{{ draftCellCount }} change{{ draftCellCount === 1 ? '' : 's' }}
|
||||
</Badge>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="draftRowCount === 0 || savingDrafts"
|
||||
@click="emit('discard-drafts')"
|
||||
>
|
||||
Discard changes
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
:disabled="draftRowCount === 0 || savingDrafts"
|
||||
@click="emit('save-drafts')"
|
||||
>
|
||||
Save changes ({{ draftRowCount }})
|
||||
</Button>
|
||||
</template>
|
||||
<!-- Bulk Actions -->
|
||||
<template v-if="selectedRowIds.length > 0">
|
||||
<Badge variant="secondary" class="px-3 py-1">
|
||||
@@ -473,6 +560,7 @@ watch(
|
||||
suppress-row-click-selection
|
||||
single-click-edit
|
||||
@cell-value-changed="handleCellValueChanged"
|
||||
@grid-ready="handleGridReady"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -512,4 +600,5 @@ watch(
|
||||
.list-view :deep(input) {
|
||||
background-color: hsl(var(--background));
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -57,6 +57,7 @@ const {
|
||||
fetchRecord,
|
||||
deleteRecord,
|
||||
deleteRecords,
|
||||
updateRecord,
|
||||
handleSave,
|
||||
} = useViewState(`/runtime/objects/${objectApiName.value}/records`)
|
||||
|
||||
@@ -165,6 +166,12 @@ const deleteDialogOpen = ref(false)
|
||||
const deleteSubmitting = ref(false)
|
||||
const pendingDeleteRows = ref<any[]>([])
|
||||
const deleteSummary = ref<{ deletedIds: string[]; deniedIds: string[] } | null>(null)
|
||||
const normalizeRecordId = (id: any) => String(id)
|
||||
|
||||
const draftEdits = ref<Record<string, Record<string, any>>>({})
|
||||
const draftOriginals = ref<Record<string, Record<string, any>>>({})
|
||||
const cellErrors = ref<Record<string, Record<string, string>>>({})
|
||||
const savingDrafts = ref(false)
|
||||
|
||||
const isSearchActive = computed(() => searchQuery.value.trim().length > 0)
|
||||
const pendingDeleteCount = computed(() => pendingDeleteRows.value.length)
|
||||
@@ -355,6 +362,22 @@ const handleLoadMore = async (page: number, pageSize: number) => {
|
||||
await loadListRecords(page, { append: true, pageSize })
|
||||
}
|
||||
|
||||
const loadAllListRecords = async () => {
|
||||
if (!records.value.length) {
|
||||
await loadListRecords(1)
|
||||
}
|
||||
const resolvedTotal = totalCount.value ?? records.value.length
|
||||
if (resolvedTotal > records.value.length) {
|
||||
await loadListRecords(1, { append: false, pageSize: resolvedTotal })
|
||||
}
|
||||
}
|
||||
|
||||
const handleViewChange = async (mode: 'list' | 'spreadsheet') => {
|
||||
if (mode === 'spreadsheet' && !isSearchActive.value) {
|
||||
await loadAllListRecords()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = async (query: string) => {
|
||||
const trimmed = query.trim()
|
||||
searchQuery.value = trimmed
|
||||
@@ -366,6 +389,140 @@ const handleSearch = async (query: string) => {
|
||||
await searchListRecords(1, { append: false, pageSize: listPageSize.value })
|
||||
}
|
||||
|
||||
const handleCellEdit = async (payload: { row: any; field: any; newValue: any; oldValue: any }) => {
|
||||
if (!payload?.row?.id || payload.newValue === payload.oldValue) return
|
||||
const recordKey = normalizeRecordId(payload.row.id)
|
||||
const fieldName = payload.field.apiName
|
||||
const originalRow = draftOriginals.value[recordKey]
|
||||
const originalValue = originalRow && Object.prototype.hasOwnProperty.call(originalRow, fieldName)
|
||||
? originalRow[fieldName]
|
||||
: payload.oldValue
|
||||
|
||||
if (Object.is(payload.newValue, originalValue)) {
|
||||
const nextDrafts = { ...draftEdits.value }
|
||||
const nextRowDrafts = { ...(nextDrafts[recordKey] || {}) }
|
||||
delete nextRowDrafts[fieldName]
|
||||
if (Object.keys(nextRowDrafts).length === 0) {
|
||||
delete nextDrafts[recordKey]
|
||||
} else {
|
||||
nextDrafts[recordKey] = nextRowDrafts
|
||||
}
|
||||
draftEdits.value = nextDrafts
|
||||
|
||||
const nextOriginals = { ...draftOriginals.value }
|
||||
const nextRowOriginals = { ...(nextOriginals[recordKey] || {}) }
|
||||
delete nextRowOriginals[fieldName]
|
||||
if (Object.keys(nextRowOriginals).length === 0) {
|
||||
delete nextOriginals[recordKey]
|
||||
} else {
|
||||
nextOriginals[recordKey] = nextRowOriginals
|
||||
}
|
||||
draftOriginals.value = nextOriginals
|
||||
} else {
|
||||
draftEdits.value = {
|
||||
...draftEdits.value,
|
||||
[recordKey]: {
|
||||
...(draftEdits.value[recordKey] || {}),
|
||||
[fieldName]: payload.newValue,
|
||||
},
|
||||
}
|
||||
if (!originalRow || !Object.prototype.hasOwnProperty.call(originalRow, fieldName)) {
|
||||
draftOriginals.value = {
|
||||
...draftOriginals.value,
|
||||
[recordKey]: {
|
||||
...(draftOriginals.value[recordKey] || {}),
|
||||
[fieldName]: payload.oldValue,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cellErrors.value[recordKey]?.[fieldName]) {
|
||||
const nextErrors = { ...cellErrors.value }
|
||||
const nextRowErrors = { ...(nextErrors[recordKey] || {}) }
|
||||
delete nextRowErrors[fieldName]
|
||||
if (Object.keys(nextRowErrors).length === 0) {
|
||||
delete nextErrors[recordKey]
|
||||
} else {
|
||||
nextErrors[recordKey] = nextRowErrors
|
||||
}
|
||||
cellErrors.value = nextErrors
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveDrafts = async () => {
|
||||
if (Object.keys(draftEdits.value).length === 0) return
|
||||
savingDrafts.value = true
|
||||
const nextErrors: Record<string, Record<string, string>> = {}
|
||||
const nextDrafts = { ...draftEdits.value }
|
||||
const nextOriginals = { ...draftOriginals.value }
|
||||
|
||||
for (const [recordKey, changes] of Object.entries(draftEdits.value)) {
|
||||
const record = records.value.find(item => normalizeRecordId(item.id) === recordKey)
|
||||
if (!record) {
|
||||
delete nextDrafts[recordKey]
|
||||
delete nextOriginals[recordKey]
|
||||
continue
|
||||
}
|
||||
try {
|
||||
await updateRecord(record.id, changes)
|
||||
delete nextDrafts[recordKey]
|
||||
delete nextOriginals[recordKey]
|
||||
} catch (e: any) {
|
||||
const message = e.message || 'Failed to update record'
|
||||
nextErrors[recordKey] = {}
|
||||
for (const fieldName of Object.keys(changes)) {
|
||||
nextErrors[recordKey][fieldName] = message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draftEdits.value = nextDrafts
|
||||
draftOriginals.value = nextOriginals
|
||||
cellErrors.value = nextErrors
|
||||
savingDrafts.value = false
|
||||
if (Object.keys(nextErrors).length > 0) {
|
||||
error.value = 'Some updates failed. Fix highlighted cells and try again.'
|
||||
}
|
||||
}
|
||||
|
||||
const handleDiscardDrafts = () => {
|
||||
for (const [recordKey, fields] of Object.entries(draftOriginals.value)) {
|
||||
const record = records.value.find(item => normalizeRecordId(item.id) === recordKey)
|
||||
if (!record) continue
|
||||
for (const [fieldName, originalValue] of Object.entries(fields)) {
|
||||
record[fieldName] = originalValue
|
||||
}
|
||||
}
|
||||
draftEdits.value = {}
|
||||
draftOriginals.value = {}
|
||||
cellErrors.value = {}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => records.value.map(record => normalizeRecordId(record.id)),
|
||||
(ids) => {
|
||||
const idSet = new Set(ids)
|
||||
const nextDrafts: Record<string, Record<string, any>> = {}
|
||||
const nextOriginals: Record<string, Record<string, any>> = {}
|
||||
const nextErrors: Record<string, Record<string, string>> = {}
|
||||
|
||||
for (const [recordKey, fields] of Object.entries(draftEdits.value)) {
|
||||
if (idSet.has(recordKey)) nextDrafts[recordKey] = fields
|
||||
}
|
||||
for (const [recordKey, fields] of Object.entries(draftOriginals.value)) {
|
||||
if (idSet.has(recordKey)) nextOriginals[recordKey] = fields
|
||||
}
|
||||
for (const [recordKey, fields] of Object.entries(cellErrors.value)) {
|
||||
if (idSet.has(recordKey)) nextErrors[recordKey] = fields
|
||||
}
|
||||
|
||||
draftEdits.value = nextDrafts
|
||||
draftOriginals.value = nextOriginals
|
||||
cellErrors.value = nextErrors
|
||||
}
|
||||
)
|
||||
|
||||
// Watch for route changes
|
||||
watch(() => route.params, async (newParams, oldParams) => {
|
||||
// Reset current record when navigating to 'new'
|
||||
@@ -438,6 +595,9 @@ onMounted(async () => {
|
||||
:total-count="totalCount"
|
||||
:search-summary="searchSummary"
|
||||
:base-url="`/runtime/objects`"
|
||||
:draft-edits="draftEdits"
|
||||
:cell-errors="cellErrors"
|
||||
:saving-drafts="savingDrafts"
|
||||
selectable
|
||||
@row-click="handleRowClick"
|
||||
@create="handleCreate"
|
||||
@@ -446,6 +606,10 @@ onMounted(async () => {
|
||||
@search="handleSearch"
|
||||
@page-change="handlePageChange"
|
||||
@load-more="handleLoadMore"
|
||||
@view-change="handleViewChange"
|
||||
@cell-edit="handleCellEdit"
|
||||
@save-drafts="handleSaveDrafts"
|
||||
@discard-drafts="handleDiscardDrafts"
|
||||
/>
|
||||
|
||||
<!-- Detail View -->
|
||||
|
||||
@@ -91,6 +91,12 @@ const editConfig = computed(() => {
|
||||
|
||||
const listPageSize = computed(() => listConfig.value?.pageSize ?? 25)
|
||||
const maxFrontendRecords = computed(() => listConfig.value?.maxFrontendRecords ?? 500)
|
||||
const normalizeRecordId = (id: any) => String(id)
|
||||
|
||||
const draftEdits = ref<Record<string, Record<string, any>>>({})
|
||||
const draftOriginals = ref<Record<string, Record<string, any>>>({})
|
||||
const cellErrors = ref<Record<string, Record<string, string>>>({})
|
||||
const savingDrafts = ref(false)
|
||||
|
||||
// Fetch object definition
|
||||
const fetchObjectDefinition = async () => {
|
||||
@@ -231,20 +237,138 @@ const handleViewChange = async (mode: 'list' | 'spreadsheet') => {
|
||||
|
||||
const handleCellEdit = async (payload: { row: any; field: any; newValue: any; oldValue: any }) => {
|
||||
if (!payload?.row?.id || payload.newValue === payload.oldValue) return
|
||||
const recordKey = normalizeRecordId(payload.row.id)
|
||||
const fieldName = payload.field.apiName
|
||||
const originalRow = draftOriginals.value[recordKey]
|
||||
const originalValue = originalRow && Object.prototype.hasOwnProperty.call(originalRow, fieldName)
|
||||
? originalRow[fieldName]
|
||||
: payload.oldValue
|
||||
|
||||
if (Object.is(payload.newValue, originalValue)) {
|
||||
const nextDrafts = { ...draftEdits.value }
|
||||
const nextRowDrafts = { ...(nextDrafts[recordKey] || {}) }
|
||||
delete nextRowDrafts[fieldName]
|
||||
if (Object.keys(nextRowDrafts).length === 0) {
|
||||
delete nextDrafts[recordKey]
|
||||
} else {
|
||||
nextDrafts[recordKey] = nextRowDrafts
|
||||
}
|
||||
draftEdits.value = nextDrafts
|
||||
|
||||
const nextOriginals = { ...draftOriginals.value }
|
||||
const nextRowOriginals = { ...(nextOriginals[recordKey] || {}) }
|
||||
delete nextRowOriginals[fieldName]
|
||||
if (Object.keys(nextRowOriginals).length === 0) {
|
||||
delete nextOriginals[recordKey]
|
||||
} else {
|
||||
nextOriginals[recordKey] = nextRowOriginals
|
||||
}
|
||||
draftOriginals.value = nextOriginals
|
||||
} else {
|
||||
draftEdits.value = {
|
||||
...draftEdits.value,
|
||||
[recordKey]: {
|
||||
...(draftEdits.value[recordKey] || {}),
|
||||
[fieldName]: payload.newValue,
|
||||
},
|
||||
}
|
||||
if (!originalRow || !Object.prototype.hasOwnProperty.call(originalRow, fieldName)) {
|
||||
draftOriginals.value = {
|
||||
...draftOriginals.value,
|
||||
[recordKey]: {
|
||||
...(draftOriginals.value[recordKey] || {}),
|
||||
[fieldName]: payload.oldValue,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cellErrors.value[recordKey]?.[fieldName]) {
|
||||
const nextErrors = { ...cellErrors.value }
|
||||
const nextRowErrors = { ...(nextErrors[recordKey] || {}) }
|
||||
delete nextRowErrors[fieldName]
|
||||
if (Object.keys(nextRowErrors).length === 0) {
|
||||
delete nextErrors[recordKey]
|
||||
} else {
|
||||
nextErrors[recordKey] = nextRowErrors
|
||||
}
|
||||
cellErrors.value = nextErrors
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveDrafts = async () => {
|
||||
if (Object.keys(draftEdits.value).length === 0) return
|
||||
savingDrafts.value = true
|
||||
const nextErrors: Record<string, Record<string, string>> = {}
|
||||
const nextDrafts = { ...draftEdits.value }
|
||||
const nextOriginals = { ...draftOriginals.value }
|
||||
|
||||
for (const [recordKey, changes] of Object.entries(draftEdits.value)) {
|
||||
const record = records.value.find(item => normalizeRecordId(item.id) === recordKey)
|
||||
if (!record) {
|
||||
delete nextDrafts[recordKey]
|
||||
delete nextOriginals[recordKey]
|
||||
continue
|
||||
}
|
||||
try {
|
||||
await updateRecord(payload.row.id, {
|
||||
...payload.row,
|
||||
[payload.field.apiName]: payload.newValue,
|
||||
})
|
||||
await updateRecord(record.id, changes)
|
||||
delete nextDrafts[recordKey]
|
||||
delete nextOriginals[recordKey]
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Failed to update record'
|
||||
const record = records.value.find(item => item.id === payload.row.id)
|
||||
if (record) {
|
||||
record[payload.field.apiName] = payload.oldValue
|
||||
const message = e.message || 'Failed to update record'
|
||||
nextErrors[recordKey] = {}
|
||||
for (const fieldName of Object.keys(changes)) {
|
||||
nextErrors[recordKey][fieldName] = message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draftEdits.value = nextDrafts
|
||||
draftOriginals.value = nextOriginals
|
||||
cellErrors.value = nextErrors
|
||||
savingDrafts.value = false
|
||||
if (Object.keys(nextErrors).length > 0) {
|
||||
error.value = 'Some updates failed. Fix highlighted cells and try again.'
|
||||
}
|
||||
}
|
||||
|
||||
const handleDiscardDrafts = () => {
|
||||
for (const [recordKey, fields] of Object.entries(draftOriginals.value)) {
|
||||
const record = records.value.find(item => normalizeRecordId(item.id) === recordKey)
|
||||
if (!record) continue
|
||||
for (const [fieldName, originalValue] of Object.entries(fields)) {
|
||||
record[fieldName] = originalValue
|
||||
}
|
||||
}
|
||||
draftEdits.value = {}
|
||||
draftOriginals.value = {}
|
||||
cellErrors.value = {}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => records.value.map(record => normalizeRecordId(record.id)),
|
||||
(ids) => {
|
||||
const idSet = new Set(ids)
|
||||
const nextDrafts: Record<string, Record<string, any>> = {}
|
||||
const nextOriginals: Record<string, Record<string, any>> = {}
|
||||
const nextErrors: Record<string, Record<string, string>> = {}
|
||||
|
||||
for (const [recordKey, fields] of Object.entries(draftEdits.value)) {
|
||||
if (idSet.has(recordKey)) nextDrafts[recordKey] = fields
|
||||
}
|
||||
for (const [recordKey, fields] of Object.entries(draftOriginals.value)) {
|
||||
if (idSet.has(recordKey)) nextOriginals[recordKey] = fields
|
||||
}
|
||||
for (const [recordKey, fields] of Object.entries(cellErrors.value)) {
|
||||
if (idSet.has(recordKey)) nextErrors[recordKey] = fields
|
||||
}
|
||||
|
||||
draftEdits.value = nextDrafts
|
||||
draftOriginals.value = nextOriginals
|
||||
cellErrors.value = nextErrors
|
||||
}
|
||||
)
|
||||
|
||||
// Watch for route changes
|
||||
watch(() => route.params, async (newParams, oldParams) => {
|
||||
// Reset current record when navigating to 'new'
|
||||
@@ -311,6 +435,9 @@ onMounted(async () => {
|
||||
:data="records"
|
||||
:loading="dataLoading"
|
||||
:total-count="totalCount"
|
||||
:draft-edits="draftEdits"
|
||||
:cell-errors="cellErrors"
|
||||
:saving-drafts="savingDrafts"
|
||||
selectable
|
||||
@row-click="handleRowClick"
|
||||
@create="handleCreate"
|
||||
@@ -320,6 +447,8 @@ onMounted(async () => {
|
||||
@load-more="handleLoadMore"
|
||||
@view-change="handleViewChange"
|
||||
@cell-edit="handleCellEdit"
|
||||
@save-drafts="handleSaveDrafts"
|
||||
@discard-drafts="handleDiscardDrafts"
|
||||
/>
|
||||
|
||||
<!-- Detail View -->
|
||||
|
||||
Reference in New Issue
Block a user