HotCRM Logo

Page Layouts

How to configure record detail pages in HotCRM using *.page.ts files

Page Layouts

Page layouts define how record detail pages are rendered in HotCRM. They control field arrangement, related lists, custom components, and page-level actions. Every page layout is defined in a *.page.ts file.

File Convention

packages/{package}/src/{object_name}.page.ts

Examples from the codebase:

PackagePage FilePurpose
CRMaccount.page.tsAccount detail page with related lists
Financeinvoice.page.tsInvoice detail with line items
Marketingcampaign.page.tsCampaign detail with member lists
Productsproduct_bundle.page.tsProduct bundle configuration page

HotCRM currently has 4 page layout files across the business packages.

Basic Page Structure

A page layout declares the object it belongs to, a layout type, field sections, and page-level actions:

// packages/crm/src/account.page.ts

export const AccountPage = {
  name: 'account_detail',
  object: 'account',
  type: 'record',
  label: 'Account Detail Page',

  layout: {
    type: 'tabs',  // Layout type: 'tabs', 'accordion', or 'wizard'
    sections: [
      // ... section definitions
    ],
  },

  actions: [
    // ... page-level action buttons
  ],
};

export default AccountPage;

Layout Types

Tabs Layout

Organizes sections into horizontal tabs — ideal for record detail pages:

layout: {
  type: 'tabs',
  sections: [
    { label: 'Account Information', columns: 2, fields: ['name', 'type', ...] },
    { label: 'Address', columns: 2, fields: ['billing_street', ...] },
    { label: 'Opportunities', type: 'related_list', object: 'opportunity', ... },
  ]
}

Accordion Layout

Collapsible sections stacked vertically — useful for long forms:

layout: {
  type: 'accordion',
  sections: [
    { label: 'Basic Info', columns: 2, fields: [...], expanded: true },
    { label: 'Details', columns: 2, fields: [...], expanded: false },
  ]
}

Wizard Layout

Step-by-step guided flow — great for creation or onboarding pages:

layout: {
  type: 'wizard',
  sections: [
    { label: 'Step 1: Contact Info', columns: 1, fields: ['name', 'email', 'phone'] },
    { label: 'Step 2: Company Info', columns: 1, fields: ['company', 'industry'] },
    { label: 'Step 3: Review', type: 'summary' },
  ]
}

Section Types

Field Section

The most common section — displays object fields in a column layout:

{
  label: 'Account Information',
  columns: 2,    // 1 or 2 column layout
  fields: [
    'name',
    'account_number',
    'type',
    'industry',
    'phone',
    'website',
    'annual_revenue',
    'employees',
    'parent_account',
    'ownership',
  ]
}

Displays child records in a table with inline actions. Filters use the array format [field, operator, value]:

{
  label: 'Opportunities',
  type: 'related_list',
  object: 'opportunity',
  columns: ['name', 'stage', 'amount', 'close_date', 'probability'],
  filters: [['stage', '!=', 'closed_lost']],   // [field, operator, value]
  sort: [{ field: 'close_date', direction: 'asc' }],
  actions: ['new', 'edit', 'delete'],
}

Custom Component Section

Embed custom UI components (e.g., AI insights, charts):

{
  label: 'AI Insights',
  type: 'component',
  component: 'AccountAIInsights',
  props: {
    showHealthScore: true,
    showChurnRisk: true,
  }
}

Real-World Example: Account Detail Page

This is the actual account.page.ts from the CRM package:

// packages/crm/src/account.page.ts

export const AccountPage = {
  name: 'account_detail',
  object: 'account',
  type: 'record',
  label: 'Account Detail Page',

  layout: {
    type: 'tabs',
    sections: [
      // Tab 1: Account Information
      {
        label: 'Account Information',
        columns: 2,
        fields: [
          'name', 'account_number', 'type', 'industry',
          'phone', 'website', 'annual_revenue', 'employees',
          'parent_account', 'ownership',
        ],
      },

      // Tab 2: Address
      {
        label: 'Address Information',
        columns: 2,
        fields: [
          'billing_street', 'billing_city', 'billing_state',
          'billing_postal_code', 'billing_country',
          'shipping_street', 'shipping_city', 'shipping_state',
          'shipping_postal_code', 'shipping_country',
        ],
      },

      // Tab 3: Additional Information
      {
        label: 'Additional Information',
        columns: 2,
        fields: [
          'description', 'rating', 'sic_code', 'ticker_symbol',
          'fax', 'created_by', 'created_date',
          'last_modified_by', 'last_modified_date',
        ],
      },

      // Related List: Opportunities
      {
        label: 'Opportunities',
        type: 'related_list',
        object: 'opportunity',
        columns: ['name', 'stage', 'amount', 'close_date', 'probability'],
        filters: [['stage', '!=', 'closed_lost']],
        sort: [{ field: 'close_date', direction: 'asc' }],
        actions: ['new', 'edit', 'delete'],
      },

      // Related List: Contacts
      {
        label: 'Contacts',
        type: 'related_list',
        object: 'contact',
        columns: ['name', 'title', 'email', 'phone', 'is_decision_maker'],
        actions: ['new', 'edit', 'delete'],
      },

      // Related List: Cases
      {
        label: 'Cases',
        type: 'related_list',
        object: 'case',
        columns: ['case_number', 'subject', 'status', 'priority', 'created_date'],
        filters: [['status', '!=', 'closed']],
        sort: [{ field: 'created_date', direction: 'desc' }],
        actions: ['new', 'edit'],
      },

      // Related List: Contracts
      {
        label: 'Contracts',
        type: 'related_list',
        object: 'contract',
        columns: ['contract_number', 'status', 'start_date', 'end_date', 'contract_term'],
        actions: ['new', 'edit'],
      },
    ],
  },

  // Page-level action buttons
  actions: [
    { name: 'edit', label: 'Edit', type: 'standard' },
    { name: 'delete', label: 'Delete', type: 'standard' },
    { name: 'clone', label: 'Clone', type: 'custom', handler: 'cloneAccount' },
    {
      name: 'ai_analyze',
      label: 'AI Health Score',
      type: 'custom',
      handler: 'analyzeAccountHealth',
      icon: 'sparkles',
    },
    {
      name: 'ai_predict_churn',
      label: 'Predict Churn',
      type: 'custom',
      handler: 'predictChurn',
      icon: 'alert-triangle',
    },
  ],
};

export default AccountPage;

Page Actions

Actions appear as buttons in the page header. They can be standard CRUD actions or custom handlers:

Standard Actions

Built-in operations provided by the runtime:

{ name: 'edit', label: 'Edit', type: 'standard' }
{ name: 'delete', label: 'Delete', type: 'standard' }

Custom Actions

Trigger custom logic, including AI-powered features:

{
  name: 'ai_analyze',
  label: 'AI Health Score',
  type: 'custom',
  handler: 'analyzeAccountHealth',   // References a registered action
  icon: 'sparkles',                   // Optional icon
}

Custom action handlers are defined in *.action.ts files and registered in the plugin. See Actions for details.

Best Practices

1. Organize Fields Logically

Group related fields together and put the most important fields first:

// ✅ Good — grouped by category
sections: [
  { label: 'Contact Info', fields: ['name', 'email', 'phone'] },
  { label: 'Company Info', fields: ['company', 'industry', 'revenue'] },
  { label: 'Status', fields: ['stage', 'priority', 'owner'] },
]

2. Use 2-Column Layout for Dense Data

// ✅ Good — compact layout for many fields
{ label: 'Account Information', columns: 2, fields: [...] }

// Use 1-column for text-heavy fields
{ label: 'Description', columns: 1, fields: ['description', 'notes'] }

Only show relevant child records to reduce noise:

// ✅ Good — exclude closed items
{
  type: 'related_list',
  object: 'opportunity',
  filters: [['stage', '!=', 'closed_lost']],
  sort: [{ field: 'close_date', direction: 'asc' }],
}

4. Add AI Actions Where Valuable

Surface AI capabilities directly on record pages:

actions: [
  { name: 'edit', label: 'Edit', type: 'standard' },
  { name: 'ai_analyze', label: 'AI Health Score', type: 'custom', handler: 'analyzeAccountHealth', icon: 'sparkles' },
  { name: 'ai_predict_churn', label: 'Predict Churn', type: 'custom', handler: 'predictChurn', icon: 'alert-triangle' },
]

Next Steps

On this page