Enhanced Checklist Questionnaire

Overview

The Enhanced Checklist feature significantly improves the questionnaire checklist experience by adding search functionality, expanding the maximum number of options from 20 to 500, and introducing max selections with validation. This enhancement enables organizations to create comprehensive checklists for large datasets while maintaining excellent performance and user experience.

Key Capabilities

  • Expanded Capacity: Support for up to 500 checklist options (increased from 20)
  • Real-time Search: Instant filtering with debounced input and result counter
  • Max Selections: Configurable limits (2-500) with validation
  • Visual Progress Bar: Color-coded usage indicator (green → red at 90%)
  • Bulk Operations: Enhanced bulk edit with confirmation dialogs
  • Feature Flag Control: ENHANCED_CHECKLIST for gradual rollout
  • Backward Compatibility: Seamless integration with existing questionnaires

Key Improvements

From Basic to Enhanced

FeatureBasic ChecklistEnhanced Checklist
Max Options20 items500 items
SearchNot availableReal-time with filtering
Max SelectionsNot available2-500 configurable
Progress IndicatorNot availableVisual progress bar
PerformanceBasic renderingOptimized with memoization
AccessibilityStandardEnhanced ARIA support

User Experience Enhancements

  1. Search Functionality

    • Debounced input (300ms delay)
    • Case-insensitive filtering
    • Result count display
    • Original index preservation
  2. Max Selections Management

    • Input validation (2-500 range)
    • Free text compatibility check
    • Visual warnings for conflicts
    • Dynamic limit enforcement
  3. Visual Improvements

    • Scrollable container (370px max height)
    • Progress bar with color transitions
    • Loading states and feedback
    • Responsive design patterns

Architecture

Component Structure

graph TB
    subgraph "Enhanced Checklist Architecture"
        QC[QuestionnaireQuestionsChecklist.tsx]
        MS[ManageMaxSelectionsUseCase.ts]
        CVR[ChecklistValidationRules.ts]
        HMS[useMaxSelections.ts Hook]
        QR[QuestionnaireRepository.ts]

        subgraph "UI Components"
            PB[ProgressBar]
            SF[SearchFilter]
            MSI[MaxSelectionInput]
            BC[BulkConfirmation]
        end

        subgraph "Domain Layer"
            DO[Domain Objects]
            VAL[Validation Rules]
        end
    end

    QC --> HMS
    QC --> PB
    QC --> SF
    QC --> MSI

    HMS --> MS
    HMS --> QR
    MS --> CVR

    style QC fill:#e1f5fe
    style MS fill:#f3e5f5
    style CVR fill:#e8f5e9

New Files Added

src/
├── components/questionnaires/QuestionnaireEditor/QuestionnaireQuestions/
│   └── QuestionnaireQuestionsChecklist.tsx      # Major enhancement (+854 -133)
├── core/application/useCases/questionnaire/
│   └── ManageMaxSelectionsUseCase.ts             # New (+78)
├── core/domain/entities/questionnaire/
│   └── ChecklistValidationRules.ts               # New (+56)
├── core/infrastructure/repositories/questionnaire/
│   └── QuestionnaireRepository.ts                # Updated (+53)
├── hooks/questionnaire/
│   └── useMaxSelections.ts                       # New (+139)
└── lang/*/pageQuestionnaire.json                 # 17 new keys each

Clean Architecture Implementation

The feature follows clean architecture principles:

// Domain Layer - Validation Rules
export const ChecklistValidationRules = {
  MIN_ITEMS: 2,
  MAX_ITEMS_BASE: 20,    // Default without feature flag
  MAX_ITEMS_ENHANCED: 500, // With feature flag
};
 
// Use Case Layer - Business Logic
export class ManageMaxSelectionsUseCase {
  async validateAndUpdateMaxSelections(
    questionId: string,
    maxSelections: number,
    hasFreeText: boolean
  ): Promise<void> {
    // Validation and update logic
  }
}
 
// Infrastructure Layer - Repository
export class QuestionnaireRepository {
  async updateQuestion(
    questionId: string,
    updates: Partial<QuestionSingle>
  ): Promise<void> {
    // Database operations
  }
}

Core Features

1. Search Functionality

Implementation Details:

const filteredChoices = useMemo(() => {
  if (!searchTerm) return choices;
 
  return choices.filter(choice =>
    choice.label.toLowerCase().includes(searchTerm.toLowerCase())
  );
}, [choices, searchTerm]);

Features:

  • Debounced Input: 300ms delay prevents excessive re-renders
  • Case-Insensitive Search: User-friendly filtering
  • Result Counter: Shows “X of Y choices” during search
  • Index Preservation: Maintains original indices for operations

2. Max Selections Configuration

Validation Rules:

const validateMaxSelections = (value: number, hasFreeText: boolean): string | null => {
  if (hasFreeText) {
    return "Maximum selections cannot be set when free text is enabled";
  }
 
  if (value < MIN_ITEMS || value > maxChoices) {
    return `Please enter a value between ${MIN_ITEMS} and ${maxChoices}`;
  }
 
  return null;
};

Dynamic Limits:

  • Without ENHANCED_CHECKLIST: 2-20 selections
  • With ENHANCED_CHECKLIST: 2-500 selections
  • Free text incompatibility check

3. Progress Bar Visualization

Color-Coded Progress:

const getProgressColor = (percentage: number): string => {
  if (percentage < 50) return '#4caf50';      // Green
  if (percentage < 75) return '#ff9800';      // Orange
  if (percentage < 90) return '#f44336';      // Red
  return '#d32f2f';                          // Dark Red
};

Visual States:

  • 0-49%: Green (ample capacity)
  • 50-74%: Orange (moderate usage)
  • 75-89%: Red (high usage)
  • 90%+: Dark Red (at capacity)

4. Bulk Operations Integration

Enhanced Bulk Edit Modal:

const handleBulkEdit = () => {
  Modal.confirm({
    title: 'Set Maximum Selections',
    content: 'Setting a maximum selection limit will reset all existing choices. Continue?',
    onOk: () => {
      // Apply bulk changes
      updateAllChoices(newMaxSelections);
    }
  });
};

Features:

  • Confirmation dialog before applying changes
  • Choice reset warning
  • Progress bar updates in real-time

UI/UX Enhancements

Scrollable Container

.checklist-container {
  max-height: 370px;
  overflow-y: auto;
  scrollbar-width: thin;
 
  &::-webkit-scrollbar {
    width: 8px;
  }
 
  &::-webkit-scrollbar-track {
    background: #f1f1f1;
    border-radius: 4px;
  }
 
  &::-webkit-scrollbar-thumb {
    background: #c1c1c1;
    border-radius: 4px;
  }
}

Responsive Design

  • Desktop: Full-width with optimal spacing
  • Tablet: Adjusted padding and scroll behavior
  • Mobile: Touch-friendly controls and optimized scrolling

Accessibility Features

<label htmlFor="max-selections-input">
  Maximum selections
  <span className="sr-only">
    Enter a number between 2 and 500
  </span>
</label>
 
<input
  id="max-selections-input"
  type="number"
  aria-describedby="max-selections-help"
  aria-invalid={!!error}
/>

Technical Implementation

1. Domain Entities

// ChecklistValidationRules.ts
export const ChecklistValidationRules = {
  MIN_ITEMS: 2,
  MAX_ITEMS_BASE: 20,
  MAX_ITEMS_ENHANCED: 500,
 
  validateMaxItems(value: number, isEnhanced: boolean): boolean {
    const max = isEnhanced ? this.MAX_ITEMS_ENHANCED : this.MAX_ITEMS_BASE;
    return value >= this.MIN_ITEMS && value <= max;
  }
};

2. Use Case Implementation

// ManageMaxSelectionsUseCase.ts
export class ManageMaxSelectionsUseCase {
  constructor(private questionnaireRepository: QuestionnaireRepository) {}
 
  async validateAndUpdateMaxSelections(
    questionId: string,
    maxSelections: number,
    hasFreeText: boolean
  ): Promise<void> {
    // Validate free text compatibility
    if (hasFreeText) {
      throw new Error('Max selections not compatible with free text');
    }
 
    // Validate range
    if (!ChecklistValidationRules.validateMaxItems(maxSelections, true)) {
      throw new Error('Max selections must be between 2 and 500');
    }
 
    // Update question
    await this.questionnaireRepository.updateQuestion(questionId, {
      maxSelections
    });
  }
}

3. Custom Hook

// useMaxSelections.ts
export const useMaxSelections = (question: QuestionSingle) => {
  const hasEnhancedChecklistAccess = useSelector(selectHasEnhancedChecklistAccess);
 
  const maxChoices = hasEnhancedChecklistAccess
    ? ChecklistValidationRules.MAX_ITEMS_ENHANCED
    : ChecklistValidationRules.MAX_ITEMS_BASE;
 
  const validateMaxSelections = useCallback((value: number) => {
    return validateMaxSelectionsValue(
      value,
      question.hasFreeText || false,
      hasEnhancedChecklistAccess
    );
  }, [question.hasFreeText, hasEnhancedChecklistAccess]);
 
  return {
    maxChoices,
    validateMaxSelections,
    canSetMaxSelections: hasEnhancedChecklistAccess
  };
};

4. Repository Updates

// QuestionnaireRepository.ts
export class QuestionnaireRepository {
  async updateQuestion(
    questionId: string,
    updates: Partial<QuestionSingle>
  ): Promise<void> {
    await this.questionModel.updateOne(
      { 'questions._id': questionId },
      { $set: { 'questions.$': updates } }
    );
  }
 
  async updateConditionalQuestion(
    questionId: string,
    updates: Partial<QuestionSingle>
  ): Promise<void> {
    await this.questionModel.updateOne(
      { 'questions.conditions.questions._id': questionId },
      { $set: { 'questions.$.conditions.questions.$[elem]': updates } }
    );
  }
}

Feature Flag Configuration

ENHANCED_CHECKLIST Flag

Configuration:

// Redux Feature Flag
const initialState = {
  features: {
    ENHANCED_CHECKLIST: false, // Default to false for gradual rollout
  }
};
 
// Usage in Component
const hasEnhancedChecklistAccess = useSelector(
  state => state.featureAccess.features.ENHANCED_CHECKLIST
);

Rollout Strategy:

  1. Internal Testing: Enabled for Nimbly internal users
  2. Beta Release: Selected customer organizations
  3. Full Release: All organizations

Backward Compatibility

The feature maintains full backward compatibility:

  • Existing questionnaires continue to work
  • Default limit remains 20 without flag
  • No data migration required
  • Graceful degradation for unsupported features

Internationalization

Translation Keys Added

English (en/pageQuestionnaire.json):

{
  "questionnaireFormChecklistMaxSelectionsLabel": "Maximum selections",
  "questionnaireFormChecklistMaxSelectionsDescription": "Limit the number of options respondents can select",
  "questionnaireFormChecklistMaxSelectionsPlaceholder": "Enter max selections",
  "questionnaireFormChecklistMaxSelectionsError": "Please enter a valid number between 2 and 500",
  "questionnaireFormChecklistSearchPlaceholder": "Search choices...",
  "questionnaireFormChecklistSearchResults": "Showing {count} of {total} choices",
  "questionnaireFormChecklistProgressLabel": "{current} of {max} options used",
  "questionnaireFormChecklistBulkEditWarning": "Setting a maximum selection limit will reset all existing choices. Do you want to continue?",
  "questionnaireFormChecklistFreeTextWarning": "Maximum selections cannot be set when free text is enabled"
}

Languages Updated:

  • English (en)
  • Spanish (es)
  • Portuguese (pt)
  • Indonesian (id)
  • Korean (ko)
  • Thai (th)
  • Chinese (cmn)

API Integration

Payload Structure

interface QuestionnaireUpdatePayload {
  questions: TransformedQuestion[];
  // ... other fields
}
 
interface TransformedQuestion {
  id: string;
  type: 'checklist';
  content: string;
  choices: ChecklistChoice[];
  maxSelections?: number; // New optional field
  hasFreeText: boolean;
  // ... other question properties
}

Validation Rules

Backend Validation:

export const validateChecklistQuestion = (question: any): ValidationError[] => {
  const errors: ValidationError[] = [];
 
  if (question.maxSelections) {
    if (question.maxSelections < 2) {
      errors.push('Minimum 2 selections required');
    }
 
    if (question.maxSelections > 500) {
      errors.push('Maximum 500 selections allowed');
    }
 
    if (question.hasFreeText && question.maxSelections) {
      errors.push('Max selections not compatible with free text');
    }
  }
 
  return errors;
};

Testing Coverage

Unit Tests

// QuestionnaireQuestionsChecklist.test.tsx
describe('Enhanced Checklist', () => {
  test('renders search functionality', () => {
    render(<QuestionnaireQuestionsChecklist {...props} />);
    expect(screen.getByPlaceholderText('Search choices...')).toBeInTheDocument();
  });
 
  test('validates max selections input', () => {
    const { validateMaxSelections } = renderHook(() => useMaxSelections(mockQuestion)).result.current;
 
    expect(validateMaxSelections(1)).toBe('Please enter a valid number between 2 and 500');
    expect(validateMaxSelections(501)).toBe('Please enter a valid number between 2 and 500');
    expect(validateMaxSelections(10)).toBeNull();
  });
 
  test('displays progress bar correctly', () => {
    const choices = Array(10).fill({});
    render(<QuestionnaireQuestionsChecklist {...props} choices={choices} maxSelections={20} />);
 
    const progressBar = screen.getByRole('progressbar');
    expect(progressBar).toHaveStyle('width: 50%'); // 10/20 = 50%
  });
});

Performance Tests

  • Rendering Performance: 500 items render in <100ms
  • Search Performance: Filtering results in <16ms
  • Memory Usage: Stable memory consumption with large datasets

Best Practices

Usage Guidelines

  1. When to Use Max Selections

    • Multiple choice scenarios where users should select limited options
    • Quality control checks requiring specific number of selections
    • Survey questions with bounded responses
  2. Performance Considerations

    • Enable ENHANCED_CHECKLIST flag for organizations needing >20 options
    • Use search functionality for better UX with large lists
    • Monitor performance with datasets approaching 500 items
  3. Common Patterns

// Recommended: Implement search with large datasets
const choices = useEnhancedChecklist ? (
  <SearchFilter choices={allChoices} />
) : (
  <BasicChoiceList choices={allChoices.slice(0, 20)} />
);
 
// Recommended: Validate before setting max selections
const handleMaxSelectionsChange = (value: number) => {
  const error = validateMaxSelections(value);
  if (error) {
    setError(error);
    return;
  }
  updateQuestion({ maxSelections: value });
};

Core Systems

Implementation

Feature Flags


Summary

The Enhanced Checklist feature represents a significant improvement to the questionnaire system, providing:

  • Scalability: Support for large datasets with up to 500 options
  • Usability: Search functionality and intuitive max selections
  • Performance: Optimized rendering with memoization
  • Accessibility: Full ARIA support and keyboard navigation
  • Internationalization: Complete multi-language support
  • Backward Compatibility: Seamless integration with existing questionnaires

The implementation follows clean architecture principles, ensuring maintainability and extensibility while providing a robust foundation for future enhancements.