Add Contact standard object, related lists, meilisearch, pagination, search, AI assistant

This commit is contained in:
Francisco Gaona
2026-01-16 18:01:26 +01:00
parent 51c82d3d95
commit 20fc90a3fb
62 changed files with 6613 additions and 278 deletions

View File

@@ -71,7 +71,12 @@ const fetchPage = async () => {
if (page.value.objectApiName) {
loadingRecords.value = true
records.value = await api.get(`/runtime/objects/${page.value.objectApiName}/records`)
const response = await api.get(
`/runtime/objects/${page.value.objectApiName}/records`
)
records.value = Array.isArray(response)
? response
: response?.data || response?.records || []
}
} catch (e: any) {
error.value = e.message

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useApi } from '@/composables/useApi'
import { useFields, useViewState } from '@/composables/useFieldViews'
@@ -31,6 +31,7 @@ const error = ref<string | null>(null)
// Use view state composable
const {
records,
totalCount,
currentRecord,
loading: dataLoading,
saving,
@@ -41,6 +42,27 @@ const {
handleSave,
} = useViewState(`/runtime/objects/${objectApiName.value}/records`)
const handleAiRecordCreated = (event: Event) => {
const detail = (event as CustomEvent).detail || {}
if (
detail?.objectApiName &&
detail.objectApiName.toLowerCase() !== objectApiName.value.toLowerCase()
) {
return
}
if (view.value === 'list') {
initializeListRecords()
}
}
onMounted(() => {
window.addEventListener('ai-record-created', handleAiRecordCreated)
})
onBeforeUnmount(() => {
window.removeEventListener('ai-record-created', handleAiRecordCreated)
})
// View configs
const listConfig = computed(() => {
if (!objectDefinition.value) return null
@@ -61,6 +83,9 @@ const editConfig = computed(() => {
return buildEditViewConfig(objectDefinition.value)
})
const listPageSize = computed(() => listConfig.value?.pageSize ?? 25)
const maxFrontendRecords = computed(() => listConfig.value?.maxFrontendRecords ?? 500)
// Fetch object definition
const fetchObjectDefinition = async () => {
try {
@@ -95,6 +120,14 @@ const handleBack = () => {
router.push(`/app/objects/${objectApiName.value}/`)
}
const handleNavigate = (relatedObjectApiName: string, relatedRecordId: string) => {
router.push(`/app/objects/${relatedObjectApiName}/${relatedRecordId}/detail`)
}
const handleCreateRelated = (relatedObjectApiName: string, _parentId: string) => {
router.push(`/app/objects/${relatedObjectApiName}/new`)
}
const handleDelete = async (rows: any[]) => {
if (confirm(`Delete ${rows.length} record(s)? This action cannot be undone.`)) {
try {
@@ -131,6 +164,39 @@ const handleCancel = () => {
}
}
const loadListRecords = async (
page = 1,
options?: { append?: boolean; pageSize?: number }
) => {
const pageSize = options?.pageSize ?? listPageSize.value
const result = await fetchRecords({ page, pageSize }, { append: options?.append })
const resolvedTotal = result?.totalCount ?? totalCount.value ?? records.value.length
totalCount.value = resolvedTotal
return result
}
const initializeListRecords = async () => {
const firstResult = await loadListRecords(1)
const resolvedTotal = firstResult?.totalCount ?? totalCount.value ?? records.value.length
const shouldPrefetchAll =
resolvedTotal <= maxFrontendRecords.value && records.value.length < resolvedTotal
if (shouldPrefetchAll) {
await loadListRecords(1, { append: false, pageSize: maxFrontendRecords.value })
}
}
const handlePageChange = async (page: number, pageSize: number) => {
const loadedPages = Math.ceil(records.value.length / pageSize)
if (page > loadedPages && totalCount.value > records.value.length) {
await loadListRecords(page, { append: true, pageSize })
}
}
const handleLoadMore = async (page: number, pageSize: number) => {
await loadListRecords(page, { append: true, pageSize })
}
// Watch for route changes
watch(() => route.params, async (newParams, oldParams) => {
// Reset current record when navigating to 'new'
@@ -145,7 +211,7 @@ watch(() => route.params, async (newParams, oldParams) => {
// Fetch records if navigating back to list
if (!newParams.recordId && !newParams.view) {
await fetchRecords()
await initializeListRecords()
}
}, { deep: true })
@@ -154,7 +220,7 @@ onMounted(async () => {
await fetchObjectDefinition()
if (view.value === 'list') {
await fetchRecords()
await initializeListRecords()
} else if (recordId.value && recordId.value !== 'new') {
await fetchRecord(recordId.value)
}
@@ -196,11 +262,14 @@ onMounted(async () => {
:config="listConfig"
:data="records"
:loading="dataLoading"
:total-count="totalCount"
selectable
@row-click="handleRowClick"
@create="handleCreate"
@edit="handleEdit"
@delete="handleDelete"
@page-change="handlePageChange"
@load-more="handleLoadMore"
/>
<!-- Detail View -->
@@ -212,6 +281,8 @@ onMounted(async () => {
@edit="handleEdit"
@delete="() => handleDelete([currentRecord])"
@back="handleBack"
@navigate="handleNavigate"
@create-related="handleCreateRelated"
/>
<!-- Edit View -->