Integration Constants
Default column configurations for integration data display and management.
Overview
The Integration Constants provide standardized column definitions for displaying integration-related data in tables and data grids. These constants ensure consistent data presentation across different integration modules within the WorkPayCore application.
Constants
Default Columns
Standard column configuration for integration data tables:
const defaultColumns = [
{
Header: 'Expense categories',
accessor: 'name',
},
{
Header: 'Earnings & Deductions',
accessor: 'name',
},
{
Header: 'Leave Policies',
accessor: 'name',
},
];
export default defaultColumns;
Usage Examples
Basic Table Implementation
import React from 'react';
import { useTable } from 'react-table';
import defaultColumns from '@/utils/constants/integrations';
interface IntegrationTableProps {
data: Array<{ name: string; [key: string]: any }>;
type: 'expense' | 'earnings' | 'leave';
}
const IntegrationTable: React.FC<IntegrationTableProps> = ({ data, type }) => {
// Filter columns based on integration type
const getColumnsForType = (integrationType: string) => {
const typeMap = {
expense: [defaultColumns[0]], // Expense categories
earnings: [defaultColumns[1]], // Earnings & Deductions
leave: [defaultColumns[2]], // Leave Policies
};
return typeMap[integrationType as keyof typeof typeMap] || defaultColumns;
};
const columns = React.useMemo(() => getColumnsForType(type), [type]);
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
useTable({ columns, data });
return (
<table {...getTableProps()} className='integration-table'>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => (
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
))}
</tr>
);
})}
</tbody>
</table>
);
};
export default IntegrationTable;
Extended Column Configuration
import React from 'react';
import defaultColumns from '@/utils/constants/integrations';
// Extend default columns with additional functionality
const extendedColumns = defaultColumns.map(column => ({
...column,
Cell: ({ value }: { value: string }) => (
<div className='integration-cell'>
<span className='cell-value'>{value}</span>
<button className='edit-btn' onClick={() => handleEdit(value)}>
Edit
</button>
</div>
),
}));
interface ExtendedIntegrationTableProps {
data: Array<{ name: string }>;
onEdit: (name: string) => void;
}
const ExtendedIntegrationTable: React.FC<ExtendedIntegrationTableProps> = ({
data,
onEdit,
}) => {
const handleEdit = (name: string) => {
onEdit(name);
};
const columns = React.useMemo(
() =>
extendedColumns.map(col => ({
...col,
Cell: ({ value }: { value: string }) => (
<div className='integration-cell'>
<span className='cell-value'>{value}</span>
<button className='edit-btn' onClick={() => handleEdit(value)}>
Edit
</button>
</div>
),
})),
[onEdit],
);
// Table implementation...
return <div className='extended-integration-table'>{/* Table JSX */}</div>;
};
Dynamic Column Generation
import React from 'react';
import defaultColumns from '@/utils/constants/integrations';
interface DynamicIntegrationTableProps {
integrationType: 'expense' | 'earnings' | 'leave' | 'all';
data: Array<{ name: string; category?: string; type?: string }>;
showActions?: boolean;
}
const DynamicIntegrationTable: React.FC<DynamicIntegrationTableProps> = ({
integrationType,
data,
showActions = false,
}) => {
const generateColumns = React.useMemo(() => {
let columns = [];
// Base columns from constants
if (integrationType === 'all') {
columns = [...defaultColumns];
} else {
const columnMap = {
expense: defaultColumns[0],
earnings: defaultColumns[1],
leave: defaultColumns[2],
};
columns = [columnMap[integrationType]];
}
// Add additional columns based on data structure
if (data.some(item => item.category)) {
columns.push({
Header: 'Category',
accessor: 'category',
});
}
if (data.some(item => item.type)) {
columns.push({
Header: 'Type',
accessor: 'type',
});
}
// Add actions column if needed
if (showActions) {
columns.push({
Header: 'Actions',
accessor: 'actions',
Cell: ({ row }: { row: any }) => (
<div className='action-buttons'>
<button onClick={() => handleView(row.original)}>View</button>
<button onClick={() => handleEdit(row.original)}>Edit</button>
<button onClick={() => handleDelete(row.original)}>Delete</button>
</div>
),
});
}
return columns;
}, [integrationType, data, showActions]);
const handleView = (item: any) => {
console.log('View:', item);
};
const handleEdit = (item: any) => {
console.log('Edit:', item);
};
const handleDelete = (item: any) => {
console.log('Delete:', item);
};
// Table implementation with generated columns...
return (
<div className='dynamic-integration-table'>
{/* Table implementation */}
</div>
);
};
Integration Data Hook
import { useState, useEffect } from 'react';
import defaultColumns from '@/utils/constants/integrations';
interface IntegrationData {
name: string;
category?: string;
type?: string;
id?: string;
}
export const useIntegrationData = (integrationType: string) => {
const [data, setData] = useState<IntegrationData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const getColumnHeaders = () => {
return defaultColumns.map(col => col.Header);
};
const validateDataStructure = (items: IntegrationData[]) => {
const requiredFields = ['name'];
return items.every(item =>
requiredFields.every(
field =>
item.hasOwnProperty(field) && item[field as keyof IntegrationData],
),
);
};
const fetchIntegrationData = async () => {
try {
setLoading(true);
const response = await fetch(`/api/integrations/${integrationType}`);
const result = await response.json();
if (validateDataStructure(result)) {
setData(result);
} else {
throw new Error('Invalid data structure');
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchIntegrationData();
}, [integrationType]);
return {
data,
loading,
error,
columnHeaders: getColumnHeaders(),
refetch: fetchIntegrationData,
};
};
Integration Filter Component
import React, { useState } from 'react';
import defaultColumns from '@/utils/constants/integrations';
interface IntegrationFilterProps {
data: Array<{ name: string; category?: string }>;
onFilterChange: (
filteredData: Array<{ name: string; category?: string }>,
) => void;
}
const IntegrationFilter: React.FC<IntegrationFilterProps> = ({
data,
onFilterChange,
}) => {
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('all');
const availableCategories = React.useMemo(() => {
const categories = new Set(data.map(item => item.category).filter(Boolean));
return Array.from(categories);
}, [data]);
const filteredData = React.useMemo(() => {
return data.filter(item => {
const matchesSearch = item.name
.toLowerCase()
.includes(searchTerm.toLowerCase());
const matchesCategory =
selectedCategory === 'all' || item.category === selectedCategory;
return matchesSearch && matchesCategory;
});
}, [data, searchTerm, selectedCategory]);
React.useEffect(() => {
onFilterChange(filteredData);
}, [filteredData, onFilterChange]);
return (
<div className='integration-filter'>
<div className='filter-controls'>
<input
type='text'
placeholder='Search by name...'
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
className='search-input'
/>
<select
value={selectedCategory}
onChange={e => setSelectedCategory(e.target.value)}
className='category-select'
>
<option value='all'>All Categories</option>
{availableCategories.map(category => (
<option key={category} value={category}>
{category}
</option>
))}
</select>
</div>
<div className='filter-info'>
Showing {filteredData.length} of {data.length} items
</div>
</div>
);
};
export default IntegrationFilter;
TypeScript Definitions
// Column definition type
export interface IntegrationColumn {
Header: string;
accessor: string;
Cell?: (props: any) => React.ReactNode;
}
// Integration data types
export interface IntegrationData {
name: string;
category?: string;
type?: string;
id?: string;
[key: string]: any;
}
// Integration type enum
export enum IntegrationType {
EXPENSE = 'expense',
EARNINGS = 'earnings',
LEAVE = 'leave',
}
// Table props type
export interface IntegrationTableProps {
data: IntegrationData[];
columns?: IntegrationColumn[];
type?: IntegrationType;
showActions?: boolean;
onEdit?: (item: IntegrationData) => void;
onDelete?: (item: IntegrationData) => void;
onView?: (item: IntegrationData) => void;
}
Best Practices
1. Column Consistency
// ✅ Good - consistent column structure
const customColumns = [
{
Header: 'Name',
accessor: 'name',
},
{
Header: 'Category',
accessor: 'category',
},
];
// ❌ Bad - inconsistent structure
const customColumns = [
{ title: 'Name', key: 'name' }, // Different property names
{ Header: 'Category', accessor: 'category' },
];
2. Data Validation
// ✅ Good - validate data before rendering
const validateIntegrationData = (data: any[]): IntegrationData[] => {
return data.filter(
item =>
item && typeof item.name === 'string' && item.name.trim().length > 0,
);
};
3. Performance Optimization
// ✅ Good - memoize columns
const columns = React.useMemo(
() => generateColumns(integrationType),
[integrationType],
);
// ✅ Good - memoize filtered data
const filteredData = React.useMemo(
() => applyFilters(data, filters),
[data, filters],
);
Performance Considerations
1. Virtual Scrolling
// For large datasets, implement virtual scrolling
import { FixedSizeList as List } from 'react-window';
const VirtualizedIntegrationTable = ({ data }: { data: IntegrationData[] }) => {
const Row = ({
index,
style,
}: {
index: number;
style: React.CSSProperties;
}) => <div style={style}>{data[index].name}</div>;
return (
<List height={400} itemCount={data.length} itemSize={50}>
{Row}
</List>
);
};
2. Pagination
// Implement pagination for better performance
export const usePaginatedIntegrationData = (
data: IntegrationData[],
pageSize: number = 10,
) => {
const [currentPage, setCurrentPage] = useState(1);
const paginatedData = React.useMemo(() => {
const startIndex = (currentPage - 1) * pageSize;
return data.slice(startIndex, startIndex + pageSize);
}, [data, currentPage, pageSize]);
return {
data: paginatedData,
currentPage,
totalPages: Math.ceil(data.length / pageSize),
setCurrentPage,
};
};
Related Utilities
- Options - For integration form options
- Settings - For integration settings configuration
- EOR Portal Constants - For EOR integration specifics
- Feature Flags - For conditional integration features
Migration Notes
When updating integration constants:
- Maintain column structure for backward compatibility
- Test table rendering after changes
- Update TypeScript types when adding new columns
- Consider data migration for existing integrations
- Document breaking changes in migration guides