WIP - fixes to spreadsheet view

This commit is contained in:
Francisco Gaona
2026-04-09 09:28:25 +02:00
parent 5f14a4050a
commit 228c3fb704
3 changed files with 393 additions and 11 deletions

View File

@@ -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 -->