# Page Layouts Architecture Diagram ## System Overview ``` ┌─────────────────────────────────────────────────────────────────┐ │ FRONTEND (Vue 3 + Nuxt) │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ Setup → Objects → [Object] → Layouts Tab │ │ │ ├───────────────────────────────────────────────────────────┤ │ │ │ │ │ │ │ ┌─────────────┐ ┌───────────────────────────────┐ │ │ │ │ │ Layouts │ │ PageLayoutEditor │ │ │ │ │ │ List │ --> │ ┌─────────────────────────┐ │ │ │ │ │ │ │ │ │ 6-Column Grid │ │ │ │ │ │ │ • Standard │ │ │ ┌───┬───┬───┬───┬───┐ │ │ │ │ │ │ │ • Compact │ │ │ │ F │ F │ F │ F │ F │ │ │ │ │ │ │ │ • Detailed │ │ │ ├───┴───┴───┴───┴───┤ │ │ │ │ │ │ │ │ │ │ │ Field 1 (w:5) │ │ │ │ │ │ │ │ [+ New] │ │ │ └─────────────────── │ │ │ │ │ │ │ └─────────────┘ │ └─────────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ Sidebar: │ │ │ │ │ │ ┌─────────────────────────┐ │ │ │ │ │ │ │ Available Fields │ │ │ │ │ │ │ │ □ Email │ │ │ │ │ │ │ │ □ Phone │ │ │ │ │ │ │ │ □ Status │ │ │ │ │ │ │ └─────────────────────────┘ │ │ │ │ │ └───────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ Record Detail/Edit Views │ │ │ ├───────────────────────────────────────────────────────┤ │ │ │ │ │ │ │ DetailViewEnhanced / EditViewEnhanced │ │ │ │ ↓ │ │ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ │ │ PageLayoutRenderer │ │ │ │ │ │ │ │ │ │ │ │ Fetches default layout for object │ │ │ │ │ │ Renders fields in custom grid positions │ │ │ │ │ │ Fallback to 2-column if no layout │ │ │ │ │ └─────────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ Composables (usePageLayouts) │ │ │ ├───────────────────────────────────────────────────────┤ │ │ │ • getPageLayouts() • createPageLayout() │ │ │ │ • getPageLayout() • updatePageLayout() │ │ │ │ • getDefaultPageLayout()• deletePageLayout() │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↕ HTTP REST API ┌─────────────────────────────────────────────────────────────┐ │ BACKEND (NestJS) │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ PageLayoutController (API Layer) │ │ │ ├───────────────────────────────────────────────────────┤ │ │ │ POST /page-layouts │ │ │ │ GET /page-layouts?objectId={id} │ │ │ │ GET /page-layouts/:id │ │ │ │ GET /page-layouts/default/:objectId │ │ │ │ PATCH /page-layouts/:id │ │ │ │ DELETE /page-layouts/:id │ │ │ └───────────────────────────────────────────────────────┘ │ │ ↕ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ PageLayoutService (Business Logic) │ │ │ ├───────────────────────────────────────────────────────┤ │ │ │ • Tenant isolation │ │ │ │ • Default layout management │ │ │ │ • CRUD operations │ │ │ │ • Validation │ │ │ └───────────────────────────────────────────────────────┘ │ │ ↕ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ PrismaService (Data Layer) │ │ │ ├───────────────────────────────────────────────────────┤ │ │ │ • Raw SQL queries │ │ │ │ • Tenant database routing │ │ │ │ • Transaction management │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↕ ┌─────────────────────────────────────────────────────────────┐ │ DATABASE (PostgreSQL) │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ Table: page_layouts │ │ │ ├───────────────────────────────────────────────────────┤ │ │ │ id UUID PRIMARY KEY │ │ │ │ name VARCHAR(255) │ │ │ │ object_id UUID → object_definitions(id) │ │ │ │ is_default BOOLEAN │ │ │ │ layout_config JSONB │ │ │ │ description TEXT │ │ │ │ created_at TIMESTAMP │ │ │ │ updated_at TIMESTAMP │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ Example layout_config JSONB: │ │ { │ │ "fields": [ │ │ { │ │ "fieldId": "uuid-123", │ │ "x": 0, // Column start (0-5) │ │ "y": 0, // Row start │ │ "w": 3, // Width (1-6 columns) │ │ "h": 1 // Height (fixed at 1) │ │ } │ │ ] │ │ } │ └─────────────────────────────────────────────────────────────┘ ``` ## Data Flow Diagrams ### Creating a Layout ``` Admin User │ ├─→ Navigates to Setup → Objects → [Object] → Page Layouts │ ├─→ Clicks "New Layout" │ ├─→ Enters layout name │ ├─→ PageLayoutEditor mounts │ │ │ ├─→ Loads object fields │ ├─→ Initializes GridStack with 6 columns │ └─→ Shows available fields in sidebar │ ├─→ Drags fields from sidebar to grid │ │ │ ├─→ GridStack handles positioning │ ├─→ User resizes field width (1-6 columns) │ └─→ User arranges fields │ ├─→ Clicks "Save Layout" │ ├─→ usePageLayouts.createPageLayout() │ │ │ └─→ POST /page-layouts │ │ │ └─→ PageLayoutController.create() │ │ │ └─→ PageLayoutService.create() │ │ │ ├─→ If is_default, unset others │ └─→ INSERT INTO page_layouts │ └─→ Layout saved ✓ ``` ### Rendering a Layout in Detail View ``` User Opens Record │ ├─→ Navigates to /[object]/[id]/detail │ ├─→ DetailViewEnhanced mounts │ │ │ └─→ onMounted() hook │ │ │ └─→ usePageLayouts.getDefaultPageLayout(objectId) │ │ │ └─→ GET /page-layouts/default/:objectId │ │ │ └─→ PageLayoutService.findDefaultByObject() │ │ │ └─→ SELECT * FROM page_layouts │ WHERE object_id = $1 │ AND is_default = true │ ├─→ Layout received │ │ │ ├─→ If layout exists: │ │ │ │ │ └─→ PageLayoutRenderer renders with layout │ │ │ │ │ ├─→ Creates CSS Grid (6 columns) │ │ ├─→ Positions fields based on x, y, w, h │ │ └─→ Renders FieldRenderer for each field │ │ │ └─→ If no layout: │ │ │ └─→ Falls back to 2-column layout │ └─→ Record displayed with custom layout ✓ ``` ## Grid Layout System ### 6-Column Grid Structure ``` ┌──────┬──────┬──────┬──────┬──────┬──────┐ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ ← Column indices └──────┴──────┴──────┴──────┴──────┴──────┘ Each column = 16.67% of container width ``` ### Example Layouts #### Two-Column Layout (Default) ``` ┌─────────────────────┬─────────────────────┐ │ Name (w:3) │ Email (w:3) │ ├─────────────────────┼─────────────────────┤ │ Phone (w:3) │ Company (w:3) │ ├─────────────────────┴─────────────────────┤ │ Description (w:6) │ └───────────────────────────────────────────┘ Field configs: - Name: {x:0, y:0, w:3, h:1} - Email: {x:3, y:0, w:3, h:1} - Phone: {x:0, y:1, w:3, h:1} - Company: {x:3, y:1, w:3, h:1} - Description: {x:0, y:2, w:6, h:1} ``` #### Three-Column Layout ``` ┌───────────┬───────────┬───────────┐ │ F1 (w:2) │ F2 (w:2) │ F3 (w:2) │ ├───────────┴───────────┴───────────┤ │ F4 (w:6) │ └───────────────────────────────────┘ Field configs: - F1: {x:0, y:0, w:2, h:1} - F2: {x:2, y:0, w:2, h:1} - F3: {x:4, y:0, w:2, h:1} - F4: {x:0, y:1, w:6, h:1} ``` #### Mixed Width Layout ``` ┌───────────────┬───────┬───────────┐ │ Title (w:3) │ ID(1) │ Type (w:2)│ ├───────────────┴───────┴───────────┤ │ Address (w:6) │ ├──────────┬────────────────────────┤ │ City(2) │ State/ZIP (w:4) │ └──────────┴────────────────────────┘ Field configs: - Title: {x:0, y:0, w:3, h:1} - ID: {x:3, y:0, w:1, h:1} - Type: {x:4, y:0, w:2, h:1} - Address: {x:0, y:1, w:6, h:1} - City: {x:0, y:2, w:2, h:1} - State: {x:2, y:2, w:4, h:1} ``` ## Component Hierarchy ``` App.vue │ └─→ NuxtLayout (default) │ ├─→ Setup Pages │ │ │ └─→ pages/setup/objects/[apiName].vue │ │ │ └─→ Tabs Component │ │ │ ├─→ Tab: Fields (existing) │ │ │ └─→ Tab: Page Layouts │ │ │ ├─→ Layout List View │ │ └─→ Card per layout │ │ │ └─→ Layout Editor View │ │ │ └─→ PageLayoutEditor │ │ │ ├─→ GridStack (6 columns) │ │ └─→ Field items │ │ │ └─→ Sidebar │ └─→ Available fields │ └─→ Record Pages │ └─→ pages/[objectName]/[[recordId]]/[[view]].vue │ ├─→ DetailViewEnhanced │ │ │ └─→ PageLayoutRenderer │ └─→ FieldRenderer (per field) │ └─→ EditViewEnhanced │ └─→ PageLayoutRenderer └─→ FieldRenderer (per field) ``` ## State Management ``` ┌─────────────────────────────────────┐ │ Component State (ref/reactive) │ ├─────────────────────────────────────┤ │ • selectedLayout │ │ • layouts[] │ │ • loadingLayouts │ │ • pageLayout (current) │ │ • formData │ │ • gridItems │ │ • placedFieldIds │ └─────────────────────────────────────┘ ↕ ┌─────────────────────────────────────┐ │ Composables (Reactive) │ ├─────────────────────────────────────┤ │ • usePageLayouts() - API calls │ │ • useApi() - HTTP client │ │ • useAuth() - Authentication │ │ • useToast() - Notifications │ └─────────────────────────────────────┘ ↕ ┌─────────────────────────────────────┐ │ Browser Storage │ ├─────────────────────────────────────┤ │ • localStorage: token, tenantId │ │ • SessionStorage: (none yet) │ │ • Cookies: (managed by server) │ └─────────────────────────────────────┘ ``` ## Security Flow ``` ┌────────────────────────────────────────────────┐ │ 1. User Login │ │ → Receives JWT token │ │ → Token stored in localStorage │ └────────────────────────────────────────────────┘ ↓ ┌────────────────────────────────────────────────┐ │ 2. API Request │ │ → useApi() adds Authorization header │ │ → useApi() adds x-tenant-id header │ └────────────────────────────────────────────────┘ ↓ ┌────────────────────────────────────────────────┐ │ 3. Backend Validation │ │ → JwtAuthGuard validates token │ │ → Extracts user info (userId, tenantId) │ │ → Attaches to request object │ └────────────────────────────────────────────────┘ ↓ ┌────────────────────────────────────────────────┐ │ 4. Service Layer │ │ → Receives tenantId from request │ │ → All queries scoped to tenant │ │ → Tenant isolation enforced │ └────────────────────────────────────────────────┘ ↓ ┌────────────────────────────────────────────────┐ │ 5. Database │ │ → Tenant-specific database selected │ │ → Query executed in tenant context │ │ → Results returned │ └────────────────────────────────────────────────┘ ``` --- **Legend:** - `→` : Data flow direction - `↕` : Bidirectional communication - `├─→` : Hierarchical relationship - `└─→` : Terminal branch - `✓` : Successful operation