@visns-studio/visns-components
v5.15.31
Published
Various packages to assist in the development of our Custom Applications.
Readme
VISNS Components
A comprehensive React component library used by the VISNS Studio team for CRM and CMS projects, featuring authentication, data visualization, form handling, and more.
Overview
VISNS Components is a React-based UI component library that provides a set of reusable, consistent, and customizable components for building web applications. It includes components for authentication, data grids, forms, navigation, and more, designed to work seamlessly together.
Recent Updates (v5.15.10)
Latest Enhancements
- Phone and ABN Formatting: Added specialized formatting for Australian phone numbers and ABNs across DataGrid, GenericDetail, and JSON fields with format support
- CameraPlacement Legacy Import: Added comprehensive legacy JSON file import functionality with support for multiple data formats, automatic image loading, and seamless database integration
- CameraPlacement Module: Enhanced interactive camera placement tool with improved UI consistency, better icon visibility, drag-and-drop image upload, professional export capabilities, and complete Verkada database integration
- Complete StandardModal Migration: All components now use StandardModal instead of reactjs-popup for consistent modal behavior across the entire library
- HTML Content Support: Terms and conditions now properly parse HTML content from TinyMCE editors with clean rendering
- Improved Typography: Refined font sizing in Terms and Conditions to match page typography hierarchy
- Modal Component Refactoring: StandardModal converted to pure React implementation with enhanced accessibility and mobile support
- Enhanced JSON Table Field: Improved json-table field type with better styling, user experience, and data editing capabilities
- Dynamic Action Labels: GenericDetail component now supports dynamic action labels and custom action buttons for better user interaction
- Dependency Updates: Updated lucide-react to version 0.536.0 for improved icon consistency and performance
- GenericAuth Enhancements: Additional config prop support for more flexible authentication configurations
- GenericGrid Advanced Features: Enhanced filtering with URL-based options, multiselect support, contact display integration, interactive date pickers with timezone support, and toggleable stage functionality
- Modular Architecture: GenericIndex refactored into standalone GenericGrid and GenericReportForm components for better reusability
- Proposal System Integration: GenericQuote enhanced with comprehensive proposal generation capabilities
- Smart Data Extraction: DropZone component with intelligent data extraction for complex API responses
- Interactive Stage Management: Clickable stage toggles with API integration and visual feedback
- Timezone Support: Automatic timezone detection and conversion for date operations
- Contact Selection Workflows: Two-step date and contact selection for survey systems
Features
Authentication System
- Login - Email/password authentication
- Two-Factor Authentication (2FA) - Additional security layer with QR code and manual setup
- Password Reset - Secure password recovery flow
- Profile Management - User profile editing
- SSO Integration - Support for Microsoft Azure
- Remember Me - Persistent login option that works with 2FA
Data Visualization
- DataGrid - Powerful data table with sorting, filtering, and pagination
- Charts - Various chart types for data visualization
- Report Builder - Custom report creation with table joins, column selection, and filtering
- Sketch Field - Interactive drawing tools for diagrams and annotations
Form Components
- Form Builder - Dynamic form creation and validation
- Field Components - Text inputs, selects, checkboxes, etc.
- Multi-select - Enhanced select with multiple selection support and selection limits
- Autocomplete - Mapbox integration for location selection
- Dropzone - Advanced file upload with drag and drop, smart data extraction, and file type icons
- AsyncSelect - Asynchronous data loading for select components
- Creatable Select - Select components that allow creating new options
- GenericEditableTable - Interactive table component with inline editing, color-coded columns, and multiple data types
Navigation
- Header/Navigation - Application header with navigation
- Sub Navigation - Secondary navigation options
- Breadcrumb - Path-based navigation component
Utility Components
- Fetch - Advanced data fetching with intelligent caching, error handling, and TanStack Query integration
- Download - File download functionality
- Loader - Loading indicators
- Notification - Toast notifications
- StandardModal - Reusable modal component with consistent styling
- Call Popup - Modal dialogs
- QR Code - QR code generation and printing
- CameraPlacement - Interactive camera placement tool with Verkada database, FOV visualization, and save/load functionality
Generic Pages
- Index Pages - List view templates
- Detail Pages - Item detail templates
- Notification Pages - User notification center
- Sorting Pages - Content ordering interfaces
- Report Pages - Custom report building and execution
- Audit Logs - System activity tracking and history
Installation
npm install visns-components
# or
yarn add visns-componentsUsage
Basic Setup
import React from 'react';
import { GenericAuth } from 'visns-components';
const App = () => {
return (
<GenericAuth
logo="/path/to/logo.png"
headerLogo="/path/to/header-logo.png"
providers={['azure']} // Optional SSO providers
themeType="primary"
navigation={navigationConfig}
routeConfig={routeConfig}
/>
);
};
export default App;New Modular Architecture (v5.10.11+)
GenericIndex Component Refactoring
Starting from version 5.10.11, the GenericIndex component has been refactored for better maintainability and reusability:
Before: Single monolithic component (2,281 lines) After: Modular architecture with separated concerns
New Components
The refactoring introduced two new standalone components that can be used independently:
GenericGrid - Standalone grid component with full data management capabilities:
import { GenericGrid } from 'visns-components';
<GenericGrid
config={{
settings: {
fetch: {
url: '/api/data',
method: 'POST',
params: { filter: 'active' }
}
},
columns: [
{ name: 'id', header: 'ID', type: 'number' },
{ name: 'name', header: 'Name', type: 'text' },
{ name: 'date', header: 'Date', type: 'date' }
]
}}
userProfile={userProfile}
onDataChange={(data) => console.log('Data updated:', data)}
onRowClick={(row) => console.log('Row clicked:', row)}
/>GenericReportForm - Standalone export form component:
import { GenericReportForm } from 'visns-components';
<GenericReportForm
config={{
form: {
title: 'Export Data',
description: 'Select your export criteria below',
filters: [
{ id: 'startDate', type: 'date', label: 'Start Date', required: true },
{ id: 'endDate', type: 'date', label: 'End Date', required: true },
{ id: 'format', type: 'dropdown', label: 'Format', options: [
{ value: 'csv', label: 'CSV' },
{ value: 'xlsx', label: 'Excel' }
]}
],
url: '/api/export',
method: 'POST',
filename: 'export.csv'
}
}}
userProfile={userProfile}
onExport={(formData) => customExportHandler(formData)}
onFormChange={(formData) => console.log('Form changed:', formData)}
/>Benefits of the Refactoring
- Reduced complexity: GenericIndex reduced from 2,281 to 1,031 lines (55% reduction)
- Enhanced reusability: Grid and form components can be used independently
- Better maintainability: Focused components are easier to debug and modify
- Improved testing: Components can be unit tested separately
- Backward compatibility: All existing GenericIndex usage remains unchanged
Shared Utilities
The refactoring also introduced shared utility functions:
shared/formatters.js: Cell content formatting and styling utilitiesshared/gridUtils.js: Data operations (sorting, filtering, transformations)
These utilities are available for use in custom components and provide consistent functionality across the library.
GenericGrid Advanced Features
Filter Enhancements (v5.10.12+)
The GenericGrid component includes powerful filtering capabilities with URL-based options and multiselect support:
// URL-based filter options
{
id: 'status',
type: 'dropdown',
label: 'Status',
optionsUrl: '/api/filter-options/status',
optionsMethod: 'POST',
isMulti: true // Enables multiple selection
}
// Year filter with "now" default value
{
id: 'year',
type: 'year',
label: 'Year',
defaultValue: 'now' // Displays current year
}Key Features:
- Dynamic Options: Filters can fetch options from API endpoints using POST method
- Multiselect Control: Use
isMultiproperty for single/multiple selection control - Smart Defaults: Year filters properly handle "now" value to display current year
- Value Initialization: Enhanced value formatting for MultiSelect components
Contact Display Integration (v5.11.1+)
GenericGrid automatically displays associated contacts when date fields have contact relationships:
// Date column with contact display
{
id: 'survey_date',
type: 'date',
header: 'Survey Date',
onClick: {
type: 'date_with_contacts',
title: 'Mark Survey Complete',
message: 'Select the survey completion date'
}
}Features:
- Date + Contact Display: Shows formatted date with contact summary badge
- Contact Tooltips: Hover over contact badge for full contact details
- Flexible Data Structure: Supports both existing contacts and manual entries
- Responsive Design: Contact badges adapt to different screen sizes
Interactive Date Picker (v5.10.13+)
Enhanced date cell interaction with confirmation dialogs and timezone support:
// Date picker with timezone support
{
id: 'completion_date',
type: 'date',
onClick: {
type: 'date_picker',
title: 'Set Completion Date',
message: 'Choose the completion date',
placeholder: 'Select date',
timezone: 'auto', // or 'Australia/Perth'
dateOnly: true // For DATE fields vs DATETIME
}
}Timezone Features (v5.10.15+):
- Automatic Detection:
timezone: "auto"detects browser timezone - Australia/Perth Support: Built-in support with
forcePerth: true - Custom Timezones: Support any timezone with
timezone: "specific/timezone" - UTC Conversion: Proper conversion between local timezone and UTC for API calls
- Field Type Awareness:
dateOnlyparameter handles DATE vs DATETIME fields correctly
Contact Selection Workflow (v5.11.0+)
Two-step date and contact selection process for comprehensive data collection:
// Date + contact selection configuration
{
id: 'survey_date',
type: 'date',
onClick: {
type: 'date_with_contacts',
contactConfig: {
maxContacts: 5,
allowManual: true,
contactsEndpoint: '/api/contacts/search',
requiredFields: ['name', 'email']
}
}
}Contact Selection Features:
- ContactSelectorModal: Modal for selecting existing or adding manual contacts
- Multiple Contact Types: Database contacts (
type: 'contact') and manual entries (type: 'manual') - Flexible Limits: Configurable contact limits and required fields
- Survey Integration: Perfect for survey systems requiring contact associations
Toggleable Stage Functionality (v5.12.2+)
Interactive stage management with toggle functionality for both stage and stageCounter columns:
// Basic stage column with toggle
{
id: 'opportunity_stage',
type: 'stage',
keyIds: ['prospect', 'qualified', 'proposal', 'won'],
valueIds: ['Prospect', 'Qualified', 'Proposal', 'Won'],
toggleable: true,
toggleConfig: {
updateUrl: '/api/opportunities/{id}/stage',
updateMethod: 'PATCH',
updateKey: 'stage_field'
}
}
// StageCounter with individual stage toggles
{
id: 'project_stages',
type: 'stageCounter',
stageConfig: {
key: 'milestones',
stageLabelKey: 'milestone_name',
stageColour: {
key: 'status',
options: [
{ id: 'completed', colour: '#28a745' },
{ id: 'pending', colour: '#6c757d' }
]
},
toggleable: true,
toggleConfig: {
activeStatusValue: 'completed',
inactiveStatusValue: 'pending',
statusField: 'status',
updateUrl: '/api/project-stages/{stage_id}',
updateMethod: 'PATCH',
updateKey: 'status'
}
}
}Toggle Features:
- Interactive Management: Click stages to enable/disable with visual feedback
- API Integration: Configurable endpoints with URL placeholders and CSRF protection
- Toast Notifications: Real-time success/error feedback
- Auto-Refresh: Grid data refreshes after successful stage updates
- Mixed Interactions: StageCounter supports both stage toggling and container navigation
DropZone Component Enhancements (v5.14.0+)
Smart Data Extraction
The DropZone component has been significantly enhanced with intelligent data extraction capabilities for complex API responses:
Problem Solved: When uploading files to nested relationships (e.g., project → estimation → design → lead → documents), the API returns different model structures at different stages:
- Initial load:
project.estimation.design.lead.documents - After upload:
lead.documents(direct lead model response)
Solution: The DropZone component now automatically adapts the data extraction key based on the API response structure:
// Configuration
{
"key": "estimation.design.lead.documents",
"url": "/ajax/leads/{key}",
"urlKey": "estimation.design.lead.id",
"file_relationship": "documents"
}
// Smart adaptation:
// Initial: extracts from project.estimation.design.lead.documents
// After upload: automatically adapts to extract from response.data.documentsFile Type Icons
Enhanced visual file identification with dynamic icons based on file extensions:
- 📄 Documents: PDF, DOC, DOCX, TXT, RTF, ODT
- 📊 Spreadsheets: XLS, XLSX, CSV, ODS
- 🖼️ Images: JPG, JPEG, PNG, GIF, BMP, SVG, WEBP, ICO
- 💻 Code Files: JS, JSX, TS, TSX, HTML, CSS, PHP, PY, JAVA, etc.
- 📦 Archives: ZIP, RAR, 7Z, TAR, GZ, BZ2
- 🎵 Audio: MP3, WAV, FLAC, AAC, OGG, M4A
- 🎬 Video: MP4, AVI, MKV, MOV, WMV, FLV, WEBM
GenericDetail Integration
The GenericDetail component now uses the enhanced VisnsDropZone component instead of inline dropzone implementation, providing:
- Consistent file handling across all dropzone instances
- Automatic data refresh after file uploads
- Smart data extraction for nested relationships
- Improved state management and UI responsiveness
Technical Implementation
Key Features:
getAdaptedDataKey(): Intelligently adapts extraction paths based on response structuregetFileIcon(): Returns appropriate Lucide React icons based on file extensions- Enhanced
fetchDataintegration: Automatic page refresh after successful uploads - Comprehensive debug logging for troubleshooting data extraction issues
Backward Compatibility:
All existing DropZone implementations continue to work without modification. The smart features activate automatically when dataKey prop is provided.
Component Documentation
Authentication Components
GenericAuth
The main component that handles authentication and routing.
<GenericAuth
layout="full" // 'full' or 'compact'
loginBg="/path/to/background.jpg" // Optional login background
logo="/path/to/logo.png" // Logo for login/auth screens
headerLogo="/path/to/header-logo.png" // Logo for header in main app
providers={['azure']} // Optional SSO providers
navigation={navigationConfig} // Navigation configuration
routeConfig={routeConfig} // Route configuration
themeType="primary" // Theme type: 'primary', 'secondary', etc.
enforce2FA={false} // Whether to enforce 2FA (default: false)
clientPortalConfig={{
component: <ClientPortalComponent />, // Main component for client portal
urls: {
login: '/clientPortal/login',
verify: '/clientPortal/verify',
logout: '/clientPortal/logout',
profile: '/clientPortal/profile',
},
keys: {
name: ['firstname', 'surname'], // Fields to use for client name display
},
}}
/>Login
Handles user login with email and password.
<Login
logo="/path/to/logo.png"
providers={['azure']} // Optional SSO providers
setSystemAuth={setAuthFunction}
setUserProfile={setUserProfileFunction}
/>TwoFactorAuth
Handles 2FA verification.
<TwoFactorAuth
logo="/path/to/logo.png"
setSystemAuth={setAuthFunction}
setUserProfile={setUserProfileFunction}
/>Reset
Password reset request form.
<Reset logo="/path/to/logo.png" setSystemAuth={setAuthFunction} />Verify
Password reset verification form.
<Verify logo="/path/to/logo.png" setSystemAuth={setAuthFunction} />Profile
User profile management.
<Profile
userProfile={userProfileData}
setUserProfile={setUserProfileFunction}
/>DataGrid Component
A powerful data table component with sorting, filtering, and pagination. The DataGrid features a modular column renderer architecture with support for 28 different column types.
<DataGrid
ajaxSetting={{
url: '/api/data',
method: 'GET',
params: {},
}}
columns={[
{ name: 'id', headerName: 'ID', width: 90 },
{ name: 'name', headerName: 'Name', width: 150 },
{
name: 'department',
headerName: 'Department',
width: 180,
groupSummaryReducer: 'count',
groupSummaryRenderer: (count) => `${count} employees`,
},
{
name: 'salary',
headerName: 'Salary',
width: 120,
type: 'number',
groupSummaryReducer: 'sum',
groupSummaryRenderer: (sum) => `$${sum.toLocaleString()}`,
},
// More columns...
]}
form={formConfig}
gridHeight={500}
defaultGroupBy={[{ name: 'department' }]}
settings={{
pagination: true,
pageSize: 25,
// More settings...
}}
setFilterData={setFilterFunction}
setConfig={setConfigFunction}
style={
{
/* Custom styles */
}
}
/>Column Renderer Architecture
The DataGrid component utilizes a modular column renderer system with 28 specialized column types, all consolidated in the ColumnRenderers.jsx file. This architecture provides:
- Consistent API: All column renderers follow a standardized interface
- Extensibility: Easy to add new column types or modify existing ones
- Maintainability: Centralized location for all column rendering logic
- Performance: Optimized rendering for each specific column type
- Intelligent Sorting: Automatic detection of sortable relationship and JSON fields
Supported Column Types:
boolean- Yes/No display with tooltipscurrency- Formatted monetary valuesdate- Date formatting with validationdatetime- Date and time formattinghtml5_date- HTML5 native date picker inputarrayCount- Count of array elementscolour- Color picker displaydropdown- Interactive dropdown selections with optional background color supportimage- Image display from file URLsfile- File download links with iconsicons- Multiple icon displayjson- JSON data extraction and displaynumber- Numeric valuesoption- Option mapping with placeholdersphone- Phone number formatting for Australian numbersabn- Australian Business Number formattingplaceholder- Static placeholder textage- Age calculation from datescoding- Custom coding logicrelation- Related data displayrelationArray- Array of related datarichtext- HTML content renderingstage- Stage-based displays with optional toggle functionalitytime- Time formattingtimer- Interactive timer controlsurl- Clickable linksinput_text- Inline text editingaddress- Formatted address displaycreatedBy- User creation trackingdaterange- Date range formattingstageCounter- Interactive visual stage progression with toggle functionality
All column renderers are exported from @visns-studio/visns-components and can be used individually or as part of the DataGrid component.
Dropdown Column Color Support
The dropdown column type now supports automatic background color application based on the selected option's colour property. This feature works in both DataGrid and GenericEditableTable components.
Configuration Example:
{
id: 'status',
label: 'Status',
type: 'dropdown',
options: [
{ id: 'Pending', label: 'Pending', colour: '#FFE5B4' },
{ id: 'Approved', label: 'Approved', colour: '#B4E5D1' },
{ id: 'Rejected', label: 'Rejected', colour: '#FFCDD2' },
{ id: 'Disputed', label: 'Disputed', colour: '#E1F5FE' }
],
width: '10%'
}Features:
- Automatic Color Application: Cells automatically display the background color of the selected option
- Total Column Integration: Total columns inherit the same background color as the status column in the same row
- Consistent Styling: Black text color is automatically applied for optimal contrast
- Flexible Configuration: Works with both static column options and dynamic dropdown data
- Cross-Component Support: Available in both DataGrid and GenericEditableTable components
Example Usage in GenericEditableTable:
<GenericEditableTable
schedulingConfig={{
columns: [
{
id: 'status',
label: 'Status',
type: 'dropdown',
options: [
{ id: 'Pending', label: 'Pending', colour: '#FFE5B4' },
{ id: 'Approved', label: 'Approved', colour: '#B4E5D1' }
]
},
{
id: 'total',
label: 'Total',
type: 'total',
keys: ['amount', 'tax'] // Will inherit status color
}
]
}}
/>HTML5 Date Type
The html5_date column type provides native HTML5 date picker functionality in GenericEditableTable components.
Features:
- Native Date Picker: Uses browser's built-in date picker for consistent UX
- Cross-browser Support: Works across all modern browsers
- Consistent Styling: Matches existing date field styling
- Read-only Support: Respects column read-only configuration
- Change Integration: Fully integrated with existing field change handlers
Configuration Example:
{
id: 'start_date',
label: 'Start Date',
type: 'html5_date',
width: '15%'
}Phone and ABN Formatting
The DataGrid and GenericDetail components include specialized formatting for Australian phone numbers and ABNs (Australian Business Numbers).
Phone Number Formatting
The phone column type automatically formats phone numbers according to Australian standards:
Supported Formats:
- Mobile Numbers:
0400 000 000(04XX XXX XXX) - 1300/1800 Numbers:
1300 000 000(1300 XXX XXX) - Landline Numbers:
(02) 0000 0000((0X) XXXX XXXX) - Generic Formatting: Applied to other number patterns
- Fallback: Returns original value if no pattern matches
Configuration Examples:
DataGrid:
{
id: 'phone',
label: 'Phone Number',
type: 'phone',
width: '15%'
}GenericDetail:
{
id: 'phone',
label: 'Phone Number',
type: 'phone',
relation: ['company'] // Also works with relationship fields
}JSON Fields with Format:
{
id: 'contact_data',
label: 'Contact',
type: 'json',
jsonData: 'phone',
format: 'phone' // Applies phone formatting to JSON field values
}ABN Formatting
The abn column type formats Australian Business Numbers:
Format: 00 000 000 000 (XX XXX XXX XXX)
- Validates 11-digit ABNs
- Returns original value if not valid format
Configuration Example:
{
id: 'abn',
label: 'ABN',
type: 'abn',
width: '15%'
}Intelligent Relationship Sorting
The DataGrid component includes intelligent sorting capabilities that automatically detect and enable sorting for relationship and JSON fields. This feature works seamlessly with the backend HasRelationshipSorting trait.
Automatic Sortable Detection:
// The DataGrid automatically detects sortable fields based on naming patterns
<DataGrid
columns={[
{
name: 'user.profile.name', // Relationship field - automatically sortable
headerName: 'User Name',
nameFrom: ['user', 'profile', 'name']
},
{
name: 'settings.theme', // JSON field - automatically sortable
headerName: 'Theme Preference',
nameFrom: ['settings', 'theme']
},
{
name: 'client.company', // BelongsTo relationship - automatically sortable
headerName: 'Company',
nameFrom: ['client', 'company']
}
]}
settings={{
intelligentSorting: true // Enable intelligent sorting (default: true)
}}
/>Manual Sortable Control:
// Override automatic detection with explicit sortable configuration
<DataGrid
columns={[
{
name: 'user.profile.name',
headerName: 'User Name',
sortable: true, // Explicitly enable sorting
nameFrom: ['user', 'profile', 'name']
},
{
name: 'complex.calculation',
headerName: 'Complex Field',
sortable: false, // Explicitly disable sorting
nameFrom: ['complex', 'calculation']
}
]}
/>Supported Sorting Patterns:
The intelligent sorting system recognizes these field patterns as sortable:
- Relationship Fields:
user.profile.name,order.customer.company - JSON Fields:
settings.theme,metadata.tags,data.preferences.language - Standard Fields: Any direct model attribute
- Nested Relationships:
post.author.profile.display_name
Field Name Detection:
The sorting system intelligently determines sortable fields using:
nameFrom Array: Primary method for complex field paths
nameFrom: ['user', 'profile', 'full_name'] // → user.profile.full_nameDot Notation: Direct string paths
name: 'client.address.city' // → client.address.cityPattern Recognition: Automatic detection of common relationship patterns
- Fields ending with
_idthat have corresponding relationships - JSON field patterns like
data.*,settings.*,metadata.* - Nested object notation
- Fields ending with
Configuration Options:
<DataGrid
settings={{
// Global intelligent sorting toggle
intelligentSorting: true, // default: true
// Additional sorting configuration
sortingConfig: {
// Custom sortable field patterns
additionalPatterns: [
/^custom_data\./, // Match custom_data.* fields
/^config\./ // Match config.* fields
],
// Fields to exclude from automatic sorting
excludePatterns: [
/^temp\./, // Exclude temp.* fields
/^cache\./ // Exclude cache.* fields
]
}
}}
/>Backend Integration:
The frontend intelligent sorting works seamlessly with the backend HasRelationshipSorting trait:
// Frontend automatically generates these API calls:
// GET /ajax/users/table?orderBy=profile.name&order=asc
// GET /ajax/orders/table?orderBy=customer.company&order=desc
// GET /ajax/products/table?orderBy=metadata.category&order=ascBenefits:
- Zero Configuration: Most relationship and JSON fields work automatically
- Performance Optimized: Uses backend subqueries instead of joins
- Type Safety: Automatic validation of sortable field patterns
- Fallback Support: Graceful handling of unsupported sort fields
- Developer Friendly: Clear visual indicators for sortable columns
DataGrid Props
| Prop | Description | Type | Default | | --------------------- | ------------------------------------------ | -------- | ------- | | ajaxSetting | Configuration for data fetching | Object | - | | columns | Column definitions | Array | - | | defaultExpandedNodes | Initial expanded nodes for TreeGrid | Object | - | | defaultGroupBy | Initial grouping configuration | Array | - | | expandedNodes | Current expanded nodes for TreeGrid | Object | - | | form | Form configuration for editing | Object | - | | gridHeight | Height of the grid | Number | 500 | | groupBy | Current grouping configuration | Array | - | | loadNode | Function to load TreeGrid nodes on demand | Function | - | | onExpandedNodesChange | Callback for TreeGrid node expand/collapse | Function | - | | onGroupByChange | Callback for grouping changes | Function | - | | renderGroupTitle | Custom renderer for group headers | Function | - | | settings | Grid settings (pagination, etc.) | Object | - | | setFilterData | Callback for filter changes | Function | - | | setConfig | Callback for configuration changes | Function | - | | style | Custom styles | Object | - | | treeColumn | Column name to display TreeGrid hierarchy | String | - | | treeNestingSize | Indentation size for TreeGrid levels | Number | 20 |
Auto-Refresh Functionality
The DataGrid component includes an auto-refresh feature that automatically reloads data at specified intervals. This is useful for displaying real-time or frequently updated data.
To enable auto-refresh:
<DataGrid
ajaxSetting={{
url: '/api/data',
method: 'GET',
params: {},
autoRefresh: 30, // Refresh every 30 seconds
// or
autoRefresh: true, // Use default 30-second interval
}}
// Other props...
/>When auto-refresh is configured:
- A checkbox appears in the DataGrid header to enable/disable auto-refresh
- Auto-refresh is enabled by default when configured
- The interval can be customized (in seconds) by setting a numeric value
- Setting
autoRefresh: trueuses the default 30-second interval - The component silently refreshes data without showing notifications
Group By Functionality
The DataGrid component supports grouping data by one or more columns, allowing you to organize and visualize data in hierarchical structures.
Using defaultGroupBy
The defaultGroupBy property allows you to specify initial grouping when the DataGrid loads:
<DataGrid
// Other props...
defaultGroupBy={[
{ name: 'category' }, // Group by a single column
]}
/>For more complex grouping with multiple levels:
<DataGrid
// Other props...
defaultGroupBy={[
{ name: 'department' }, // First level grouping
{ name: 'role' }, // Second level grouping
]}
/>Dynamic Grouping
You can also control grouping programmatically:
const [groupBy, setGroupBy] = useState([{ name: 'category' }]);
// Later in your component
<DataGrid
// Other props...
groupBy={groupBy}
onGroupByChange={setGroupBy}
/>;Server-Side Grouping
For server-side grouping, the DataGrid sends the groupBy information to your API endpoint:
<DataGrid
ajaxSetting={{
url: '/api/data',
method: 'POST',
// Other settings...
}}
defaultGroupBy={[{ name: 'category' }]}
// Other props...
/>Your server will receive a request with groupBy information:
{
"page": 1,
"take": 25,
"groupBy": [{ "name": "category" }]
// Other parameters...
}Group Summaries
You can add summary calculations to grouped data:
<DataGrid
// Other props...
columns={[
{
name: 'amount',
headerName: 'Amount',
type: 'number',
groupSummaryReducer: 'sum', // Options: 'sum', 'avg', 'min', 'max', 'count'
groupSummaryRenderer: (summary) => `Total: $${summary.toFixed(2)}`,
},
// Other columns...
]}
defaultGroupBy={[{ name: 'category' }]}
/>Group-Level Bulk Edit Operations (Enhanced v5.13.0+)
The DataGrid component now supports powerful bulk editing capabilities through group-based operations, allowing users to efficiently manage multiple items simultaneously.
New in v5.13.0:
- Dropdown Column Color Support: Automatic background color application for dropdown columns based on option colors
- Total Column Color Integration: Total columns now inherit background colors from associated status columns
- HTML5 Date Type: Added native HTML5 date picker support for GenericEditableTable components
- Enhanced GenericEditableTable: Improved column type support and consistent styling across components
How Group Bulk Edit Works
When data is grouped using the groupBy or defaultGroupBy properties, each group header can display action icons for common operations. The new bulk edit functionality adds an "edit" icon that opens a form modal for updating all items in the group simultaneously.
Configuration Structure
Group operations are configured through the ajaxSetting.groupBySetting.icons array:
<DataGrid
ajaxSetting={{
url: '/api/data',
groupBy: ['category'],
groupBySetting: {
icons: [
{
id: 'edit',
label: 'Edit Group',
formModal: {
title: 'Bulk Edit Items',
url: '/ajax/data/bulkFormUpdate',
method: 'POST',
groupKey: 'category',
fields: ['assigned_user_id', 'status'],
data: {
stage: 'processing'
}
},
show: [
{
id: 'status',
value: 'pending'
}
]
}
]
}
}}
// Other props...
/>Configuration Properties
Icon Configuration:
id: Must be set to'edit'to enable bulk edit functionalitylabel: Display text for the action buttonformModal: Configuration object for the bulk edit formshow: Optional conditions to control when the icon appears
Form Modal Configuration:
title: Title displayed in the bulk edit modalurl: API endpoint for bulk update requestsmethod: HTTP method (typically 'POST')groupKey: Field name used for grouping (matches the groupBy value)fields: Array of field IDs that can be bulk editeddata: Additional data to send with the bulk update request
Form Integration
The bulk edit feature integrates seamlessly with the existing Form component. When the edit icon is clicked:
- A form modal opens with the specified title
- Only the fields listed in the
fieldsarray are displayed - The form shows an indication that this is a bulk operation
- On submission, all items in the group are updated simultaneously
Backend Integration
The bulk edit functionality expects a specific API endpoint structure:
// POST /ajax/data/bulkFormUpdate
{
"bulkEdit": true,
"groupValue": "Category Name",
"groupKey": "category",
"rowIds": [1, 2, 3, 4],
"assigned_user_id": 5,
"status": "in_progress",
// Additional form fields...
}
// Expected Response:
{
"error": "",
"message": "Bulk update completed successfully",
"data": {
"updated_count": 4,
"group_value": "Category Name",
"updated_items": [...]
}
}Features and Benefits
User Experience:
- No Confirmation Dialog: Edit actions open the form immediately (unlike timer operations)
- No Toast Notifications: Silent operation that focuses on the form interaction
- Visual Group Context: Modal title includes group information and item count
- Field Filtering: Only relevant fields are shown for bulk editing
Technical Features:
- Transaction Safety: All updates wrapped in database transactions
- Partial Updates: Only changed fields are updated
- Audit Trail: Full change tracking for compliance
- Error Handling: Graceful handling of validation errors and conflicts
- Performance Optimized: Efficient bulk operations with minimal database calls
Advanced Example
// Manufacturing workflow with bulk edit capabilities
<DataGrid
ajaxSetting={{
url: '/ajax/manufacturing/items',
groupBy: ['work_order'],
groupBySetting: {
icons: [
// Bulk edit for user assignment
{
id: 'edit',
label: 'Assign Operator',
formModal: {
title: 'Bulk Assign Operator',
url: '/ajax/manufacturing/bulkAssign',
method: 'POST',
groupKey: 'work_order',
fields: ['operator_id', 'priority'],
data: {
stage: 'production',
update_type: 'assignment'
}
},
show: [
{ id: 'status', value: 'ready' }
]
},
// Timer operations (existing functionality)
{
id: 'clock',
label: 'Start Production',
fetch: {
url: '/ajax/manufacturing/groupTimer',
method: 'POST',
data: {
action: 'start_production',
timestamp: 'now()'
}
}
}
]
}
}}
columns={[
{ name: 'item_code', headerName: 'Item Code' },
{ name: 'operator', headerName: 'Operator' },
{ name: 'status', headerName: 'Status' },
{ name: 'priority', headerName: 'Priority' }
]}
form={{
fields: [
{
id: 'operator_id',
label: 'Production Operator',
type: 'dropdown-ajax',
url: '/ajax/users/operators',
required: true
},
{
id: 'priority',
label: 'Priority Level',
type: 'select',
options: [
{ value: 'normal', label: 'Normal' },
{ value: 'high', label: 'High' },
{ value: 'urgent', label: 'Urgent' }
]
}
]
}}
/>This implementation provides a powerful and intuitive way to manage bulk operations in grouped data scenarios, significantly improving workflow efficiency for users managing large datasets.
Customizing Group Rendering
You can customize how groups are displayed:
<DataGrid
// Other props...
renderGroupTitle={(groupData) => (
<div style={{ fontWeight: 'bold', color: 'var(--primary-color)' }}>
{groupData.name}: {groupData.value} ({groupData.count} items)
</div>
)}
defaultGroupBy={[{ name: 'category' }]}
/>Complete Example with Dynamic Grouping
Here's a complete example of a component that uses DataGrid with dynamic grouping controls:
import React, { useState, useEffect } from 'react';
import { DataGrid } from 'visns-components';
const EmployeeDataGrid = () => {
// State for grouping
const [groupBy, setGroupBy] = useState([]);
// Sample data for the grid
const [employees, setEmployees] = useState([]);
// Fetch data on component mount
useEffect(() => {
const fetchEmployees = async () => {
try {
const response = await fetch('/api/employees');
const data = await response.json();
setEmployees(data);
} catch (error) {
console.error('Error fetching employees:', error);
}
};
fetchEmployees();
}, []);
// Column definitions
const columns = [
{
name: 'id',
headerName: 'ID',
width: 80,
},
{
name: 'name',
headerName: 'Name',
width: 180,
},
{
name: 'department',
headerName: 'Department',
width: 150,
groupSummaryReducer: 'count',
groupSummaryRenderer: (count) => `${count} employees`,
},
{
name: 'role',
headerName: 'Role',
width: 150,
},
{
name: 'salary',
headerName: 'Salary',
width: 120,
type: 'number',
groupSummaryReducer: 'sum',
groupSummaryRenderer: (sum) => `$${sum.toLocaleString()}`,
},
{
name: 'hireDate',
headerName: 'Hire Date',
width: 120,
type: 'date',
},
];
// Group by options for the dropdown
const groupByOptions = [
{ label: 'No Grouping', value: 'none' },
{ label: 'Department', value: 'department' },
{ label: 'Role', value: 'role' },
{ label: 'Department & Role', value: 'department-role' },
];
// Handle group by change
const handleGroupByChange = (e) => {
const value = e.target.value;
switch (value) {
case 'department':
setGroupBy([{ name: 'department' }]);
break;
case 'role':
setGroupBy([{ name: 'role' }]);
break;
case 'department-role':
setGroupBy([{ name: 'department' }, { name: 'role' }]);
break;
default:
setGroupBy([]);
break;
}
};
return (
<div>
<div style={{ marginBottom: '1rem' }}>
<label htmlFor="groupBy">Group By: </label>
<select
id="groupBy"
onChange={handleGroupByChange}
style={{ padding: '0.5rem', borderRadius: '4px' }}
>
{groupByOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
<DataGrid
columns={columns}
dataSource={employees}
groupBy={groupBy}
onGroupByChange={setGroupBy}
gridHeight={600}
settings={{
pagination: true,
pageSize: 25,
}}
renderGroupTitle={(groupData) => (
<div
style={{
fontWeight: 'bold',
color: 'var(--primary-color)',
padding: '0.25rem 0',
}}
>
{groupData.name === 'department'
? 'Department'
: 'Role'}
:<span style={{ marginLeft: '0.5rem' }}>
{groupData.value} ({groupData.count} employees)
</span>
</div>
)}
/>
</div>
);
};
export default EmployeeDataGrid;TreeGrid Functionality
The DataGrid component also supports hierarchical data display through its TreeGrid functionality. This allows you to represent parent-child relationships in your data with expandable/collapsible nodes.
Basic TreeGrid Configuration
To use the TreeGrid functionality:
<DataGrid
// Other props...
treeColumn="name" // Specify which column will display the expand/collapse icons
dataSource={hierarchicalData} // Data with parent-child relationships
/>Your data should have a structure where parent nodes contain child nodes:
const hierarchicalData = [
{
id: 1,
name: 'Parent 1',
nodes: [
// Child nodes
{ id: 11, name: 'Child 1.1' },
{
id: 12,
name: 'Child 1.2',
nodes: [
// Nested children
{ id: 121, name: 'Grandchild 1.2.1' },
],
},
],
},
{
id: 2,
name: 'Parent 2',
nodes: [{ id: 21, name: 'Child 2.1' }],
},
];Controlling Expanded Nodes
You can control which nodes are expanded:
const [expandedNodes, setExpandedNodes] = useState({
1: true, // Node with ID 1 is expanded
'1/12': true, // Node with path 1/12 is expanded
});
<DataGrid
// Other props...
treeColumn="name"
dataSource={hierarchicalData}
expandedNodes={expandedNodes}
onExpandedNodesChange={setExpandedNodes}
/>;Asynchronous Node Loading
For large datasets, you can load child nodes on demand:
<DataGrid
// Other props...
treeColumn="name"
dataSource={asyncHierarchicalData}
loadNode={({ node }) => {
// Return a Promise that resolves to the child nodes
return fetchChildNodes(node.id);
}}
/>For asynchronous loading, parent nodes should have nodes: null to indicate they have children that need to be loaded.
Complete TreeGrid Example
Here's a complete example of a component that uses DataGrid with TreeGrid functionality:
import React, { useState } from 'react';
import { DataGrid } from 'visns-components';
const OrganizationTreeGrid = () => {
// Sample hierarchical data
const departments = [
{
id: 1,
name: 'Executive',
budget: 1500000,
nodes: [
{
id: 11,
name: 'Finance',
budget: 850000,
nodes: [
{ id: 111, name: 'Accounting', budget: 350000 },
{ id: 112, name: 'Investment', budget: 500000 },
],
},
{
id: 12,
name: 'Operations',
budget: 650000,
nodes: [
{ id: 121, name: 'HR', budget: 200000 },
{ id: 122, name: 'Facilities', budget: 450000 },
],
},
],
},
{
id: 2,
name: 'Sales & Marketing',
budget: 1200000,
nodes: [
{ id: 21, name: 'Sales', budget: 700000 },
{ id: 22, name: 'Marketing', budget: 500000 },
],
},
{
id: 3,
name: 'Technology',
budget: 2000000,
nodes: [
{ id: 31, name: 'Development', budget: 1200000 },
{ id: 32, name: 'IT Support', budget: 800000 },
],
},
];
// State for expanded nodes
const [expandedNodes, setExpandedNodes] = useState({
1: true, // Executive department is expanded by default
3: true, // Technology department is expanded by default
});
// Column definitions
const columns = [
{
name: 'name',
headerName: 'Department',
width: 250,
// This column will display the tree expand/collapse icons
},
{
name: 'budget',
headerName: 'Budget',
width: 150,
type: 'number',
render: ({ value }) => `$${value.toLocaleString()}`,
},
];
return (
<div>
<h2>Organization Structure</h2>
<DataGrid
columns={columns}
dataSource={departments}
treeColumn="name"
expandedNodes={expandedNodes}
onExpandedNodesChange={setExpandedNodes}
treeNestingSize={30} // Indentation size for nested levels
gridHeight={500}
idProperty="id"
/>
</div>
);
};
export default OrganizationTreeGrid;Form Components
Form
A flexible form component with validation.
<Form
fields={[
{
name: 'firstName',
label: 'First Name',
type: 'text',
required: true,
},
{ name: 'email', label: 'Email', type: 'email', required: true },
// More fields...
]}
onSubmit={handleSubmit}
initialValues={initialData}
validationSchema={validationSchema}
/>AsyncSelect
Asynchronous select component with search functionality and dynamic data loading.
<AsyncSelect
url="/api/options"
valueKey="id"
labelKey="name"
placeholder="Select an option"
onChange={handleChange}
inputValue={selectedValue}
settings={{
id: 'myAsyncSelect',
params: { additionalParam: 'value' }, // Optional: additional parameters to send with the request
}}
isCreatable={false} // Optional: allow creating new options
creatableConfig={{
// Configuration for creatable options
url: '/api/options/create',
method: 'POST',
}}
/>The AsyncSelect component supports these features:
- Dynamic Data Loading: Loads options from an API endpoint as the user types
- Debounced Requests: Prevents excessive API calls during typing
- Custom Parameters: Send additional parameters with the API request
- Creatable Options: Allow users to create new options that don't exist in the API
MultiSelect
Select component with multiple selection support and optional selection limits.
<MultiSelect
options={options}
inputValue={selectedValues}
onChange={handleChange}
placeholder="Select options"
multi={true}
settings={{
id: 'myMultiSelect',
limit: 3, // Optional: limit the number of selections
}}
isCreatable={false} // Optional: allow creating new options
creatableConfig={{}} // Configuration for creatable options
/>The MultiSelect component supports these features:
- Selection Limits: Set a maximum number of items that can be selected
- Single Item Mode: When
limit={1}withmulti={true}, it keeps only the most recently selected item - Creatable Options: Allow users to create new options that don't exist in the list
Autocomplete
Location autocomplete with Mapbox integration.
<Autocomplete
apiKey="your-mapbox-api-key"
placeholder="Enter a location"
onChange={handleLocationChange}
value={location}
/>DropZone
Advanced file upload component with drag and drop support, smart data extraction, and file type icons.
Key Features:
- Smart Data Extraction: Automatically adapts to API response structures using nested keys
- File Type Icons: Dynamic icons based on file extensions (PDF, images, documents, etc.)
- Nested API Support: Handles complex data structures like
estimation.design.lead.documents - Auto-Refresh: Intelligent file list updates after upload
- Gallery Mode: Image preview and gallery display
- Progress Tracking: Upload progress indication
- File Management: Edit, delete, and download functionality
Basic Usage:
<DropZone
fetchData={handleRefresh}
files={currentFiles}
settings={{
url: '/ajax/files',
method: 'POST',
type: 'gallery', // or 'list'
data: {
file_relationship: 'documents'
},
folder: 'uploads',
filetype: 'image',
deleteUrl: '/ajax/files/delete'
}}
/>Smart Data Extraction:
<DropZone
fetchData={handleRefresh}
files={files}
url="/ajax/leads/{key}"
urlKey="estimation.design.lead.id"
dataKey="documents" // Extracts files from response.data.documents
entityData={projectData}
routeParams={routeParams}
settings={{
method: 'PUT',
data: {
file_relationship: 'documents'
}
}}
/>Supported File Types & Icons:
- 📄 Documents: PDF, DOC, DOCX, TXT, RTF, ODT
- 📊 Spreadsheets: XLS, XLSX, CSV, ODS
- 🖼️ Images: JPG, JPEG, PNG, GIF, BMP, SVG, WEBP, ICO
- 💻 Code Files: JS, JSX, TS, TSX, HTML, CSS, PHP, PY, JAVA, etc.
- 📦 Archives: ZIP, RAR, 7Z, TAR, GZ, BZ2
- 🎵 Audio: MP3, WAV, FLAC, AAC, OGG, M4A
- 🎬 Video: MP4, AVI, MKV, MOV, WMV, FLV, WEBM
Configuration Options:
fetchData: Function called after successful uploads to refresh datafiles: Array of current files to displayurl: Upload endpoint (supports {key} placeholders)urlKey: Dot notation path for URL parameter replacementdataKey: Dot notation path for extracting files from API responsesdeleteUrl: Endpoint for file deletionentityData: Context data for URL parameter resolutionrouteParams: Route parameters for URL processingsettings.type: 'gallery' for images, 'list' for documentssettings.filetype: File type restrictionssettings.folder: Upload folder designation
Association Management Components
AssociationManager
A sophisticated component for managing entity relationships and associations within your application. The AssociationManager provides an intuitive interface for creating, viewing, and managing complex many-to-many relationships between different data entities.
<AssociationManager
config={{
title: "Client Associations",
description: "Manage relationships between clients and projects",
primaryEntity: {
id: "clients",
label: "Clients",
displayKey: "name",
fetchUrl: "/api/clients"
},
secondaryEntity: {
id: "projects",
label: "Projects",
displayKey: "title",
fetchUrl: "/api/projects"
},
association: {
fetchUrl: "/api/client-project-associations",
createUrl: "/api/client-project-associations",
deleteUrl: "/api/client-project-associations",
pivotTable: "client_project"
},
permissions: {
canCreate: true,
canDelete: true,
canView: true
},
ui: {
searchPlaceholder: "Search clients or projects...",
emptyStateMessage: "No associations found",
confirmDeleteMessage: "Are you sure you want to remove this association?"
}
}}
userProfile={userProfile}
onAssociationChange={(data) => console.log('Association changed:', data)}
/>Key Features:
Bidirectional Relationship Management:
- Create associations from either entity perspective
- Automatic relationship synchronization
- Real-time updates across related views
Advanced Search and Filtering:
- Multi-entity search capabilities
- Filter by entity type, status, or custom attributes
- Debounced search to optimize performance
Bulk Operations:
- Select multiple entities for batch association
- Bulk delete with confirmation dialogs
- Mass import/export functionality
Visual Association Mapping:
- Interactive relationship visualization
- Drag-and-drop association creation
- Color-coded entity types
Permission-Based Access Control:
- Granular permissions for create, read, update, delete
- Role-based access to specific entity types
- Custom permission validation
Configuration Options:
const associationConfig = {
// Required: Basic entity configuration
primaryEntity: {
id: "clients", // Unique identifier
label: "Clients", // Display name
displayKey: "name", // Field to show in lists
fetchUrl: "/api/clients", // API endpoint
searchFields: ["name", "email"], // Fields to search
icon: "Users", // Lucide icon name
color: "#3b82f6" // Brand color
},
secondaryEntity: {
id: "projects",
label: "Projects",
displayKey: "title",
fetchUrl: "/api/projects",
searchFields: ["title", "description"],
icon: "FolderOpen",
color: "#10b981"
},
// Association configuration
association: {
fetchUrl: "/api/associations", // Get existing associations
createUrl: "/api/associations", // Create new associations
updateUrl: "/api/associations", // Update associations (optional)
deleteUrl: "/api/associations", // Delete associations
pivotTable: "client_project", // Database table name
// Additional pivot data
pivotFields: [
{
name: "role",
label: "Role",
type: "select",
options: [
{ value: "manager", label: "Manager" },
{ value: "contributor", label: "Contributor" }
]
},
{
name: "start_date",
label: "Start Date",
type: "date"
}
]
},
// UI customization
ui: {
layout: "split", // "split", "tabs", "cards"
theme: "default", // "default", "compact", "detailed"
showEntityCounts: true, // Show association counts
showLastModified: true, // Show modification timestamps
enableDragDrop: true, // Enable drag-and-drop
confirmActions: true, // Show confirmation dialogs
// Custom labels and messages
labels: {
createButton: "Add Association",
deleteButton: "Remove",
searchPlaceholder: "Search entities...",
noResultsMessage: "No entities found",
loadingMessage: "Loading associations..."
}
},
// Validation rules
validation: {
preventDuplicates: true, // Prevent duplicate associations
requirePivotData: false, // Require additional pivot fields
customValidation: (primary, secondary, pivotData) => {
// Custom validation logic
return { valid: true, message: "" };
}
},
// Performance optimization
performance: {
enableVirtualization: true, // For large datasets
pageSize: 50, // Pagination size
debounceMs: 300, // Search debounce
cacheResults: true // Cache API responses
}
};API Integration:
The AssociationManager expects specific API response formats:
// GET /api/clients (Primary Entity)
{
"data": [
{
"id": 1,
"name": "Acme Corp",
"email": "[email protected]",
"created_at": "2023-01-01T00:00:00Z"
}
],
"total": 150,
"page": 1,
"per_page": 50
}
// GET /api/associations (Existing Associations)
{
"data": [
{
"id": 1,
"primary_id": 1,
"secondary_id": 5,
"pivot_data": {
"role": "manager",
"start_date": "2023-01-01"
},
"primary": {
"id": 1,
"name": "Acme Corp"
},
"secondary": {
"id": 5,
"title": "Website Redesign"
},
"created_at": "2023-01-01T00:00:00Z"
}
]
}
// POST /api/associations (Create Association)
{
"primary_id": 1,
"secondary_id": 5,
"pivot_data": {
"role": "manager",
"start_date": "2023-01-01"
}
}
// Response:
{
"success": true,
"data": {
"id": 1,
"primary_id": 1,
"secondary_id": 5,
"created_at": "2023-01-01T00:00:00Z"
},
"message": "Association created successfully"
}Event Handling:
<AssociationManager
config={associationConfig}
onAssociationChange={(event) => {
// event.type: 'created', 'updated', 'deleted'
// event.data: Association data
// event.entities: Affected entities
console.log('Association event:', event);
}}
onEntitySelect={(entity, type) => {
// Handle entity selection
console.log('Entity selected:', entity, type);
}}
onError={(error) => {
// Handle errors
console.error('Association error:', error);
}}
/>Advanced Usage Examples:
- Client-Project Association with Roles:
// Managing client assignments to projects with specific roles
<AssociationManager
config={{
primaryEntity: { id: "clients", label: "Clients", ... },
secondaryEntity: { id: "projects", label: "Projects", ... },
association: {
pivotFields: [
{
name: "role",
label: "Role in Project",
type: "select",
required: true,
options: [
{ value: "sponsor", label: "Project Sponsor" },
{ value: "stakeholder", label: "Key Stakeholder" },
{ value: "user", label: "End User" }
]
}
]
}
}}
/>- User-Permission Association:
// Managing user permissions with expiration dates
<AssociationManager
config={{
primaryEntity: { id: "users", label: "Users", ... },
secondaryEntity: { id: "permissions", label: "Permissions", ... },
association: {
pivotFields: [
{
name: "expires_at",
label: "Expiration Date",
type: "datetime",
required: false
},
{
name: "granted_by",
label: "Granted By",
type: "select",
fetchUrl: "/api/users/managers"
}
]
}
}}
/>Business Card OCR Component
BusinessCardOcr (Enhanced v5.11.1+)
An advanced OCR (Optical Character Recognition) component specifically designed for scanning business cards and automatically extracting contact information. The component provides intelligent text recognition, client matching, and seamless contact management integration.
<BusinessCardOcr
isOpen={showScanner}
onClose={() => setShowScanner(false)}
onContactSaved={(contact) => {
console.log('Contact saved:', contact);
// Handle the saved contact data
}}
fields={[
{ id: 'name', label: 'Full Name', required: tru