Files
neo/PAGE_LAYOUTS_ARCHITECTURE.md
2025-12-22 23:48:09 +01:00

24 KiB

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