Use AI assistant to create records in the system, added configurable list views
This commit is contained in:
@@ -3,6 +3,7 @@ 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'
|
||||
import { usePageLayouts } from '@/composables/usePageLayouts'
|
||||
import ListView from '@/components/views/ListView.vue'
|
||||
import DetailView from '@/components/views/DetailViewEnhanced.vue'
|
||||
import EditView from '@/components/views/EditViewEnhanced.vue'
|
||||
@@ -19,6 +20,7 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { api } = useApi()
|
||||
const { buildListViewConfig, buildDetailViewConfig, buildEditViewConfig } = useFields()
|
||||
const { getDefaultPageLayout } = usePageLayouts()
|
||||
|
||||
// Use breadcrumbs composable
|
||||
const { setBreadcrumbs } = useBreadcrumbs()
|
||||
@@ -40,6 +42,7 @@ const view = computed(() => {
|
||||
|
||||
// State
|
||||
const objectDefinition = ref<any>(null)
|
||||
const listViewLayout = ref<any>(null)
|
||||
const loading = ref(true)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
@@ -134,11 +137,13 @@ watch([objectDefinition, currentRecord, recordId], () => {
|
||||
// View configs
|
||||
const listConfig = computed(() => {
|
||||
if (!objectDefinition.value) return null
|
||||
// Pass the list view layout config to buildListViewConfig if available
|
||||
const layoutConfig = listViewLayout.value?.layout_config || listViewLayout.value?.layoutConfig
|
||||
return buildListViewConfig(objectDefinition.value, {
|
||||
searchable: true,
|
||||
exportable: true,
|
||||
filterable: true,
|
||||
})
|
||||
}, layoutConfig)
|
||||
})
|
||||
|
||||
const detailConfig = computed(() => {
|
||||
@@ -172,6 +177,16 @@ const fetchObjectDefinition = async () => {
|
||||
error.value = null
|
||||
const response = await api.get(`/setup/objects/${objectApiName.value}`)
|
||||
objectDefinition.value = response
|
||||
|
||||
// Fetch the default list view layout for this object
|
||||
if (response?.id) {
|
||||
try {
|
||||
listViewLayout.value = await getDefaultPageLayout(response.id, 'list')
|
||||
} catch (e) {
|
||||
// No list view layout configured, will use default behavior
|
||||
listViewLayout.value = null
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Failed to load object definition'
|
||||
console.error('Error fetching object definition:', e)
|
||||
|
||||
@@ -3,6 +3,7 @@ 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'
|
||||
import { usePageLayouts } from '@/composables/usePageLayouts'
|
||||
import ListView from '@/components/views/ListView.vue'
|
||||
import DetailView from '@/components/views/DetailView.vue'
|
||||
import EditView from '@/components/views/EditView.vue'
|
||||
@@ -11,6 +12,7 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { api } = useApi()
|
||||
const { buildListViewConfig, buildDetailViewConfig, buildEditViewConfig } = useFields()
|
||||
const { getDefaultPageLayout } = usePageLayouts()
|
||||
|
||||
// Get object API name from route
|
||||
const objectApiName = computed(() => route.params.objectName as string)
|
||||
@@ -25,6 +27,7 @@ const view = computed(() => {
|
||||
|
||||
// State
|
||||
const objectDefinition = ref<any>(null)
|
||||
const listViewLayout = ref<any>(null)
|
||||
const loading = ref(true)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
@@ -66,11 +69,13 @@ onBeforeUnmount(() => {
|
||||
// View configs
|
||||
const listConfig = computed(() => {
|
||||
if (!objectDefinition.value) return null
|
||||
// Pass the list view layout config to buildListViewConfig if available
|
||||
const layoutConfig = listViewLayout.value?.layout_config || listViewLayout.value?.layoutConfig
|
||||
return buildListViewConfig(objectDefinition.value, {
|
||||
searchable: true,
|
||||
exportable: true,
|
||||
filterable: true,
|
||||
})
|
||||
}, layoutConfig)
|
||||
})
|
||||
|
||||
const detailConfig = computed(() => {
|
||||
@@ -93,6 +98,16 @@ const fetchObjectDefinition = async () => {
|
||||
error.value = null
|
||||
const response = await api.get(`/setup/objects/${objectApiName.value}`)
|
||||
objectDefinition.value = response
|
||||
|
||||
// Fetch the default list view layout for this object
|
||||
if (response?.id) {
|
||||
try {
|
||||
listViewLayout.value = await getDefaultPageLayout(response.id, 'list')
|
||||
} catch (e) {
|
||||
// No list view layout configured, will use default behavior
|
||||
listViewLayout.value = null
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Failed to load object definition'
|
||||
console.error('Error fetching object definition:', e)
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
<!-- Tabs -->
|
||||
<div class="mb-8">
|
||||
<Tabs v-model="activeTab" default-value="fields" class="w-full">
|
||||
<TabsList class="grid w-full grid-cols-3 max-w-2xl">
|
||||
<TabsList class="grid w-full grid-cols-4 max-w-2xl">
|
||||
<TabsTrigger value="fields">Fields</TabsTrigger>
|
||||
<TabsTrigger value="access">Access</TabsTrigger>
|
||||
<TabsTrigger value="layouts">Page Layouts</TabsTrigger>
|
||||
<TabsTrigger value="listLayouts">List View Layouts</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<!-- Fields Tab -->
|
||||
@@ -148,7 +149,7 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
v-if="layout.isDefault"
|
||||
v-if="layout.isDefault || layout.is_default"
|
||||
class="px-2 py-1 bg-primary/10 text-primary rounded text-xs"
|
||||
>
|
||||
Default
|
||||
@@ -185,6 +186,84 @@
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<!-- List View Layouts Tab -->
|
||||
<TabsContent value="listLayouts" class="mt-6">
|
||||
<div v-if="!selectedListLayout" class="space-y-4">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-semibold">List View Layouts</h2>
|
||||
<Button @click="handleCreateListLayout">
|
||||
<Plus class="w-4 h-4 mr-2" />
|
||||
New List Layout
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-muted-foreground mb-4">
|
||||
Configure which fields appear in list views and their order.
|
||||
</p>
|
||||
|
||||
<div v-if="loadingListLayouts" class="text-center py-8">
|
||||
Loading list layouts...
|
||||
</div>
|
||||
|
||||
<div v-else-if="listLayouts.length === 0" class="text-center py-8 text-muted-foreground">
|
||||
No list view layouts yet. Create one to customize your list views.
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-2">
|
||||
<div
|
||||
v-for="layout in listLayouts"
|
||||
:key="layout.id"
|
||||
class="p-4 border rounded-lg bg-card hover:border-primary cursor-pointer transition-colors"
|
||||
@click="handleSelectListLayout(layout)"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="font-semibold">{{ layout.name }}</h3>
|
||||
<p v-if="layout.description" class="text-sm text-muted-foreground">
|
||||
{{ layout.description }}
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground mt-1">
|
||||
{{ getListLayoutFieldCount(layout) }} fields configured
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
v-if="layout.isDefault || layout.is_default"
|
||||
class="px-2 py-1 bg-primary/10 text-primary rounded text-xs"
|
||||
>
|
||||
Default
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click.stop="handleDeleteListLayout(layout.id)"
|
||||
>
|
||||
<Trash2 class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- List Layout Editor -->
|
||||
<div v-else>
|
||||
<div class="mb-4">
|
||||
<Button variant="outline" @click="selectedListLayout = null">
|
||||
<ArrowLeft class="w-4 h-4 mr-2" />
|
||||
Back to List Layouts
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<ListViewLayoutEditor
|
||||
:fields="object.fields"
|
||||
:initial-layout="(selectedListLayout.layoutConfig || selectedListLayout.layout_config)?.fields || []"
|
||||
:layout-name="selectedListLayout.name"
|
||||
@save="handleSaveListLayout"
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
@@ -299,6 +378,7 @@ import { Plus, Trash2, ArrowLeft } from 'lucide-vue-next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import PageLayoutEditor from '@/components/PageLayoutEditor.vue'
|
||||
import ListViewLayoutEditor from '@/components/ListViewLayoutEditor.vue'
|
||||
import ObjectAccessSettings from '@/components/ObjectAccessSettings.vue'
|
||||
import FieldTypeSelector from '@/components/fields/FieldTypeSelector.vue'
|
||||
import FieldAttributesCommon from '@/components/fields/FieldAttributesCommon.vue'
|
||||
@@ -315,11 +395,16 @@ const loading = ref(true)
|
||||
const error = ref<string | null>(null)
|
||||
const activeTab = ref('fields')
|
||||
|
||||
// Page layouts state
|
||||
// Page layouts state (detail/edit layouts)
|
||||
const layouts = ref<PageLayout[]>([])
|
||||
const loadingLayouts = ref(false)
|
||||
const selectedLayout = ref<PageLayout | null>(null)
|
||||
|
||||
// List view layouts state
|
||||
const listLayouts = ref<PageLayout[]>([])
|
||||
const loadingListLayouts = ref(false)
|
||||
const selectedListLayout = ref<PageLayout | null>(null)
|
||||
|
||||
// Field management state
|
||||
const showFieldDialog = ref(false)
|
||||
const fieldDialogMode = ref<'create' | 'edit'>('create')
|
||||
@@ -420,7 +505,8 @@ const fetchLayouts = async () => {
|
||||
|
||||
try {
|
||||
loadingLayouts.value = true
|
||||
layouts.value = await getPageLayouts(object.value.id)
|
||||
// Fetch only detail layouts (default type)
|
||||
layouts.value = await getPageLayouts(object.value.id, 'detail')
|
||||
} catch (e: any) {
|
||||
console.error('Error fetching layouts:', e)
|
||||
toast.error('Failed to load page layouts')
|
||||
@@ -429,6 +515,20 @@ const fetchLayouts = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchListLayouts = async () => {
|
||||
if (!object.value) return
|
||||
|
||||
try {
|
||||
loadingListLayouts.value = true
|
||||
listLayouts.value = await getPageLayouts(object.value.id, 'list')
|
||||
} catch (e: any) {
|
||||
console.error('Error fetching list layouts:', e)
|
||||
toast.error('Failed to load list view layouts')
|
||||
} finally {
|
||||
loadingListLayouts.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const openFieldDialog = async (mode: 'create' | 'edit', field?: any) => {
|
||||
fieldDialogMode.value = mode
|
||||
fieldDialogError.value = null
|
||||
@@ -684,6 +784,7 @@ const handleCreateLayout = async () => {
|
||||
const newLayout = await createPageLayout({
|
||||
name,
|
||||
objectId: object.value.id,
|
||||
layoutType: 'detail',
|
||||
isDefault: layouts.value.length === 0,
|
||||
layoutConfig: { fields: [], relatedLists: [] },
|
||||
})
|
||||
@@ -736,6 +837,73 @@ const handleDeleteLayout = async (layoutId: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
// List View Layout methods
|
||||
const handleCreateListLayout = async () => {
|
||||
const name = prompt('Enter a name for the new list view layout:')
|
||||
if (!name) return
|
||||
|
||||
try {
|
||||
const newLayout = await createPageLayout({
|
||||
name,
|
||||
objectId: object.value.id,
|
||||
layoutType: 'list',
|
||||
isDefault: listLayouts.value.length === 0,
|
||||
layoutConfig: { fields: [] },
|
||||
})
|
||||
|
||||
listLayouts.value.push(newLayout)
|
||||
selectedListLayout.value = newLayout
|
||||
toast.success('List view layout created successfully')
|
||||
} catch (e: any) {
|
||||
console.error('Error creating list layout:', e)
|
||||
toast.error('Failed to create list view layout')
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectListLayout = (layout: PageLayout) => {
|
||||
selectedListLayout.value = layout
|
||||
}
|
||||
|
||||
const handleSaveListLayout = async (layoutConfig: { fields: FieldLayoutItem[] }) => {
|
||||
if (!selectedListLayout.value) return
|
||||
|
||||
try {
|
||||
const updated = await updatePageLayout(selectedListLayout.value.id, {
|
||||
layoutConfig,
|
||||
})
|
||||
|
||||
// Update the layout in the list
|
||||
const index = listLayouts.value.findIndex(l => l.id === selectedListLayout.value!.id)
|
||||
if (index !== -1) {
|
||||
listLayouts.value[index] = updated
|
||||
}
|
||||
|
||||
selectedListLayout.value = updated
|
||||
toast.success('List view layout saved successfully')
|
||||
} catch (e: any) {
|
||||
console.error('Error saving list layout:', e)
|
||||
toast.error('Failed to save list view layout')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteListLayout = async (layoutId: string) => {
|
||||
if (!confirm('Are you sure you want to delete this list view layout?')) return
|
||||
|
||||
try {
|
||||
await deletePageLayout(layoutId)
|
||||
listLayouts.value = listLayouts.value.filter(l => l.id !== layoutId)
|
||||
toast.success('List view layout deleted successfully')
|
||||
} catch (e: any) {
|
||||
console.error('Error deleting list layout:', e)
|
||||
toast.error('Failed to delete list view layout')
|
||||
}
|
||||
}
|
||||
|
||||
const getListLayoutFieldCount = (layout: PageLayout): number => {
|
||||
const config = layout.layoutConfig || layout.layout_config
|
||||
return config?.fields?.length || 0
|
||||
}
|
||||
|
||||
const handleAccessUpdate = (orgWideDefault: string) => {
|
||||
if (object.value) {
|
||||
object.value.orgWideDefault = orgWideDefault
|
||||
@@ -747,6 +915,9 @@ watch(activeTab, (newTab) => {
|
||||
if (newTab === 'layouts' && layouts.value.length === 0 && !loadingLayouts.value) {
|
||||
fetchLayouts()
|
||||
}
|
||||
if (newTab === 'listLayouts' && listLayouts.value.length === 0 && !loadingListLayouts.value) {
|
||||
fetchListLayouts()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -755,5 +926,8 @@ onMounted(async () => {
|
||||
if (activeTab.value === 'layouts') {
|
||||
await fetchLayouts()
|
||||
}
|
||||
if (activeTab.value === 'listLayouts') {
|
||||
await fetchListLayouts()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user