Skip to content

COLUMN Extensions

COLUMN extensions add custom columns to list and table views, displaying calculated data, plugin-specific metrics, or custom formatting.

  • Display calculated metrics (total hours, completion %)
  • Show plugin-specific data
  • Custom formatting and rendering
  • Sortable and filterable columns
  • Action columns with buttons
{
point: 'work-unit.list.columns',
plugin: 'timer',
priority: 60,
match: {
businessType: ['SERVICE', 'PROJECT']
},
extension: {
type: ExtensionType.COLUMN,
id: 'hours-tracked',
header: 'Time Tracked',
accessor: (row) => getHoursTracked(row),
cell: TimerColumnCell,
width: 120,
sortable: true,
align: 'center'
}
}
  • type: ExtensionType.COLUMN
  • id: Unique identifier
  • header: Column header text
  • accessor: (row: any) => any - Extract value from row
  • cell: Cell renderer component
  • width: Column width in pixels
  • sortable: Enable sorting
  • filterable: Enable filtering
  • align: 'left' | 'center' | 'right'
  • sticky: Pin column to left/right
export function TimerColumnCell({ row }: { row: any }) {
const hours = getHoursTracked(row);
if (hours === 0) {
return <Text c="dimmed">—</Text>;
}
return (
<Group gap="xs">
<IconClock size={14} />
<Text size="sm">{hours.toFixed(1)}h</Text>
</Group>
);
}
export function getHoursTracked(row: any): number {
return row.totalSeconds ? row.totalSeconds / 3600 : 0;
}
{
type: ExtensionType.COLUMN,
id: 'completion',
header: 'Completion',
accessor: (row) => row.completionPercentage || 0,
cell: CompletionCell,
sortable: true,
sortFn: (a, b) => {
return (a.completionPercentage || 0) - (b.completionPercentage || 0);
},
width: 100
}
function CompletionCell({ row }: { row: any }) {
const percentage = row.completionPercentage || 0;
return <Progress value={percentage} label={`${percentage}%`} />;
}
{
type: ExtensionType.COLUMN,
id: 'quick-actions',
header: 'Actions',
cell: QuickActionsCell,
width: 120,
align: 'center',
sortable: false
}
function QuickActionsCell({ row }: { row: any }) {
const [isProcessing, setIsProcessing] = useState(false);
return (
<Group gap="xs">
<ActionIcon
loading={isProcessing}
onClick={async () => {
setIsProcessing(true);
await processItem(row.id);
setIsProcessing(false);
}}
>
<IconCheck />
</ActionIcon>
<ActionIcon color="error">
<IconTrash />
</ActionIcon>
</Group>
);
}
  • Keep cell components simple and fast
  • Cache expensive calculations
  • Handle null/undefined values gracefully
  • Use consistent formatting
  • Provide meaningful empty states