UI Components
Clientra CRM includes 17 reusable UI components in assets/js/components/. These are global classes instantiated and shared across page modules, providing consistent UI patterns throughout the application.
DataTable
File: components/DataTable.js
The primary data display component used on every list page. Provides sorting, pagination, column management, row selection, bulk actions, and inline search.
Features
- Sortable columns with directional indicators
- Client-side and server-side pagination
- Column visibility toggle (via ColumnManager)
- Multi-row selection with checkbox column
- Bulk action buttons (delete, export, assign, bulk update)
- Select All across pages: On Properties, "Select All" selects all matching records across all pages (not just the visible page)
- Empty state messaging
- Loading skeleton states
- Row click handlers for quick view
Usage
const table = new DataTable('#table-container', { columns: [ { key: 'ref', label: 'Ref', sortable: true }, { key: 'name', label: 'Name', sortable: true }, { key: 'status', label: 'Status', sortable: false }, { key: 'actions', label: '', sortable: false } ], data: leads, pageSize: 25, onSort: (col, dir) => loadData({ sort: col, dir }), onRowClick: (row) => showQuickView(row.id) });
QuickViewModal
File: components/QuickViewModal.js
A side-sliding panel modal for displaying entity details without navigating away from the list page. Used on Properties, Leads, Contacts, and other list views.
QuickViewModal.esc(value) when injecting user-generated content into innerHTML inside the modal. Never inject raw strings directly — this prevents XSS attacks.
// CORRECT — Always escape user data modal.setContent(` <div class="qv-field"> <label>Name</label> <span>${QuickViewModal.esc(contact.name)}</span> </div> `); // WRONG — Never inject raw user data modal.setContent(`<span>${contact.name}</span>`); // XSS risk! // Open / close the modal const modal = new QuickViewModal(); modal.open({ title: 'Lead Details', width: '480px' }); modal.close();
SalesOfferModal
File: components/SalesOfferModal.js
Specialized modal for generating property sales offer PDFs. Allows selecting a property, choosing a PDF template and letterhead, and generating a professional sales offer document complete with property images.
FilterPanel
File: components/FilterPanel.js
A collapsible advanced filter panel that renders dynamic filter controls based on a configuration schema. Used on all list pages for filtering data. On the Properties page, filters support cascading/linked behavior — selecting a Location automatically narrows the Community and Project dropdowns to only show related items from the lookups system.
Supported Filter Types
| Type | Description |
|---|---|
text | Free-text search input |
select | Dropdown with predefined options |
multiselect | Multiple-choice dropdown |
date | Date picker (single date) |
daterange | Start/end date range picker |
number | Numeric input with min/max |
boolean | Yes/No toggle |
lookup | Dynamic lookup dropdown (LookupSelect) |
const filters = new FilterPanel('#filter-container', { filters: [ { key: 'status', label: 'Status', type: 'select', options: ['New', 'Contacted', 'Won', 'Lost'] }, { key: 'location', label: 'Location', type: 'lookup', source: 'locations' }, { key: 'price_min', label: 'Min Price', type: 'number' }, ], onChange: (values) => loadLeads(values) });
FilterPresets
File: components/FilterPresets.js
Works alongside FilterPanel to allow users to save, name, and quickly load their commonly-used filter configurations. Presets are stored in localStorage per page per user.
Chart
File: components/Chart.js
A wrapper component around Chart.js 4.4.0 (loaded from CDN). Provides consistent styling, theming integration, and a simplified configuration API for all dashboard charts.
Supported Chart Types
const chart = new Chart('#chart-canvas', { type: 'bar', labels: ['Jan', 'Feb', 'Mar'], datasets: [{ label: 'Deals Closed', data: [12, 18, 9], backgroundColor: 'var(--color-primary)' }], options: { responsive: true } });
KPICard
File: components/KPICard.js
Key Performance Indicator card component used on the Dashboard and Report pages. Displays a metric value with label, trend indicator, and optional icon.
KPICard.render({ title: 'Active Leads', value: 142, change: '+12%', trend: 'up', // 'up' | 'down' | 'neutral' icon: '🎯', color: 'primary' // 'primary' | 'success' | 'warning' | 'danger' });
PropertyCard
File: components/PropertyCard.js
Grid view card for property listings. Displays the cover image, property type badge, key details (beds, baths, size), price, and status badge. Used in the Properties grid view and the Portal page.
Timeline
File: components/Timeline.js
Entity activity timeline component. Renders the full communication and activity history for any CRM entity (lead, deal, contact, property, owner) in a chronological feed.
Rendered Event Types
- Email sent/received with subject and preview
- Call logged with duration and outcome
- WhatsApp message sent
- Meeting scheduled or completed
- Note added with full text
- Status/stage change events
- Field update audit entries
ContactPicker
File: components/ContactPicker.js
An autocomplete search input for selecting contacts. Used on lead, deal, and communication forms to link entities to existing contacts without leaving the current form.
const picker = new ContactPicker('#contact-field', { placeholder: 'Search contacts...', minChars: 2, onSelect: (contact) => { form.contactId = contact.id; form.contactName = contact.name; } });
ProjectPicker
File: components/ProjectPicker.js
Autocomplete project search input for linking entities to real estate development projects. Returns the project ID and name on selection.
UnitPicker
File: components/UnitPicker.js
Unit search and selection input. Optionally scoped to a specific project. Used when creating listings or deals to link to a specific unit within a development.
LookupSelect
File: components/LookupSelect.js
Dynamic dropdown that fetches options from the Lookups API. Used for developers, locations, communities, projects, and phases throughout all forms.
Available Lookup Sources
const locationSelect = new LookupSelect('#location-field', { source: 'locations', placeholder: 'Select location...', allowCreate: true, // Allow adding new items inline value: existingLocationId, onChange: (val) => { form.locationId = val; } });
ColumnManager
File: components/ColumnManager.js
Column visibility management panel for DataTable. Renders a dropdown with checkboxes to show/hide individual table columns. Column preferences are persisted in localStorage per table per user.
FileUploader
File: components/FileUploader.js
Multi-file upload component with drag-and-drop support, progress tracking, file type validation, and size limits.
Features
- Drag-and-drop upload zone
- Click to browse file picker
- Per-file upload progress bars
- File type and size validation
- Preview for image files
- Upload cancellation
- Supports local storage and Wasabi S3
const uploader = new FileUploader('#upload-zone', { entityType: 'property', entityId: propertyId, accept: ['.pdf', '.jpg', '.png', '.docx'], maxSizeMB: 25, multiple: true, onComplete: (files) => refreshDocumentList() });
NotificationPanel
File: components/NotificationPanel.js
Real-time notification dropdown panel rendered in the top navigation bar. Displays unread notification count, notification list, and mark-as-read functionality.
Features
- Unread count badge on nav icon
- Dropdown panel with notification feed
- Mark individual notifications as read
- Mark all notifications as read (POST request)
- Delete individual notifications
- Polling interval for new notifications
- Click notification to navigate to related entity
mark_all_notifications_read endpoint uses POST method. Both api.php and api.js enforce this. Do not use GET for this operation.
ResponsibilityPicker
File: components/ResponsibilityPicker.js
Entity responsibility assignment component. Allows selecting primary and secondary agents responsible for a lead, deal, or property. Used to define agent accountability beyond simple "assigned to" fields.