Added page layouts

This commit is contained in:
Francisco Gaona
2025-12-23 09:44:05 +01:00
parent be6e34914e
commit 838a010fb2
26 changed files with 3002 additions and 40 deletions

View File

@@ -13,38 +13,119 @@
<h1 class="text-3xl font-bold mb-6">{{ object.label }}</h1>
<!-- Tabs -->
<div class="mb-8">
<h2 class="text-2xl font-semibold mb-4">Fields</h2>
<div class="space-y-2">
<div
v-for="field in object.fields"
:key="field.id"
class="p-4 border rounded-lg bg-card"
>
<div class="flex items-center justify-between">
<div>
<h3 class="font-semibold">{{ field.label }}</h3>
<p class="text-sm text-muted-foreground">
Type: {{ field.type }} | API Name: {{ field.apiName }}
</p>
</div>
<div class="flex gap-2 text-xs">
<span
v-if="field.isRequired"
class="px-2 py-1 bg-destructive/10 text-destructive rounded"
>
Required
</span>
<span
v-if="field.isUnique"
class="px-2 py-1 bg-primary/10 text-primary rounded"
>
Unique
</span>
<Tabs v-model="activeTab" default-value="fields" class="w-full">
<TabsList class="grid w-full grid-cols-2 max-w-md">
<TabsTrigger value="fields">Fields</TabsTrigger>
<TabsTrigger value="layouts">Page Layouts</TabsTrigger>
</TabsList>
<!-- Fields Tab -->
<TabsContent value="fields" class="mt-6">
<div class="space-y-2">
<div
v-for="field in object.fields"
:key="field.id"
class="p-4 border rounded-lg bg-card"
>
<div class="flex items-center justify-between">
<div>
<h3 class="font-semibold">{{ field.label }}</h3>
<p class="text-sm text-muted-foreground">
Type: {{ field.type }} | API Name: {{ field.apiName }}
</p>
</div>
<div class="flex gap-2 text-xs">
<span
v-if="field.isRequired"
class="px-2 py-1 bg-destructive/10 text-destructive rounded"
>
Required
</span>
<span
v-if="field.isUnique"
class="px-2 py-1 bg-primary/10 text-primary rounded"
>
Unique
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</TabsContent>
<!-- Page Layouts Tab -->
<TabsContent value="layouts" class="mt-6">
<div v-if="!selectedLayout" class="space-y-4">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold">Page Layouts</h2>
<Button @click="handleCreateLayout">
<Plus class="w-4 h-4 mr-2" />
New Layout
</Button>
</div>
<div v-if="loadingLayouts" class="text-center py-8">
Loading layouts...
</div>
<div v-else-if="layouts.length === 0" class="text-center py-8 text-muted-foreground">
No page layouts yet. Create one to get started.
</div>
<div v-else class="space-y-2">
<div
v-for="layout in layouts"
:key="layout.id"
class="p-4 border rounded-lg bg-card hover:border-primary cursor-pointer transition-colors"
@click="handleSelectLayout(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>
</div>
<div class="flex items-center gap-2">
<span
v-if="layout.isDefault"
class="px-2 py-1 bg-primary/10 text-primary rounded text-xs"
>
Default
</span>
<Button
variant="ghost"
size="sm"
@click.stop="handleDeleteLayout(layout.id)"
>
<Trash2 class="w-4 h-4" />
</Button>
</div>
</div>
</div>
</div>
</div>
<!-- Layout Editor -->
<div v-else>
<div class="mb-4">
<Button variant="outline" @click="selectedLayout = null">
<ArrowLeft class="w-4 h-4 mr-2" />
Back to Layouts
</Button>
</div>
<PageLayoutEditor
:fields="object.fields"
:initial-layout="(selectedLayout.layoutConfig || selectedLayout.layout_config)?.fields || []"
:layout-name="selectedLayout.name"
@save="handleSaveLayout"
/>
</div>
</TabsContent>
</Tabs>
</div>
</div>
</main>
@@ -53,12 +134,26 @@
</template>
<script setup lang="ts">
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 type { PageLayout, FieldLayoutItem } from '~/types/page-layout'
const route = useRoute()
const { api } = useApi()
const { getPageLayouts, createPageLayout, updatePageLayout, deletePageLayout } = usePageLayouts()
const { toast } = useToast()
const object = ref<any>(null)
const loading = ref(true)
const error = ref<string | null>(null)
const activeTab = ref('fields')
// Page layouts state
const layouts = ref<PageLayout[]>([])
const loadingLayouts = ref(false)
const selectedLayout = ref<PageLayout | null>(null)
const fetchObject = async () => {
try {
@@ -72,7 +167,92 @@ const fetchObject = async () => {
}
}
onMounted(() => {
fetchObject()
const fetchLayouts = async () => {
if (!object.value) return
try {
loadingLayouts.value = true
layouts.value = await getPageLayouts(object.value.id)
} catch (e: any) {
console.error('Error fetching layouts:', e)
toast.error('Failed to load page layouts')
} finally {
loadingLayouts.value = false
}
}
const handleCreateLayout = async () => {
const name = prompt('Enter a name for the new layout:')
if (!name) return
try {
const newLayout = await createPageLayout({
name,
objectId: object.value.id,
isDefault: layouts.value.length === 0,
layoutConfig: { fields: [] },
})
layouts.value.push(newLayout)
selectedLayout.value = newLayout
toast.success('Layout created successfully')
} catch (e: any) {
console.error('Error creating layout:', e)
toast.error('Failed to create layout')
}
}
const handleSelectLayout = (layout: PageLayout) => {
selectedLayout.value = layout
}
const handleSaveLayout = async (fields: FieldLayoutItem[]) => {
if (!selectedLayout.value) return
try {
const updated = await updatePageLayout(selectedLayout.value.id, {
layoutConfig: { fields },
})
// Update the layout in the list
const index = layouts.value.findIndex(l => l.id === selectedLayout.value!.id)
if (index !== -1) {
layouts.value[index] = updated
}
selectedLayout.value = updated
toast.success('Layout saved successfully')
} catch (e: any) {
console.error('Error saving layout:', e)
toast.error('Failed to save layout')
}
}
const handleDeleteLayout = async (layoutId: string) => {
if (!confirm('Are you sure you want to delete this layout?')) return
try {
await deletePageLayout(layoutId)
layouts.value = layouts.value.filter(l => l.id !== layoutId)
toast.success('Layout deleted successfully')
} catch (e: any) {
console.error('Error deleting layout:', e)
toast.error('Failed to delete layout')
}
}
// Watch for tab changes to load layouts
watch(activeTab, (newTab) => {
if (newTab === 'layouts' && layouts.value.length === 0 && !loadingLayouts.value) {
fetchLayouts()
}
})
onMounted(async () => {
await fetchObject()
// If we start on layouts tab, load them
if (activeTab.value === 'layouts') {
await fetchLayouts()
}
})
</script>