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

304
PAGE_LAYOUTS_GUIDE.md Normal file
View File

@@ -0,0 +1,304 @@
# 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