305 lines
8.3 KiB
Markdown
305 lines
8.3 KiB
Markdown
# Page Layouts Feature
|
|
|
|
## Overview
|
|
|
|
The Page Layouts feature allows administrators to customize how fields are displayed in detail and edit views using a visual drag-and-drop interface based on GridStack.js with a 6-column grid system.
|
|
|
|
## Architecture
|
|
|
|
### Backend Components
|
|
|
|
1. **Database Schema** (`migrations/tenant/20250126000008_create_page_layouts.js`)
|
|
- `page_layouts` table stores layout configurations
|
|
- Fields: `id`, `name`, `object_id`, `is_default`, `layout_config`, `description`
|
|
- JSON-based `layout_config` stores field positions and sizes
|
|
|
|
2. **API Endpoints** (`src/page-layout/`)
|
|
- `POST /page-layouts` - Create a new page layout
|
|
- `GET /page-layouts?objectId={id}` - Get all layouts for an object
|
|
- `GET /page-layouts/:id` - Get a specific layout
|
|
- `GET /page-layouts/default/:objectId` - Get the default layout for an object
|
|
- `PATCH /page-layouts/:id` - Update a layout
|
|
- `DELETE /page-layouts/:id` - Delete a layout
|
|
|
|
### Frontend Components
|
|
|
|
1. **PageLayoutEditor.vue** - Visual editor for creating/editing layouts
|
|
- 6-column grid system using GridStack.js
|
|
- Drag and drop fields from sidebar
|
|
- Resize fields horizontally (1-6 columns width)
|
|
- Default width: 3 columns (2-column template effect)
|
|
- Save layout configuration
|
|
|
|
2. **PageLayoutRenderer.vue** - Renders fields based on saved layouts
|
|
- Used in detail and edit views
|
|
- Falls back to traditional 2-column layout if no layout configured
|
|
- Supports all field types
|
|
|
|
3. **DetailViewEnhanced.vue** & **EditViewEnhanced.vue**
|
|
- Enhanced versions of views with page layout support
|
|
- Automatically fetch and use default page layout
|
|
- Maintain backward compatibility with section-based layouts
|
|
|
|
### Types
|
|
|
|
- **PageLayout** (`types/page-layout.ts`)
|
|
- Layout metadata and configuration
|
|
- Field position and size definitions
|
|
- Grid configuration options
|
|
|
|
## Usage
|
|
|
|
### 1. Run Database Migration
|
|
|
|
```bash
|
|
cd backend
|
|
npm run migrate:tenant
|
|
```
|
|
|
|
### 2. Configure Page Layouts
|
|
|
|
Navigate to **Setup → Objects → [Object Name] → Page Layouts** tab:
|
|
|
|
1. Click "New Layout" to create a layout
|
|
2. Enter a layout name
|
|
3. Drag fields from the right sidebar onto the 6-column grid
|
|
4. Resize fields by dragging their edges (width only)
|
|
5. Rearrange fields by dragging them to new positions
|
|
6. Click "Save Layout" to persist changes
|
|
|
|
### 3. Use in Views
|
|
|
|
#### Option A: Use Enhanced Views (Recommended)
|
|
|
|
Replace existing views in your page:
|
|
|
|
```vue
|
|
<script setup>
|
|
import DetailViewEnhanced from '@/components/views/DetailViewEnhanced.vue'
|
|
import EditViewEnhanced from '@/components/views/EditViewEnhanced.vue'
|
|
|
|
const objectDefinition = ref(null)
|
|
|
|
// Fetch object definition...
|
|
</script>
|
|
|
|
<template>
|
|
<!-- Detail View -->
|
|
<DetailViewEnhanced
|
|
:config="detailConfig"
|
|
:data="currentRecord"
|
|
:object-id="objectDefinition.id"
|
|
@edit="handleEdit"
|
|
@delete="handleDelete"
|
|
@back="handleBack"
|
|
/>
|
|
|
|
<!-- Edit View -->
|
|
<EditViewEnhanced
|
|
:config="editConfig"
|
|
:data="currentRecord"
|
|
:object-id="objectDefinition.id"
|
|
@save="handleSave"
|
|
@cancel="handleCancel"
|
|
@back="handleBack"
|
|
/>
|
|
</template>
|
|
```
|
|
|
|
#### Option B: Use PageLayoutRenderer Directly
|
|
|
|
```vue
|
|
<script setup>
|
|
import PageLayoutRenderer from '@/components/PageLayoutRenderer.vue'
|
|
import { usePageLayouts } from '~/composables/usePageLayouts'
|
|
|
|
const { getDefaultPageLayout } = usePageLayouts()
|
|
const pageLayout = ref(null)
|
|
|
|
onMounted(async () => {
|
|
const layout = await getDefaultPageLayout(objectId)
|
|
if (layout) {
|
|
pageLayout.value = layout.layoutConfig
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<PageLayoutRenderer
|
|
:fields="fields"
|
|
:layout="pageLayout"
|
|
:model-value="formData"
|
|
:readonly="false"
|
|
@update:model-value="formData = $event"
|
|
/>
|
|
</template>
|
|
```
|
|
|
|
### 4. Composable API
|
|
|
|
```typescript
|
|
const {
|
|
getPageLayouts, // Get all layouts for an object
|
|
getPageLayout, // Get a specific layout
|
|
getDefaultPageLayout, // Get default layout for an object
|
|
createPageLayout, // Create new layout
|
|
updatePageLayout, // Update existing layout
|
|
deletePageLayout // Delete layout
|
|
} = usePageLayouts()
|
|
|
|
// Example: Create a layout
|
|
await createPageLayout({
|
|
name: 'Sales Layout',
|
|
objectId: 'uuid-here',
|
|
isDefault: true,
|
|
layoutConfig: {
|
|
fields: [
|
|
{ fieldId: 'field-1', x: 0, y: 0, w: 3, h: 1 },
|
|
{ fieldId: 'field-2', x: 3, y: 0, w: 3, h: 1 },
|
|
]
|
|
}
|
|
})
|
|
```
|
|
|
|
## Grid System
|
|
|
|
### Configuration
|
|
- **Columns**: 6
|
|
- **Default field width**: 3 columns (50% width)
|
|
- **Min width**: 1 column (16.67%)
|
|
- **Max width**: 6 columns (100%)
|
|
- **Height**: Fixed at 1 unit (80px), uniform across all fields
|
|
|
|
### Layout Example
|
|
|
|
```
|
|
┌─────────────────────┬─────────────────────┐
|
|
│ Field 1 (w:3) │ Field 2 (w:3) │ ← Two 3-column fields
|
|
├─────────────────────┴─────────────────────┤
|
|
│ Field 3 (w:6) │ ← One full-width field
|
|
├──────────┬──────────┬──────────┬──────────┤
|
|
│ F4 (w:2) │ F5 (w:2) │ F6 (w:2) │ (empty) │ ← Three 2-column fields
|
|
└──────────┴──────────┴──────────┴──────────┘
|
|
```
|
|
|
|
## Features
|
|
|
|
### Editor
|
|
- ✅ 6-column responsive grid
|
|
- ✅ Drag fields from sidebar to grid
|
|
- ✅ Drag to reposition fields on grid
|
|
- ✅ Resize fields horizontally (1-6 columns)
|
|
- ✅ Remove fields from layout
|
|
- ✅ Save layout configuration
|
|
- ✅ Clear all fields
|
|
|
|
### Renderer
|
|
- ✅ Renders fields based on saved layout
|
|
- ✅ Respects field positioning and sizing
|
|
- ✅ Supports all field types
|
|
- ✅ Falls back to 2-column layout if no layout configured
|
|
- ✅ Works in both readonly (detail) and edit modes
|
|
|
|
### Layout Management
|
|
- ✅ Multiple layouts per object
|
|
- ✅ Default layout designation
|
|
- ✅ Create, read, update, delete layouts
|
|
- ✅ Tab-based interface in object setup
|
|
|
|
## Backward Compatibility
|
|
|
|
The system maintains full backward compatibility:
|
|
- Objects without page layouts use traditional section-based views
|
|
- Existing DetailView and EditView components continue to work
|
|
- Enhanced views automatically detect and use page layouts when available
|
|
|
|
## Technical Details
|
|
|
|
### Layout Storage Format
|
|
|
|
```json
|
|
{
|
|
"fields": [
|
|
{
|
|
"fieldId": "uuid-of-field",
|
|
"x": 0, // Column start (0-5)
|
|
"y": 0, // Row start (0-based)
|
|
"w": 3, // Width in columns (1-6)
|
|
"h": 1 // Height in rows (fixed at 1)
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Field Component Mapping
|
|
|
|
The renderer automatically maps field types to appropriate components:
|
|
- TEXT → TextFieldView
|
|
- NUMBER → NumberFieldView
|
|
- DATE/DATETIME → DateFieldView
|
|
- BOOLEAN → BooleanFieldView
|
|
- PICKLIST → SelectFieldView
|
|
- EMAIL → EmailFieldView
|
|
- PHONE → PhoneFieldView
|
|
- URL → UrlFieldView
|
|
- CURRENCY → CurrencyFieldView
|
|
- PERCENT → PercentFieldView
|
|
- TEXTAREA → TextareaFieldView
|
|
|
|
## Development
|
|
|
|
### Adding New Field Types
|
|
|
|
1. Create field view component in `components/fields/`
|
|
2. Add mapping in `PageLayoutRenderer.vue`:
|
|
|
|
```typescript
|
|
const componentMap: Record<string, any> = {
|
|
// ... existing mappings
|
|
NEW_TYPE: NewFieldView,
|
|
}
|
|
```
|
|
|
|
### Customizing Grid Settings
|
|
|
|
Edit `PageLayoutEditor.vue`:
|
|
|
|
```typescript
|
|
grid = GridStack.init({
|
|
column: 6, // Change column count
|
|
cellHeight: 80, // Change cell height
|
|
minRow: 1, // Minimum rows
|
|
float: true, // Allow floating
|
|
acceptWidgets: true,
|
|
animate: true,
|
|
})
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Layout not appearing
|
|
- Ensure migration has been run
|
|
- Check that a default layout is set
|
|
- Verify objectId is passed to enhanced views
|
|
|
|
### Fields not draggable
|
|
- Check GridStack CSS is loaded
|
|
- Verify draggable attribute on sidebar fields
|
|
- Check browser console for errors
|
|
|
|
### Layout not saving
|
|
- Verify API endpoints are accessible
|
|
- Check authentication token
|
|
- Review backend logs for errors
|
|
|
|
## Future Enhancements
|
|
|
|
- [ ] Variable field heights
|
|
- [ ] Field-level permissions in layouts
|
|
- [ ] Clone/duplicate layouts
|
|
- [ ] Layout templates
|
|
- [ ] Layout preview mode
|
|
- [ ] Responsive breakpoints
|
|
- [ ] Related list sections
|
|
- [ ] Custom components in layouts
|