Add record access strategy

This commit is contained in:
Francisco Gaona
2026-01-05 07:48:22 +01:00
parent 838a010fb2
commit 16907aadf8
97 changed files with 11350 additions and 208 deletions

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue'
import { computed, ref, watch, onMounted } from 'vue'
import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
@@ -9,19 +9,27 @@ import { DatePicker } from '@/components/ui/date-picker'
import { Badge } from '@/components/ui/badge'
import { FieldConfig, FieldType, ViewMode } from '@/types/field-types'
import { Label } from '@/components/ui/label'
import LookupField from '@/components/fields/LookupField.vue'
interface Props {
field: FieldConfig
modelValue: any
mode: ViewMode
readonly?: boolean
baseUrl?: string // Base URL for API calls
recordData?: any // Full record data to access related objects
}
const props = defineProps<Props>()
const props = withDefaults(defineProps<Props>(), {
baseUrl: '/central',
})
const emit = defineEmits<{
'update:modelValue': [value: any]
}>()
const { api } = useApi()
const value = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
@@ -32,10 +40,44 @@ const isEditMode = computed(() => props.mode === ViewMode.EDIT)
const isListMode = computed(() => props.mode === ViewMode.LIST)
const isDetailMode = computed(() => props.mode === ViewMode.DETAIL)
// Check if field is a relationship field
const isRelationshipField = computed(() => {
return [FieldType.BELONGS_TO].includes(props.field.type)
})
// Get relation object name from field apiName (e.g., 'ownerId' -> 'owner')
const getRelationPropertyName = () => {
// Backend attaches related object using field apiName without 'Id' suffix, lowercase
// e.g., ownerId -> owner, accountId -> account
return props.field.apiName.replace(/Id$/, '').toLowerCase()
}
// Display value for relationship fields
const relationshipDisplayValue = computed(() => {
if (!isRelationshipField.value) return props.modelValue || '-'
// First, check if the parent record data includes the related object
// This happens when backend uses .withGraphFetched()
if (props.recordData) {
const relationPropertyName = getRelationPropertyName()
const relatedObject = props.recordData[relationPropertyName]
if (relatedObject && typeof relatedObject === 'object') {
const displayField = props.field.relationDisplayField || 'name'
return relatedObject[displayField] || relatedObject.id || props.modelValue
}
}
// If no related object found in recordData, just show the ID
// (The fetch mechanism is removed to avoid N+1 queries)
return props.modelValue || '-'
})
const formatValue = (val: any): string => {
if (val === null || val === undefined) return '-'
switch (props.field.type) {
case FieldType.BELONGS_TO:
return relationshipDisplayValue.value
case FieldType.DATE:
return val instanceof Date ? val.toLocaleDateString() : new Date(val).toLocaleDateString()
case FieldType.DATETIME:
@@ -78,6 +120,7 @@ const formatValue = (val: any): string => {
{{ formatValue(value) }}
</Badge>
<template v-else>
{{ formatValue(value) }}
</template>
</div>
@@ -113,9 +156,17 @@ const formatValue = (val: any): string => {
<!-- Edit View - Input components -->
<div v-else-if="isEditMode && !isReadOnly">
<!-- Relationship Field - Lookup -->
<LookupField
v-if="field.type === FieldType.BELONGS_TO"
:field="field"
v-model="value"
:base-url="baseUrl"
/>
<!-- Text Input -->
<Input
v-if="[FieldType.TEXT, FieldType.EMAIL, FieldType.URL, FieldType.PASSWORD].includes(field.type)"
v-else-if="[FieldType.TEXT, FieldType.EMAIL, FieldType.URL, FieldType.PASSWORD].includes(field.type)"
:id="field.id"
v-model="value"
:type="field.type === FieldType.PASSWORD ? 'password' : field.type === FieldType.EMAIL ? 'email' : field.type === FieldType.URL ? 'url' : 'text'"