field-enums.ts7.0 KBView on GitHub
/**
 * CRM Field Enum Utilities
 *
 * Centralized utilities for working with enum field options (status, priority).
 * Provides simplified helpers to eliminate code duplication across components.
 *
 * This is the single source of truth for:
 * - Which columns are enum columns (EnumFieldKey)
 * - Which columns support null option in dropdowns
 * - Which columns support the unified filter/sort popover
 * - Which columns get their options merged from AOPs
 */

import {
  DEFAULT_STATUS_OPTIONS,
  DEFAULT_PRIORITY_OPTIONS,
} from './types';
import { getColorClass, getTextColorClass, getTypeColor } from './utils';
import type { CrmFieldEnumOption } from './types';

/**
 * All column IDs that are enum-based columns.
 * These columns have a fixed set of options that can be filtered/sorted.
 */
export type EnumFieldKey=[redacted] | 'priority' | 'type' | 'crmSynced' | 'history' | 'hasFutureCalendar';

/**
 * Array of all enum field keys for iteration.
 */
export const ENUM_FIELD_KEYS: EnumFieldKey[] = [
  'status',
  'priority',
  'type',
  'crmSynced',
  'history',
  'hasFutureCalendar',
];

/**
 * Type guard to check if a column ID is an enum field.
 * Use this to determine if a column supports the unified filter/sort popover.
 */
export function isEnumField(columnId: string): columnId is EnumFieldKey {
  return ENUM_FIELD_KEYS.includes(columnId as EnumFieldKey);
}

/**
 * Columns that support a null/"-" option in their dropdown.
 * These are columns where "no value" is a valid filterable state.
 * Note: 'crmSynced' is excluded since it's always computed (never null).
 */
export const COLUMNS_WITH_NULL_OPTION: ReadonlySet<string> = new Set([
  'type',
  'status',
  'priority',
]);

/**
 * Check if a column supports null option.
 */
export function supportsNullOption(columnId: string): boolean {
  return COLUMNS_WITH_NULL_OPTION.has(columnId);
}

/**
 * Fields that get their enum options merged from AOP's conversationFieldDefinitions.
 * These fields have values that vary per AOP and need to be merged when multiple AOPs are selected.
 */
export const AOP_MERGEABLE_FIELDS = ['status', 'priority'] as const;
export type AopMergeableField = (typeof AOP_MERGEABLE_FIELDS)[number];

/**
 * Check if a field gets its options merged from AOPs.
 */
export function isAopMergeableField(fieldId: string): fieldId is AopMergeableField {
  return AOP_MERGEABLE_FIELDS.includes(fieldId as AopMergeableField);
}

/**
 * Default enum options for crmSynced column.
 * Single source of truth - used by both field-enums and conversation-columns.
 */
export const DEFAULT_CRM_SYNCED_OPTIONS: CrmFieldEnumOption[] = [
  { value: 'synced', label: 'Synced', enumOrder: 0, color: 'green' },
  { value: 'not_synced', label: 'Not Synced', enumOrder: 1, color: 'gray' },
];

/**
 * Default enum options for history/timeline column.
 * Single source of truth - used by both field-enums and conversation-columns.
 */
export const DEFAULT_HISTORY_OPTIONS: CrmFieldEnumOption[] = [
  { value: 'meeting', label: 'Meeting', enumOrder: 0, color: 'purple' },
  { value: 'email_outbound', label: 'Outbound Email', enumOrder: 1, color: 'pink' },
  { value: 'email_inbound', label: 'Inbound Email', enumOrder: 2, color: 'green' },
];

/**
 * Gets the default options array for a given field key
 */
function getDefaultOptions(fieldKey=[redacted] CrmFieldEnumOption[] {
  switch (fieldKey) {
    case 'status':
      return DEFAULT_STATUS_OPTIONS;
    case 'priority':
      return DEFAULT_PRIORITY_OPTIONS;
    case 'type':
      return []; // Type field doesn't have default options
    case 'crmSynced':
      return DEFAULT_CRM_SYNCED_OPTIONS;
    case 'history':
      return DEFAULT_HISTORY_OPTIONS;
    default:
      return [];
  }
}

/**
 * Core function that finds an enum option by value.
 * Checks enumOptions first (from AOP definitions), then falls back to default options.
 *
 * @param value - The enum value to find
 * @param fieldKey - The field key ('status', 'priority', 'type')
 * @param enumOptions - Optional array of enum options from AOP definitions
 * @returns The found option object or null
 */
export function getEnumOption(
  value: string | null | undefined,
  fieldKey=[redacted],
  enumOptions?: CrmFieldEnumOption[],
): CrmFieldEnumOption | null {
  if (!value) return null;

  const normalizedValue = value?.toLowerCase();

  // First, check enumOptions (from AOP definitions)
  if (enumOptions && enumOptions.length > 0) {
    const option = enumOptions.find((opt) => opt.value?.toLowerCase() === normalizedValue);
    if (option) return option;
  }

  // Fallback to default options
  const defaultOptions = getDefaultOptions(fieldKey);
  const defaultOption = defaultOptions.find((opt) => opt.value?.toLowerCase() === normalizedValue);
  return defaultOption || null;
}

/**
 * Gets the Tailwind color class for an enum value.
 * Handles special case for 'type' field which uses getTypeColor.
 *
 * @param value - The enum value
 * @param fieldKey - The field key ('status', 'priority', 'type')
 * @param enumOptions - Optional array of enum options from AOP definitions
 * @returns Tailwind color class string
 */
export function getEnumColor(
  value: string | null | undefined,
  fieldKey=[redacted],
  enumOptions?: CrmFieldEnumOption[],
): string {
  // Handle null values explicitly - always gray
  if (value === null) {
    return 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400';
  }

  // Special handling for 'type' field
  if (fieldKey === 'type') {
    return getTypeColor(value);
  }

  // Special handling for 'crmSynced' field
  if (fieldKey === 'crmSynced') {
    if (value === 'synced') {
      return 'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-300';
    }
    return 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400';
  }

  // Special handling for 'history' field (event types)
  if (fieldKey === 'history') {
    if (value === 'meeting') {
      return 'bg-purple-100 text-purple-800 dark:bg-purple-900/50 dark:text-purple-300';
    }
    if (value === 'email_outbound') {
      return 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300';
    }
    if (value === 'email_inbound') {
      return 'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-300';
    }
  }

  const option = getEnumOption(value, fieldKey, enumOptions);
  if (option) {
    return getColorClass(option.color);
  }

  // Fallback to gray if no option found
  return getColorClass('gray');
}

/**
 * Sorts an array of enum options by their enumOrder property.
 *
 * @param options - Array of enum options to sort
 * @returns Sorted array of options
 */
export function sortEnumOptions(options: CrmFieldEnumOption[]): CrmFieldEnumOption[] {
  return [...options].sort((a, b) => a.enumOrder - b.enumOrder);
}

export function getEnumTextColor(
  value: string | null | undefined,
  fieldKey=[redacted],
  enumOptions?: CrmFieldEnumOption[],
): string {
  if (value === null || value === undefined) return 'text-gray-600 dark:text-gray-400';
  const option = getEnumOption(value, fieldKey, enumOptions);
  if (option) return getTextColorClass(option.color);
  return 'text-gray-600 dark:text-gray-400';
}