Overview

The Questionnaire Module is a comprehensive system for creating, editing, and managing audit questionnaires within the Nimbly audit administration platform. This module supports complex questionnaire structures with multiple question types, conditional logic, department assignments, automated assignment workflows, and multimedia attachments.

Key Features

  • 15+ Question Types: Binary flags, multiple choice, scoring, range flags, checklists, attachments, etc.
  • Conditional Questions: Parent-child question relationships with branching logic
  • Department Management: Multi-department assignment with granular permissions
  • Auto Assignment: Three-tier assignment system (questionnaire, category, question level)
  • Rich Attachments: Photos, videos, documents with AI object detection
  • Advanced Validation: Real-time validation with detailed error handling
  • Drag & Drop Interface: Intuitive question and category reordering
  • Scoring System: Weighted scoring with pass/fail criteria
  • Clean Architecture: Domain-driven design with clear separation of concerns

Architecture

The questionnaire module follows a clean architecture pattern with clear separation between presentation, application, domain, and infrastructure layers.

Directory Structure

src/
├── components/questionnaires/
│   ├── QuestionnaireEditor/
│   │   ├── QuestionnaireEditor.tsx                 # Main editor component
│   │   ├── QuestionnaireEditorContainer.tsx        # Container wrapper
│   │   ├── QuestionnaireEditorForm.tsx             # Main form component
│   │   ├── QuestionnaireEditorFormCategory.tsx     # Category management
│   │   ├── QuestionnaireEditorFormQuestions.tsx    # Question form handler
│   │   ├── QuestionnaireEditorDepartmentList.tsx   # Department assignment
│   │   ├── QuestionnaireAutoAssignmentModal.tsx    # Auto assignment modal
│   │   ├── CategoryAttributesModal.tsx             # Category attributes
│   │   ├── QuestionnaireQuestions/                 # Question type components
│   │   │   ├── QuestionnaireQuestionsLabelFlag.tsx
│   │   │   ├── QuestionnaireQuestionsMultipleChoice.tsx
│   │   │   ├── QuestionnaireQuestionsScore.tsx
│   │   │   ├── QuestionnaireQuestionsAttachment.tsx
│   │   │   └── ... (15+ question type components)
│   │   ├── QuestionnaireConditional/               # Conditional questions
│   │   │   ├── QuestionnaireConditionalQuestions.tsx
│   │   │   ├── QuestionnaireConditionalQuestionsHead.tsx
│   │   │   └── QuestionnaireConditionalQuestionsBody.tsx
│   │   └── utils/                                  # Utility functions
│   ├── QuestionnaireList.js                        # Main list component
│   ├── QuestionnaireManager.js                     # Manager component
│   └── ... (other list/management components)
├── core/
│   ├── application/services/questionnaire/         # Application services
│   ├── domain/entities/questionnaire/              # Domain entities
│   ├── domain/models/questionnaire/                # Domain models
│   ├── infrastructure/repositories/questionnaire/  # Data repositories
│   └── presentation/controllers/questionnaire/     # Presentation controllers
├── reducers/questionnaire/                         # State management
├── sagas/questionnaire/                            # Side effects
├── services/questionnaire.service.ts               # API service layer
└── pages/
    ├── questionnaires.js                           # Main listing page
    └── questionnaires-edit.js                      # Editor page

Component Hierarchy

QuestionnaireEditPage
└── QuestionnaireEditor (main container)
    └── QuestionnaireEditorContainer (wrapper)
        └── QuestionnaireEditorAdminPage (admin layout)
            ├── QuestionnaireEditorHeader (header controls)
            ├── QuestionnaireEditorForm (main form)
            │   ├── QuestionnaireEditorDepartmentList
            │   ├── QuestionnaireEditorFormCategory (per category)
            │   │   └── QuestionnaireEditorFormQuestions (per question)
            │   │       ├── Question Type Components
            │   │       └── QuestionnaireConditionalQuestions
            │   └── QuestionnaireEditorPassCriteria
            └── QuestionnaireEditorSummary (sidebar summary)

Routes and Navigation

Admin Routes

RouteComponentPurposeAccess Control
/admin/questionnairesQuestionnairesPageMain questionnaires listingRoleResources.ADMIN_QUESTIONNAIRE_ALL
/admin/questionnaires/editQuestionnaireEditPageQuestionnaire editorRoleResources.ADMIN_QUESTIONNAIRE_ALL
/admin/questionnaires/edit?questionnaire=newQuestionnaireEditPageCreate new questionnaireRoleResources.ADMIN_QUESTIONNAIRE_ALL
/admin/questionnaires/edit?questionnaire={id}QuestionnaireEditPageEdit existing questionnaireRoleResources.ADMIN_QUESTIONNAIRE_ALL
/admin/formula-builderFormulaListPageFormula managementRoleResources.ADMIN_QUESTIONNAIRE_ALL
/admin/formula-builder/{formulaId}FormulaBuilderPageFormula builderRoleResources.ADMIN_QUESTIONNAIRE_ALL

Customer Feedback Routes

RouteComponentPurposeAccess Control
/admin/customerFeedbackCustomerFeedbackCampaignPageCampaign managementFeatures.CUSTOMER_FEEDBACK
/admin/customerFeedback/qr-editorCustomerFeedbackQrEditorPageQR code editorRoleResources.ADMIN_CUSTOMERFEEDBACK_QR
/admin/customerFeedback/{questionnaireId}CustomerFeedbackCampaignDetailPageCampaign detailsFeatures.CUSTOMER_FEEDBACK

Route Protection

  • Feature Flags: Features.CUSTOMIZABLE_QUESTIONNAIRE
  • Role-Based Access: Admin and Superadmin roles
  • Supervisor Override: allowSupervisorEditQuestionnaire setting

Core Components

QuestionnaireEditor.tsx

Purpose: Main container component managing questionnaire state and business logic.

Key Responsibilities:

  • State management via Redux integration
  • Questionnaire data loading and persistence
  • Validation orchestration
  • Save/publish workflow management
  • Tutorial system integration

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireEditor.tsx

Key Methods:

  • setQuestionnaire(): Loads questionnaire data for editing
  • handleSave(): Orchestrates questionnaire persistence
  • validateQuestion(): Comprehensive question validation
  • renderQuestionnaireValidation(): Form-wide validation

QuestionnaireEditorForm.tsx

Purpose: Main form component handling questionnaire structure and interactions.

Key Responsibilities:

  • Category and question management
  • Drag & drop functionality
  • Department assignment integration
  • Auto assignment modal triggers

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireEditorForm.tsx

Key Methods:

  • handleAddCategory(): Creates new question categories
  • handleAddQuestion(): Adds questions to categories
  • handleDragEnd(): Manages drag & drop reordering

QuestionnaireEditorFormCategory.tsx

Purpose: Individual category management component.

Key Responsibilities:

  • Category title and selection management
  • Category weight configuration
  • Category-level auto assignment
  • Category deletion with validation

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireEditorFormCategory.tsx

Key Features:

  • Creatable category selection with dynamic ID generation
  • Weight-based scoring calculation
  • Category attributes management
  • Auto assignment integration

Question Types

The questionnaire system supports 15+ question types, each with specialized functionality and validation rules.

Binary/Flag Questions

Binary with Red Flag (binary-with-red-flag)

Purpose: Yes/No questions with flag color indicators and issue creation capability.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireQuestions/QuestionnaireQuestionsLabelFlag.tsx

Features:

  • Customizable flag labels (Green/Yellow/Red)
  • Fail toggle for audit failure conditions
  • Label propagation across questionnaire/category
  • Conditional question support
  • Potential remedies management

Configuration Options:

  • Green flag label (default: “Pass”)
  • Yellow flag label (default: “Minor Issue”)
  • Red flag label (default: “Major Issue”)
  • Answer required toggle
  • Fail condition toggle

Binary (binary)

Purpose: Simple Yes/No questions without flag indicators.

Features:

  • Basic yes/no response
  • Answer required validation
  • Conditional question support

Multiple Choice Questions

Multiple Choice (multiple-choice)

Purpose: Single-select questions with 2-20 predefined options.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireQuestions/QuestionnaireQuestionsMultipleChoice.tsx

Features:

  • Dynamic choice management (2-20 choices)
  • Duplicate validation
  • Firebase character validation
  • Fail toggles per choice
  • Conditional question spawning per choice

Validation Rules:

  • Minimum 2 choices required
  • No duplicate choices allowed
  • No empty choices allowed
  • Firebase invalid characters blocked (". $ # [ ] / ' " ,)

Multiple Choice with Scoring (multiple-choice-score)

Purpose: Multiple choice questions with weighted scoring per option.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireQuestions/QuestionnaireQuestionsWeightScore.tsx

Features:

  • Flag color assignment per choice (Green/Yellow/Red)
  • Numeric scoring per option
  • Negative score handling
  • Unique label validation
  • Score weight calculation

Scoring Questions

Score (score)

Purpose: Numeric scoring questions with configurable score ranges and weights.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireQuestions/QuestionnaireQuestionsScore.tsx

Features:

  • Score thresholds (1-10)
  • Score weights for questionnaire totals
  • Negative scoring capability
  • Fail conditions based on score
  • Automatic weight calculation

Configuration:

  • Score range: 1-10
  • Score weight: Custom numeric value
  • Fail threshold: Configurable score limit

Scale (scale)

Purpose: Rating scale questions with predefined scales.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireQuestions/QuestionnaireQuestionsScale.tsx

Features:

  • 1-5 or 1-10 scale options
  • Custom labels per scale point
  • Issue triggering thresholds
  • Answer requirement validation

Text and Input Questions

Open Ended (open)

Purpose: Free-text response questions with flag triggers.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireQuestions/QuestionnaireQuestionOpenEnded.tsx

Features:

  • Free text input
  • Red/Yellow flag triggers for issue creation
  • Character limit validation
  • Answer requirement options

Number (number)

Purpose: Numeric input questions with unit of measure support.

Features:

  • Numeric validation
  • UOM (Unit of Measure) selection
  • SKU integration support
  • Min/max value validation

Selection Questions

Checklist (checklist)

Purpose: Multiple-select questions with independent item validation.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireQuestions/QuestionnaireQuestionsChecklist.tsx

Features:

  • Multiple item selection (2-20 items)
  • Red flag toggles per item
  • Fail conditions per item
  • Dynamic item management

Range Questions

Range Flag (range-flag)

Purpose: Numeric range questions with color-coded flag ranges.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireQuestions/QuestionnaireQuestionsRangeFlag.tsx

Features:

  • Complex range configuration with min/max bounds
  • Multi-flag support (Green/Yellow/Red) within ranges
  • Range inversion capability
  • Interactive slider interface
  • Overlap and out-of-range validation

Range Configuration:

  • Global min/max range definition
  • Multiple flag ranges within global range
  • Flag color assignment per range
  • Inversion logic (high/low value interpretation)

Legacy Questions

Inventory (inventory) - Deprecated

Purpose: Legacy inventory tracking questions.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireQuestions/QuestionnaireQuestionsInventoryOld.tsx

Status: Deprecated - use Inventory V2 Features: Basic SKU field input

Metadata Components

Reference (reference)

Purpose: Additional context and reference information for questions.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireQuestions/QuestionnaireQuestionsReference.tsx

Features:

  • Reference text field for additional question context
  • Rich text formatting support
  • External link integration

Tags (tag)

Purpose: Question categorization and metadata management.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireQuestions/QuestionnaireQuestionsTag.tsx

Features:

  • Multi-tag selection
  • Organization-specific tag pools
  • Tag creation and management

Recommendations (recommendation)

Purpose: Potential remedy suggestions for binary questions.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireQuestions/QuestionnaireQuestionsRecommendation.tsx

Features:

  • Multiple remedy suggestions
  • Dynamic recommendation list
  • Integration with binary question types

Department Assignment & Categories

Department Management System

The questionnaire module includes sophisticated department assignment capabilities that control questionnaire visibility and access across organizational departments.

QuestionnaireEditorDepartmentList.tsx

Purpose: Manages department assignment for questionnaires.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireEditorDepartmentList.tsx

Key Features:

  • Multi-department selection via dropdown
  • Tag-based department display
  • Auto assignment integration
  • Department removal with cascade cleanup

Department Assignment Workflow:

  1. Selection: Dropdown populated from organization departments
  2. Addition: Selected departments added to questionnaire scope
  3. Display: Departments shown as removable tags
  4. Auto Assignment: Triggers auto assignment recalculation
  5. Removal: Department removal with auto assignment cleanup

Integration Points:

  • Auto assignment modal dependencies
  • Tutorial system guidance
  • Permission validation

Category Management System

Categories organize questions into logical groups within questionnaires, each with configurable attributes and scoring weights.

QuestionnaireEditorFormCategory.tsx

Purpose: Individual category management with advanced configuration options.

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireEditorFormCategory.tsx

Category Features:

  • Dynamic Category Selection: Creatable dropdown with new category generation
  • Weight-Based Scoring: Category-level weight configuration affecting all questions
  • Category Attributes: Custom key-value metadata per category
  • Auto Assignment Integration: Category-level assignment rules
  • Score Calculation: Real-time total score weight calculation

Category Configuration:

interface Category {
  id: string;
  title: string;
  selectValue: {
    label: string;
    value: string;
    _id?: string;
    __isNew__?: boolean;
  };
  questionIds: string[];
  categoryWeights?: number;
}

CategoryAttributesModal.tsx

Purpose: Advanced category metadata management.

File Location: /src/components/questionnaires/QuestionnaireEditor/CategoryAttributesModal.tsx

Attributes System:

  • Key-Value Pairs: Flexible metadata structure
  • Validation: Required key and value validation
  • CRUD Operations: Add, edit, delete attribute pairs
  • Persistence: Attributes saved with questionnaire data

Category Attributes Structure:

interface CategoryAttributes {
  categoryID: string;
  categoryName: string;
  attributes: Array<{
    key: string;
    value: string;
  }>;
}

Category Business Logic

Score Calculation

Categories automatically calculate total score weights based on constituent questions:

const renderCategoryScoreWeight = (categoryId: string): number => {
  let categoryWeight = 0;
  
  // Parent question scores
  category.questionIds.forEach((id) => {
    if (question.type === 'score') {
      categoryWeight += question.scoreWeight || question.score;
    }
    
    // Child question scores (conditional questions)
    const conditionalQuestions = question.conditionalQuestion;
    if (conditionalQuestions) {
      Object.keys(conditionalQuestions.conditions).forEach((condition) => {
        conditionalQuestions.conditions[condition].forEach((childQuestion) => {
          if (childQuestion.type === 'score') {
            categoryWeight += childQuestion.scoreWeight || childQuestion.score;
          }
        });
      });
    }
  });
  
  return categoryWeight;
};

Category Deletion Logic

Category deletion includes comprehensive cleanup:

  1. Score Recalculation: Updates total questionnaire score weights
  2. Video Quota: Adjusts video usage quotas
  3. Auto Assignment: Removes category-level assignments
  4. Question Cleanup: Removes all associated questions

Auto Assignment System

The Auto Assignment System provides sophisticated automation for issue assignment based on questionnaire responses. The system operates at three hierarchical levels with multiple assignment strategies.

Architecture Overview

File Location: /src/components/questionnaires/QuestionnaireEditor/QuestionnaireAutoAssignmentModal.tsx

Assignment Hierarchy

1. Questionnaire Level Assignment

Purpose: Default assignment rules applied to all issues from the questionnaire. Priority: Lowest (overridden by category and question level assignments)

2. Category Level Assignment

Purpose: Assignment rules for specific question categories. Priority: Medium (overrides questionnaire level, overridden by question level)

3. Question Level Assignment

Purpose: Assignment rules for individual questions. Priority: Highest (overrides all other levels)

Assignment Types

Specific User Assignment

Type: SPECIFIC_USER Purpose: Assign to specific users across departments.

Features:

  • Cross-department user selection
  • Single user assignment per questionnaire/category/question
  • Department filtering based on questionnaire scope

Use Cases:

  • Specialized questions requiring specific expertise
  • Escalation to subject matter experts
  • Cross-departmental coordination

Schedule-Based Department Assignment

Type: SCHEDULE_DEPT Purpose: Assign based on auditor schedules within selected departments.

Features:

  • Multi-department assignment
  • Level-based filtering (auditor hierarchy levels)
  • Schedule-aware assignment logic
  • Multiple assignees per rule

Configuration:

  • Department selection from questionnaire scope
  • Auditor level filtering (Level 1, Level 2, etc.)
  • Multiple user assignment capability

Specific Department, User and Site Assignment

Type: SPECIFIC_DEPT_USER_SITE Purpose: Complex assignment rules based on department, user type, and site mapping. Availability: Category level only

Assignment Detail Structure:

interface AssignmentDetail {
  userType: 'issue_owner' | 'escalated_user';
  issueOwner?: string;          // User ID for issue owner
  escalated_users?: string[];   // User IDs for escalated users
  siteIDs: string[];           // Site IDs or ['_all_'] for all sites
  level: number;               // Auditor level (1-99)
  escalatedAfter: number;      // Hours before escalation (-1 for no escalation)
}

Member Structure:

interface MemberDetail {
  siteIDs: string[];          // Site coverage
  userIDs: string[];          // Assigned user IDs
}

Assignment Configuration UI

Department Selection

  • Dropdown populated from questionnaire-assigned departments
  • Level filtering based on department user hierarchy
  • User selection filtered by department and level

Site Selection

  • Multi-select with site conflict prevention
  • “All Sites” option for comprehensive coverage
  • Site exclusion logic for overlapping assignments

Escalation Configuration

  • Time-based escalation (1 hour to 4 weeks)
  • “No Escalation” option available
  • Level-based escalation chains

Member Management

  • Separate member assignment for notifications
  • Site-based member filtering
  • User selection with department constraints

Auto Assignment Data Structure

interface AutoAssignmentTypeV2 {
  questionnaireLevel: {
    type: AutoAssignmentDetailTypeEnum;
    assignedTo: AutoAssignmentDetailAssignee[];
    members: string[];
  } | null;
  
  categoryLevel: Array<{
    categoryID: string;
    type: AutoAssignmentDetailTypeEnum;
    assignedTo: AutoAssignmentDetailAssignee[];
    members: string[];
    deptUserSiteMapping?: {
      departmentID: string;
      assignmentDetails: AssignmentDetail[];
      members: MemberDetail[];
    };
  }>;
  
  questionLevel: Array<{
    questionIdx: number;
    childQuestionIdx?: number;
    type: AutoAssignmentDetailTypeEnum;
    assignedTo: AutoAssignmentDetailAssignee[];
    members: string[];
  }>;
}

Assignment Logic Flow

  1. Issue Creation: When questionnaire generates an issue
  2. Level Resolution: Check question → category → questionnaire level assignments
  3. Type Processing: Execute assignment logic based on type
  4. Site Matching: For site-specific assignments, match issue site to rules
  5. User Assignment: Assign to specified users or schedule-based logic
  6. Member Notification: Notify assigned members
  7. Escalation Setup: Configure escalation timers if specified

Validation Rules

Assignment Validation

  • At least one assignment rule required per level
  • No overlapping site assignments for same user type
  • Valid user and department selection required
  • Escalation time limits enforced

Site Conflict Prevention

  • Site exclusion logic prevents double assignment
  • Visual indicators for disabled options
  • Real-time validation during configuration

Data Integrity

  • Department existence validation
  • User permission verification
  • Site access confirmation

Conditional Questions

Conditional questions provide dynamic questionnaire branching based on parent question responses, enabling sophisticated logic flows and targeted data collection.

Architecture Overview

Core Files:

Conditional Question Structure

interface ConditionalQuestion {
  conditions: {
    [conditionKey: string]: Question[];  // Array of child questions per condition
  };
}
 
interface Question {
  // ... other question properties
  isConditional: boolean;
  conditionalQuestion?: ConditionalQuestion;
}

Parent-Child Relationships

Condition Mapping

Conditional questions are organized by parent question response values:

// Example: Binary question with conditional children
{
  "yes": [
    { type: "score", content: "Rate the implementation quality", ... },
    { type: "open", content: "Describe specific improvements", ... }
  ],
  "no": [
    { type: "binary-with-red-flag", content: "Is this a critical issue?", ... }
  ]
}

Multiple Choice Conditions

For multiple choice questions, each option can trigger different conditional questions:

{
  "Option 1": [ /* specific questions for option 1 */ ],
  "Option 2": [ /* specific questions for option 2 */ ],
  "Option 3": [ /* specific questions for option 3 */ ]
}

Visibility and Validation Logic

Visibility Control

File Location: /src/components/questionnaires/QuestionnaireEditor/utils/allowViewQuestion.ts:18-42

const allowViewQuestion = (
  question: Question,
  condition: string,
  childIndex: number
): boolean => {
  // Validate condition exists
  if (!question.conditionalQuestion?.conditions[condition]) {
    return false;
  }
  
  // Validate child index exists
  if (!question.conditionalQuestion.conditions[condition][childIndex]) {
    return false;
  }
  
  // Validate question type is allowed
  const childQuestion = question.conditionalQuestion.conditions[condition][childIndex];
  return isValidQuestionType(childQuestion.type);
};

State Management

Conditional questions use separate state tracking:

interface EditingConditionalQuestionPool {
  [parentQuestionId: string]: {
    [condition: string]: boolean[];  // Edit state per child question
  };
}

Business Rules:

  • Only one conditional question can be edited at a time across all parents
  • Edit state reset when switching between conditional questions
  • Parent question edit state independent of child question states

Conditional Question Features

Full Question Type Support

All standard question types are supported within conditional branches:

  • Binary flags with custom labels
  • Multiple choice with sub-conditions
  • Scoring questions with weights
  • Attachments with full configuration
  • Range flags with complex validation

Inheritance Patterns

Child questions inherit certain properties from parents:

  • Category assignment from parent question
  • Flag label defaults (overridable)
  • Department scope and auto assignment eligibility

Drag and Drop Support

Conditional questions support full drag-and-drop reordering:

  • Reorder within condition groups
  • Visual indicators during drag operations
  • State preservation during reordering

Validation Rules

Structural Validation

  • At least one condition must have at least one question
  • Condition keys cannot contain Firebase invalid characters
  • Child questions must have valid content and configuration

Content Validation

  • All child questions subject to standard question validation
  • Conditional-specific validation for answer choice dependencies
  • Recursive validation for nested conditional structures

Character Restrictions

Firebase limitations impose character restrictions on condition keys:

  • Prohibited: . $ # [ ] / ' " ,
  • Enforced during parent question choice creation
  • Validation error display for invalid characters

Conditional Question Workflow

Creation Process

  1. Parent Question Setup: Configure parent question with responses
  2. Conditional Toggle: Enable conditional questions on parent
  3. Condition Generation: System creates condition slots for each parent response
  4. Child Question Addition: Add questions to specific conditions
  5. Configuration: Configure child questions independently

Editing Workflow

  1. Selection: Click “Show More” to expand conditional questions
  2. Edit Mode: Enter edit mode for specific child question
  3. Configuration: Full question configuration interface
  4. Validation: Real-time validation during editing
  5. Save: Automatic save on edit mode exit

Rendering Logic

Conditional questions use collapsible interface:

  • Collapsed State: “Show More” button with question count
  • Expanded State: Full question list with edit capabilities
  • Edit State: Single question editing with full interface
  • Responsive Design: Mobile-optimized conditional interfaces

Attachments System

The attachments system provides comprehensive multimedia support for questionnaires, including photos, videos, documents, and AI-powered object detection capabilities.

Architecture Overview

Primary Component: QuestionnaireQuestionsAttachment.tsx

Attachment Types and Limits

Photo Attachments

Supported Formats: .jpg, .jpeg, .png, .gif Configuration Options:

  • Maximum: 1-6 photos per question (configurable dropdown)
  • Minimum: 0-6 photos (cannot exceed maximum)
  • Special Case: Inventory V2 questions limited to 1 photo maximum
  • File Size Limit: 7MB per file (default)

AI Integration:

  • Object detection via OBJECT_DETECTION feature flag
  • Maximum 3 keywords (comma-separated)
  • AI keyword recommendations via /vertex-ai/imagevalidation/recommend-keywords API
  • Automatic validation when photo attachment enabled

Video Attachments

Supported Formats: .mp4, .mov, .avi Quota Management:

  • Per-question limit: 1-3 videos (dynamic based on remaining quota)
  • Organization-level quota: 2 videos total
  • Enhanced quota: 3 videos for bnnrestaurant and sustainnovation organizations
  • Global limit tracked across all questions in questionnaire

Validation Rules:

  • Real-time quota checking during configuration
  • Dynamic limit adjustment based on remaining quota
  • Video count tracking in questionnaire state

Document Attachments

Supported Formats: .pdf, .doc, .docx, .xls, .xlsx, .csv Configuration Options:

  • Maximum: 1-6 documents per question
  • Minimum: 0-6 documents (cannot exceed maximum)
  • File Size Limit: 7MB per file (default)

AI Object Detection Integration

Feature Access Control

Requirements:

  • OBJECT_DETECTION feature flag enabled
  • Photo attachments enabled for question
  • Organization-level permissions verified

Keyword Management

Configuration Interface:

  • Textarea input for manual keyword entry
  • Maximum 3 keywords with comma separation
  • Real-time validation and count display
  • AI recommendation integration

AI Recommendation Workflow:

  1. Trigger: Click “Get AI Recommendations” button
  2. API Call: POST to /vertex-ai/imagevalidation/recommend-keywords
  3. Response Processing: Parse recommended keywords
  4. User Selection: Apply or cancel recommendation workflow
  5. Integration: Merge with existing keywords (max 3 total)

Object Detection Configuration

interface ImageDetectionConfig {
  useImageDetection: boolean;
  imageDetectionKeywords: string;
  imageDetectionScore?: number;
  imageDetectionResult?: DetectionResult;
}

Attachment Validation System

File Type Validation

Implementation: Server-side and client-side validation Rules:

  • Strict file extension checking
  • MIME type verification
  • Extensible validation via additionalValidTypes parameter

File Size Validation

Default Limit: 7MB (7,000,000 bytes) per file Implementation: useUploadFileAttachment hook Validation Points:

  • Client-side pre-upload validation
  • Server-side upload validation
  • Progress tracking during upload

Count Validation

Rules:

  • Default maximum: 10 attachments per upload batch
  • Configurable via maxFiles parameter
  • Per-question type limits enforced
  • Organization-specific quotas applied

Feature Access Control

Photo/Video Feature Gates

Primary Gate: PHOTO_ATTACHMENT_ANNOTATION feature flag Secondary Gates:

  • Organization-level video feature flag (features.video)
  • Video quota availability
  • Department permissions

Access Denied Handling:

  • Feature upgrade modal display
  • Graceful degradation to basic attachments
  • Permission-based UI hiding

Object Detection Access

Requirements:

  • OBJECT_DETECTION feature flag
  • Photo attachments enabled
  • Valid organization subscription

Fallback Behavior:

  • Hide AI recommendation interface
  • Maintain manual keyword entry
  • Preserve existing keyword data

Attachment Configuration Interface

Per-Question Configuration

Photo Settings:

  • Checkbox toggle for photo enablement
  • Min/Max dropdown selectors (0-6 range)
  • Object detection keyword textarea
  • AI recommendation button

Video Settings:

  • Checkbox toggle for video enablement
  • Dynamic limit selector based on quota
  • Quota display and warnings
  • Organization limit notifications

Document Settings:

  • Checkbox toggle for document enablement
  • Min/Max dropdown selectors (0-6 range)
  • File type restriction display

Conditional Question Support

Full Feature Parity:

  • All attachment types supported in conditional branches
  • Separate limits per conditional question
  • Independent AI object detection per branch
  • Quota tracking across parent and child questions

UI/UX Features

Responsive Design:

  • Mobile-optimized attachment interfaces
  • Touch-friendly controls for tablet use
  • Progressive disclosure of advanced features

Validation Feedback:

  • Real-time validation error display
  • Quota exceeded warnings
  • File type mismatch notifications
  • Size limit violation alerts

Attachment Data Structure

interface QuestionAttachments {
  // Photo configuration
  photoLimit: number;           // 0-6
  photoMinimum: number;         // 0-photoLimit
  
  // Video configuration  
  videoLimit: number;           // 0-3 (quota dependent)
  videoMinimum: number;         // 0-videoLimit
  
  // Document configuration
  documentLimit?: number;       // 0-6
  documentMinimum?: number;     // 0-documentLimit
  
  // AI object detection
  useImageDetection: boolean;
  imageDetectionKeywords: string;  // Comma-separated, max 3
  imageDetectionScore?: number;
  imageDetectionResult?: any;
}

API Integration

The questionnaire module integrates with a comprehensive API layer supporting CRUD operations, bulk processing, template management, and AI services.

Core API Endpoints

Questionnaire Management APIs

EndpointMethodPurposeFile Reference
/questionnairesGETFetch all questionnairesquestionnaire.service.ts
/questionnaires/POSTCreate/Update questionnairequestionnaire.service.ts
/questionnaires/{questionnaireID}DELETEDelete questionnairedeleteQuestionnaire.ts
/questionnaires/questionnaireIndexesGETGet all questionnaire indexesquestionnaire.service.ts
/questionnaires/questionnaireIndexes/{id}GETGet specific questionnaire by IDfetchQuestionnaireByID.ts
/questionnaires/questionnaireIndexes/{id}/latestGETGet latest questionnaire versionquestionnaire.service.ts
/questionnaires/questionnaireIndexes/minifiedGETGet minified questionnaire data with propsfetchQuestionnaireByProps.ts
/questionnaires/questionnaireIndexes/paginateGETGet paginated questionnairesquestionnaire.service.ts

Template Management APIs

EndpointMethodPurposeAuthentication
/questionnaires/questionnairetemplatesGETFetch questionnaire templatesFirebase Token
/questionnaires/questionnairetemplates/templateGETDownload questionnaire template by titleFirebase Token

Department Integration APIs

EndpointMethodPurposeAuthentication
/departments/questionnaire/{questionnaireKey}GETGet questionnaire departmentsFirebase Token
/departments/questionnaire-indexPOSTUpdate questionnaire departmentsFirebase Token

Bulk Operations APIs

EndpointMethodPurposeFile Reference
/questionnaires/downloadPOSTDownload multiple questionnairesdownloadQuestionnaires.ts
/questionnaires/bulk-uploadPOSTUpload bulk questionnairesuploadBulkQuestionnaire.ts
/bulk-edit/downloadPOSTBulk download questionnaires (revamp)bulkDownloadQuestionnaire.service.ts

Customer Feedback APIs

EndpointMethodPurposeAuthentication
/customer-feedback/campaignGETGet campaign listFirebase Token
/customer-feedback/campaign/{id}GETGet specific campaignFirebase Token
/customer-feedback/campaign/status/{questionnaireID}PUTUpdate campaign statusFirebase Token
/customer-feedback/trends/{questionnaireId}POSTGet campaign report dataFirebase Token

AI and Analytics APIs

EndpointMethodPurposeAuthentication
/vertex-ai/imagevalidation/recommend-keywordsPOSTGet AI-recommended keywords for object detectionFirebase Token
/statistics/issues/questionnairesGETGet questionnaire usage statisticsFirebase Token

Authentication Mechanisms

Firebase Token Authentication

Implementation: Most questionnaire APIs use Firebase token authentication Token Acquisition: getToken() function from Firebase Auth Header Format: Authorization: Bearer {firebaseToken} Token Scope: Organization-scoped with role-based permissions

Internal Token Authentication

Use Case: Bulk operations revamp APIs Token Acquisition: getInternalToken() function Purpose: Enhanced security for bulk edit operations Scope: Internal service-to-service communication

API Response Schemas

Standard Response Format

interface ApiResponse<T> {
  message: 'SUCCESS' | 'FAILED';
  data: T;
  error?: string;
  statusCode?: number;
}

Questionnaire Response Types

interface PopulatedQuestionnaireIndex {
  questionnaireIndexID: string;
  title: string;
  status: QuestionnaireStatus;
  dateCreated: string;
  dateUpdated: string;
  modifiedBy: string;
  departmentIDs: string[];
  questions: Question[];
  autoAssignmentV2?: AutoAssignmentTypeV2;
  categoryAttributes: CategoryAttributes[];
  passingCriteria?: string;
  passingValue?: number | object;
  passingType?: string;
}
 
interface UpsertQuestionnairePayload {
  title: string;
  questions: Question[];
  status: QuestionnaireStatus;
  dateCreated: string;
  dateUpdated: string;
  type: 'inventory' | 'default';
  questionnaireIndexID: string;
  modifiedBy: string;
  tags: { [key: string]: boolean };
  departmentIDs: string[];
}

Error Handling Patterns

Client-Side Error Handling

// Standard error handling pattern
try {
  const response = await questionnaireService.updateQuestionnaire(data);
  if (response.message === 'SUCCESS') {
    toast.success('Questionnaire saved successfully');
  } else {
    toast.error(response.error || 'Failed to save questionnaire');
  }
} catch (error) {
  toast.error('Network error occurred');
  console.error('API Error:', error);
}

Redux Saga Error Handling

// Saga error handling with retry logic
function* upsertQuestionnaireSaga(action) {
  try {
    const response = yield call(questionnaireAPI.upsert, action.payload);
    yield put(upsertQuestionnaireSuccess(response.data));
  } catch (error) {
    yield put(upsertQuestionnaireFailure(error.message));
    yield call(toast.error, 'Failed to save questionnaire');
  }
}

API Integration Services

Questionnaire Service Layer

File: /src/services/questionnaire.service.ts

Key Methods:

  • fetchQuestionnaireIndexes(): Paginated questionnaire retrieval
  • upsertQuestionnaire(): Create/update questionnaire
  • deleteQuestionnaire(): Questionnaire removal
  • fetchTemplates(): Template management

Domain Service Integration

Architecture: Clean architecture pattern with domain services Location: /src/core/application/services/questionnaire/

Service Layer Benefits:

  • Business logic separation
  • Testable service methods
  • Repository pattern abstraction
  • Cross-cutting concern handling

Repository Pattern

Implementation: Infrastructure layer repositories Location: /src/core/infrastructure/repositories/questionnaire/

Repository Benefits:

  • Data access abstraction
  • Caching layer integration
  • Multiple data source support
  • Mock implementation for testing

State Management

The questionnaire module employs a sophisticated state management architecture using Redux with Redux-Saga for side effects, following clean architecture principles with clear separation of concerns.

Redux Store Architecture

State Structure Overview

interface RootState {
  questionnaireDetail: QuestionnaireDetailState;
  questionnaire: QuestionnaireState;
  questionnaireIndex: QuestionnaireIndexState;
  admin: {
    manage: AdminManageState;
  };
  // ... other state slices
}

QuestionnaireDetail State Slice

Primary File: /src/reducers/questionnaireDetail/questionnaireDetail.action.ts

Core State Structure

interface QuestionnaireDetailState {
  // Basic questionnaire properties
  title: string;
  questionnaireIndex: string;
  isPageLoaded: boolean;
  isBusy: boolean;
  checkField: boolean;
  
  // Question and category management
  categories: CategoryPool;
  categoryIds: CategoryIdPool;
  questions: QuestionPool;
  editingQuestions: EditingQuestionPool;
  editingConditionalQuestions: EditingConditionalQuestionPool;
  
  // Scoring system
  questionWithScore: number;
  questionWithVideos: number;
  totalScoreWeight: number;
  videoSet: number;
  
  // Department and assignment
  questionnaireDepartments: string[];
  questionnaireDeptLabel: DepartmentOption[];
  autoAssignment: AutoAssignmentType;
  autoAssignmentV2: AutoAssignmentTypeV2;
  
  // UI state
  modalShown: boolean;
  autoAssignmentModal: boolean;
  modalType: 'questionnaireLevel' | 'categoryLevel' | 'questionLevel';
  selectedID: string;
  selectedCategory: string;
  
  // Validation and criteria
  passingCriteria?: string;
  passingValue?: number | object;
  passingType?: string;
  categoryAttributes: CategoryAttributes[];
}

Key State Types

// Category management
interface CategoryPool {
  [categoryId: string]: {
    id: string;
    title: string;
    selectValue: {
      label: string;
      value: string;
      _id?: string;
      __isNew__?: boolean;
    };
    questionIds: string[];
    categoryWeights?: number;
  };
}
 
// Question management
interface QuestionPool {
  [questionId: string]: QuestionSingle;
}
 
// Edit state tracking
interface EditingQuestionPool {
  [questionId: string]: boolean;
}
 
interface EditingConditionalQuestionPool {
  [parentQuestionId: string]: {
    [condition: string]: boolean[];
  };
}

Action Creators

Category Management Actions

// Category CRUD operations
export const setCategories = (categories: CategoryPool) => ({
  type: 'SET_CATEGORIES',
  payload: categories
});
 
export const setCategoryIds = (categoryIds: CategoryIdPool) => ({
  type: 'SET_CATEGORY_IDS',
  payload: categoryIds
});
 
export const setCategoryAttributes = (attributes: CategoryAttributes[]) => ({
  type: 'SET_CATEGORY_ATTRIBUTES',
  payload: attributes
});

Question Management Actions

// Question CRUD operations
export const setQuestions = (questions: QuestionPool) => ({
  type: 'SET_QUESTIONS',
  payload: questions
});
 
export const setEditingQuestions = (editingQuestions: EditingQuestionPool) => ({
  type: 'SET_EDITING_QUESTIONS',
  payload: editingQuestions
});
 
export const setConditionalQuestions = (
  questionId: string,
  condition: string,
  conditionalQuestions: Question[]
) => ({
  type: 'SET_CONDITIONAL_QUESTIONS',
  payload: { questionId, condition, conditionalQuestions }
});

Auto Assignment Actions

// Auto assignment management
export const setAutoAssignmentV2 = (autoAssignment: AutoAssignmentTypeV2) => ({
  type: 'SET_AUTO_ASSIGNMENT_V2',
  payload: autoAssignment
});
 
export const showQuestionnaireAutoAssignmentModal = (
  modalType: string,
  selectedID: string,
  questionIdx: string
) => ({
  type: 'SHOW_QUESTIONNAIRE_AUTO_ASSIGNMENT_MODAL',
  payload: { modalType, selectedID, questionIdx }
});

Redux-Saga Side Effects

Questionnaire Saga Files

Key Saga Operations

// Questionnaire CRUD sagas
function* fetchQuestionnaireSaga(action) {
  try {
    const questionnaire = yield call(questionnaireAPI.fetchById, action.payload.id);
    yield put(setQuestionnaireSuccess(questionnaire));
  } catch (error) {
    yield put(setQuestionnaireFailure(error.message));
  }
}
 
function* upsertQuestionnaireSaga(action) {
  try {
    const result = yield call(questionnaireAPI.upsert, action.payload);
    yield put(upsertQuestionnaireSuccess(result));
    yield call(toast.success, 'Questionnaire saved successfully');
  } catch (error) {
    yield put(upsertQuestionnaireFailure(error.message));
    yield call(toast.error, 'Failed to save questionnaire');
  }
}

State Selectors and Computed Values

Selector Patterns

// Basic selectors
const selectCategories = (state: QuestionnaireDetail) => state.questionnaireDetail.categories;
const selectQuestions = (state: QuestionnaireDetail) => state.questionnaireDetail.questions;
const selectCategoryIds = (state: QuestionnaireDetail) => state.questionnaireDetail.categoryIds;
 
// Computed selectors
const selectTotalQuestions = createSelector(
  [selectCategories, selectCategoryIds],
  (categories, categoryIds) => {
    return categoryIds.reduce((total, categoryId) => {
      return total + categories[categoryId].questionIds.length;
    }, 0);
  }
);
 
const selectTotalScoreWeight = createSelector(
  [selectQuestions, selectCategories, selectCategoryIds],
  (questions, categories, categoryIds) => {
    let totalWeight = 0;
    categoryIds.forEach(categoryId => {
      categories[categoryId].questionIds.forEach(questionId => {
        if (questions[questionId].type === 'score') {
          totalWeight += questions[questionId].scoreWeight || 0;
        }
      });
    });
    return totalWeight;
  }
);

State Persistence and Hydration

Auto-Save Mechanisms

The questionnaire editor implements automatic state persistence:

  1. Form State Persistence: Automatic save on field changes
  2. Draft Management: Periodic draft saves during editing
  3. Conflict Resolution: Optimistic updates with rollback capability
  4. Session Recovery: State restoration on page refresh

State Hydration Flow

// Component mount hydration
useEffect(() => {
  if (props.search === '?questionnaire=new') {
    // Initialize new questionnaire state
    dispatch(action.setIsPageLoaded(true));
  } else if (!isPageLoaded) {
    // Load existing questionnaire
    setQuestionnaire();
  }
}, [props.search]);
 
// Questionnaire loading
const setQuestionnaire = async () => {
  const key = props.search.substring('?questionnaire='.length);
  const selectedQuestionnaire = await fetchQuestionaireByID(key);
  
  // Hydrate state from API response
  const formData = updateQuestionnaire(selectedQuestionnaire, inventoryQuestions);
  dispatch(action.setQuestions(formData.newQuestions));
  dispatch(action.setCategories(formData.newCategories));
  // ... additional state hydration
};

State Management Best Practices

Immutable Updates

All state updates follow immutable patterns:

// Correct: Immutable category update
const handleUpdateCategory = (categoryId: string, updates: Partial<Category>) => {
  const newCategories = {
    ...categories,
    [categoryId]: {
      ...categories[categoryId],
      ...updates
    }
  };
  dispatch(action.setCategories(newCategories));
};
 
// Correct: Immutable question addition
const handleAddQuestion = (categoryId: string, newQuestion: Question) => {
  const newQuestions = {
    ...questions,
    [newQuestion.id]: newQuestion
  };
  const newCategories = {
    ...categories,
    [categoryId]: {
      ...categories[categoryId],
      questionIds: [...categories[categoryId].questionIds, newQuestion.id]
    }
  };
  
  dispatch(action.setQuestions(newQuestions));
  dispatch(action.setCategories(newCategories));
};

Performance Optimizations

  1. Memoized Selectors: Using reselect for computed values
  2. Shallow Comparisons: Preventing unnecessary re-renders
  3. Batched Updates: Grouping related state changes
  4. Lazy Loading: Loading questionnaire data on-demand

Validation & Business Logic

The questionnaire module implements comprehensive validation at multiple levels, ensuring data integrity, business rule compliance, and user experience optimization.

Validation Architecture

Multi-Layer Validation Strategy

  1. Client-Side Real-Time Validation: Immediate feedback during form interaction
  2. Pre-Save Validation: Comprehensive validation before persistence
  3. Server-Side Validation: Final validation at API level
  4. Business Rule Validation: Domain-specific constraint enforcement

Question-Level Validation

Core Validation Function

File Location: QuestionnaireEditor.tsx:286-453

const validateQuestion = (question: QuestionSingle): {
  error: boolean;
  question: QuestionSingle;
  tags: { [key: string]: boolean };
} => {
  const tags: { [key: string]: boolean } = {};
  let isRangeFlagInvalid = false;
  let isConditionalInvalid = false;
 
  // Normalize numeric fields
  question.photoLimit = Number(question.photoLimit) || 0;
  question.photoMinimum = Number(question.photoMinimum) || 0;
  question.videoLimit = Number(question.videoLimit) || 0;
  question.videoMinimum = Number(question.videoMinimum) || 0;
 
  // Type-specific validation logic
  // ... detailed validation per question type
  
  return { error: hasValidationErrors, question, tags };
};

Question Type Validation Rules

Binary/Flag Questions
// Binary question validation
if (question.type === 'binary-with-red-flag' || question.type === 'binary') {
  // Validate remedy fields
  if (question.type !== 'binary-with-red-flag' && question.type !== 'binary') {
    question.remedy = '';
    question.potentialRemedies = [];
  } else {
    question.potentialRemedies = question.potentialRemedies
      ? question.potentialRemedies.map(remedy => remedy.trim()).filter(remedy => remedy)
      : [];
  }
  
  // Flag label validation
  if (question.type !== 'binary-with-red-flag') {
    if (question.flagLabel) {
      if (!question.flagLabel.green && !question.flagLabel.yellow && !question.flagLabel.red) {
        delete question.flagLabel;
      }
    }
  }
}
Multiple Choice Validation
// Multiple choice validation
if (question.type === 'multiple-choice') {
  question.multipleChoices = question.multipleChoices
    ? question.multipleChoices.map(choice => choice.trim())
    : [];
 
  // Check for empty choices
  const emptyChoiceIndex = question.multipleChoices.indexOf('');
  
  // Check for duplicates
  const hasDuplicates = question.multipleChoices.length !== 
    [...new Set(question.multipleChoices)].length;
  
  // Firebase character validation
  const hasInvalidCharacters = hasFirebaseInvalidCharacter(question.multipleChoices);
  
  if (emptyChoiceIndex > -1 || hasDuplicates || hasInvalidCharacters) {
    return { error: true, question, tags };
  }
}
Range Flag Validation
// Range flag validation
if (question.type === 'range-flag') {
  const { isOutOfRange, isOverlap, isMinValid, isMaxValid, isMinMaxValid } = 
    checkOutOfRangeValidation(question);
  
  isRangeFlagInvalid = isOutOfRange || isOverlap || !isMinValid || !isMaxValid || !isMinMaxValid;
  
  if (!isRangeFlagInvalid) {
    // Normalize range values
    question.rangeOptions.map(option => {
      option.rangeFrom = Number(option.rangeFrom);
      option.rangeTo = Number(option.rangeTo);
    });
    question.minRange = Number(question.minRange);
    question.maxRange = Number(question.maxRange);
  }
}
Score Question Validation
// Score question validation
if (question.type === 'score') {
  if (!question.hasOwnProperty('scoreWeight')) {
    question.scoreWeight = question.score;
  } else {
    if (isNaN(Number(question.scoreWeight))) {
      question.scoreWeight = 0;
    } else {
      question.scoreWeight = Number(question.scoreWeight);
    }
  }
  
  // Score weight must be greater than 0
  if (question.scoreWeight === 0) {
    return { error: true, question, tags };
  }
}

Range Flag Validation Logic

File Location: /src/components/questionnaires/utils/checkOutOfRangeValidation.ts

interface RangeValidationResult {
  isOutOfRange: boolean;
  isOverlap: boolean;
  isMinValid: boolean;
  isMaxValid: boolean;
  isMinMaxValid: boolean;
  errorFlag: boolean;
}
 
const checkOutOfRangeValidation = (question: Question): RangeValidationResult => {
  const minRange = Number(question.minRange);
  const maxRange = Number(question.maxRange);
  const rangeOptions = question.rangeOptions || [];
 
  // Global range validation
  const isMinMaxValid = minRange < maxRange;
  
  // Individual range validation
  let isOutOfRange = false;
  let isOverlap = false;
  
  for (let i = 0; i < rangeOptions.length; i++) {
    const option = rangeOptions[i];
    const rangeFrom = Number(option.rangeFrom);
    const rangeTo = Number(option.rangeTo);
    
    // Check if range is within global bounds
    if (rangeFrom < minRange || rangeTo > maxRange) {
      isOutOfRange = true;
      break;
    }
    
    // Check for overlapping ranges
    for (let j = i + 1; j < rangeOptions.length; j++) {
      const otherOption = rangeOptions[j];
      const otherFrom = Number(otherOption.rangeFrom);
      const otherTo = Number(otherOption.rangeTo);
      
      if ((rangeFrom <= otherTo && rangeTo >= otherFrom)) {
        isOverlap = true;
        break;
      }
    }
    
    if (isOverlap) break;
  }
  
  return {
    isOutOfRange,
    isOverlap,
    isMinValid: !isNaN(minRange),
    isMaxValid: !isNaN(maxRange),
    isMinMaxValid,
    errorFlag: isOutOfRange || isOverlap || !isMinMaxValid
  };
};

Form-Level Validation

Questionnaire Structure Validation

File Location: QuestionnaireEditor.tsx:862-931

const renderQuestionnaireValidation = (): string | null => {
  const { allowSupervisorEditQuestionnaire, profile } = props;
 
  // Permission validation
  if (profile.role === 'supervisor' && !allowSupervisorEditQuestionnaire) {
    return t('error.questionnairePage.validation.onlyAdmin');
  }
  
  // Title validation
  if (!title) {
    return t('error.questionnairePage.validation.missingTitle');
  }
 
  // Category structure validation
  for (let i = 0; i < categoryIds.length; i++) {
    const categoryId = categoryIds[i];
    const category = categories[categoryId];
 
    // Category title validation
    if (!category.title) {
      handleScrollIntoView(categoryId);
      return t('error.questionnairePage.validation.missingCategoryTitle');
    }
 
    // Category must have questions
    if (category?.questionIds?.length <= 0) {
      handleScrollIntoView(categoryId);
      return t('error.questionnairePage.validation.missingQuestion');
    }
 
    // Question validation within category
    for (let j = 0; j < category.questionIds.length; j++) {
      const questionId = category.questionIds[j];
      const question = questions[questionId];
      
      const validateParentQuestion = validateInput(question, questionId, `Question ${j + 1}`);
      if (validateParentQuestion) {
        return validateParentQuestion;
      }
 
      // Conditional question validation
      if (question.isConditional) {
        const conditionalQuestion = question.conditionalQuestion;
        
        if (!conditionalQuestion || !conditionalQuestion?.conditions) {
          return t('error.questionnairePage.emptyConditional', { questionNumber: j + 1 });
        }
 
        // Validate each conditional branch
        for (const [condition, conditionItem] of Object.entries(conditionalQuestion.conditions)) {
          // Firebase character validation for condition keys
          if (hasFirebaseInvalidCharacter([condition])) {
            return `Answer choice on Question ${j + 1} must not contain ". $ # [ ] / ' \" ,"`;
          }
          
          // Validate each child question
          for (let index = 0; index < conditionItem.length; index++) {
            const item = conditionItem[index];
            const conditionalId = `conditional-question-${questionId}-${condition}-${index}`;
            const conditionalCaption = t('error.questionnairePage.validation.conditionalCaption', {
              conditionalNumber: index + 1,
              questionNumber: j + 1,
              condition,
            });
            
            const validateChildQuestion = validateInput(item, conditionalId, conditionalCaption);
            if (validateChildQuestion) {
              return validateChildQuestion;
            }
          }
        }
      }
    }
  }
 
  return null; // No validation errors
};

Business Logic Validation

Pass Criteria Validation

// Pass criteria business logic validation
if (questionnaireDetail?.passingCriteria || questionnaireDetail?.passingValue || questionnaireDetail?.passingType) {
  // Criteria selection required
  if (!questionnaireDetail?.passingCriteria) {
    toast.error('Please Select Pass Criteria');
    hasError = true;
  }
  
  // Range-based criteria validation
  if (questionnaireDetail?.passingCriteria === enums.Comparator.IN_BETWEEN ||
      questionnaireDetail?.passingCriteria === enums.Comparator.NOT_IN_BETWEEN) {
    if (!(questionnaireDetail?.passingValue?.upperBound > questionnaireDetail?.passingValue?.lowerBound)) {
      toast.error('Please Select Valid Pass Value');
      hasError = true;
    }
  } else {
    // Single value criteria validation
    if (!(questionnaireDetail?.passingValue > 0)) {
      toast.error('Please Select Valid Pass Value');
      hasError = true;
    }
  }
  
  // Pass type required
  if (!questionnaireDetail?.passingType) {
    toast.error('Please Select Pass Type');
    hasError = true;
  }
}

Auto Assignment Validation

// Auto assignment validation rules
const validateAutoAssignment = (autoAssignmentV2: AutoAssignmentTypeV2): boolean => {
  // Questionnaire level validation
  if (autoAssignmentV2.questionnaireLevel) {
    if (!autoAssignmentV2.questionnaireLevel.assignedTo.length) {
      toast.error('Questionnaire level assignment requires at least one assignee');
      return false;
    }
  }
  
  // Category level validation
  autoAssignmentV2.categoryLevel.forEach((category, index) => {
    if (category.type === 'SPECIFIC_DEPT_USER_SITE') {
      if (!category.deptUserSiteMapping?.assignmentDetails?.length) {
        toast.error(`Category ${index + 1} requires assignment details`);
        return false;
      }
    } else {
      if (!category.assignedTo.length) {
        toast.error(`Category ${index + 1} requires at least one assignee`);
        return false;
      }
    }
  });
  
  return true;
};

Validation Error Handling

User Experience Patterns

// Error display with auto-scroll
const handleValidationError = (errorMessage: string, targetId?: string) => {
  // Display error message
  toast.error(errorMessage);
  
  // Auto-scroll to problematic field
  if (targetId) {
    const target = document.getElementById(targetId);
    if (target) {
      target.scrollIntoView({ 
        behavior: 'smooth', 
        block: 'center', 
        inline: 'nearest' 
      });
    }
  }
  
  // Set validation state
  dispatch(action.setCheckField(true));
  dispatch(action.setIsBusy(false));
};

Real-Time Validation Feedback

// Real-time validation during form interaction
const handleFieldChange = (fieldName: string, value: any, questionId: string) => {
  // Update field value
  updateQuestionField(questionId, fieldName, value);
  
  // Immediate validation feedback
  const validationResult = validateField(fieldName, value, questionType);
  if (validationResult.hasError) {
    setFieldError(questionId, fieldName, validationResult.message);
  } else {
    clearFieldError(questionId, fieldName);
  }
};

Character and Content Validation

Firebase Character Restrictions

File Location: /src/components/questionnaires/utils/hasFirebaseInvalidCharacter.ts

const FIREBASE_INVALID_CHARACTERS = ['.', '$', '#', '[', ']', '/', "'", '"', ','];
 
const hasFirebaseInvalidCharacter = (strings: string[]): boolean => {
  return strings.some(str => 
    FIREBASE_INVALID_CHARACTERS.some(char => str.includes(char))
  );
};

Content Length Validation

// Content length limits
const VALIDATION_LIMITS = {
  QUESTION_CONTENT_MAX: 500,
  CHOICE_TEXT_MAX: 100,
  CATEGORY_TITLE_MAX: 100,
  QUESTIONNAIRE_TITLE_MAX: 200,
  REMEDY_TEXT_MAX: 300,
  REFERENCE_TEXT_MAX: 1000,
  KEYWORDS_MAX: 3
};
 
const validateContentLength = (content: string, type: string): boolean => {
  const limit = VALIDATION_LIMITS[`${type}_MAX`];
  return content.length <= limit;
};

Mermaid Diagrams

Questionnaire Editor Architecture Flow

graph TB
    A[QuestionnaireEditPage] --> B[QuestionnaireEditor]
    B --> C[QuestionnaireEditorContainer]
    C --> D[QuestionnaireEditorAdminPage]
    
    D --> E[QuestionnaireEditorHeader]
    D --> F[QuestionnaireEditorForm]
    D --> G[QuestionnaireEditorSummary]
    
    F --> H[QuestionnaireEditorDepartmentList]
    F --> I[QuestionnaireEditorFormCategory]
    F --> J[QuestionnaireEditorPassCriteria]
    
    I --> K[QuestionnaireEditorFormQuestions]
    K --> L[Question Type Components]
    K --> M[QuestionnaireConditionalQuestions]
    
    L --> N[Binary/Flag Questions]
    L --> O[Multiple Choice Questions]
    L --> P[Score Questions]
    L --> Q[Attachment Questions]
    L --> R[Range Flag Questions]
    
    B --> S[Redux State Management]
    S --> T[QuestionnaireDetail Reducer]
    S --> U[Questionnaire Sagas]
    S --> V[API Services]

Question Type System Architecture

graph LR
    A[Question Base] --> B[Binary Questions]
    A --> C[Multiple Choice]
    A --> D[Score Questions]
    A --> E[Text Questions]
    A --> F[Attachment Questions]
    A --> G[Range Questions]
    
    B --> B1[binary]
    B --> B2[binary-with-red-flag]
    
    C --> C1[multiple-choice]
    C --> C2[multiple-choice-score]
    C --> C3[checklist]
    
    D --> D1[score]
    D --> D2[scale]
    D --> D3[weighted-score]
    
    E --> E1[open]
    E --> E2[number]
    
    F --> F1[Photo Attachments]
    F --> F2[Video Attachments]
    F --> F3[Document Attachments]
    F --> F4[AI Object Detection]
    
    G --> G1[range-flag]
    G --> G2[Range Validation]
    G --> G3[Flag Color Mapping]

Auto Assignment System Flow

graph TD
    A[Issue Creation] --> B[Assignment Level Check]
    
    B --> C{Question Level Assignment?}
    C -->|Yes| D[Apply Question Assignment]
    C -->|No| E{Category Level Assignment?}
    
    E -->|Yes| F[Apply Category Assignment]
    E -->|No| G{Questionnaire Level Assignment?}
    
    G -->|Yes| H[Apply Questionnaire Assignment]
    G -->|No| I[No Auto Assignment]
    
    D --> J[Assignment Type Check]
    F --> J
    H --> J
    
    J --> K{Specific User?}
    J --> L{Schedule Dept?}
    J --> M{Dept/User/Site?}
    
    K --> N[Assign to Specific User]
    L --> O[Schedule-Based Assignment]
    M --> P[Site-Specific Assignment]
    
    O --> Q[Check Auditor Schedule]
    O --> R[Filter by Department]
    O --> S[Filter by Level]
    
    P --> T[Match Issue Site]
    P --> U[Apply User Type Rules]
    P --> V[Set Escalation Timer]
    
    N --> W[Send Notifications]
    Q --> W
    T --> W
    
    W --> X[Assignment Complete]

State Management Flow

graph LR
    A[User Action] --> B[Action Creator]
    B --> C[Redux Dispatch]
    C --> D{Async Action?}
    
    D -->|Yes| E[Redux Saga]
    D -->|No| F[Reducer]
    
    E --> G[API Call]
    G --> H{Success?}
    
    H -->|Yes| I[Success Action]
    H -->|No| J[Error Action]
    
    I --> F
    J --> F
    
    F --> K[State Update]
    K --> L[Component Re-render]
    L --> M[UI Update]
    
    E --> N[Side Effects]
    N --> O[Toast Notifications]
    N --> P[Navigation]
    N --> Q[Analytics Tracking]

Package Dependencies

Core Framework Dependencies

PackageVersionPurposeUsage in Questionnaire Module
react^17.0.2Core React libraryComponent architecture, hooks, context
react-dom^17.0.2React DOM rendererComponent rendering, portal usage
react-router-dom^5.3.0Client-side routingQuestionnaire navigation, route protection
react-redux^7.2.6React-Redux integrationState management, component connection
redux^4.1.2State managementCentral state store, predictable updates
redux-saga^1.1.3Side effect managementAsync operations, API calls, business logic

UI and Styling Dependencies

PackageVersionPurposeUsage in Questionnaire Module
styled-components^5.3.0CSS-in-JS stylingComponent styling, theme management
react-beautiful-dnd^13.1.0Drag and drop functionalityQuestion/category reordering
react-select^5.2.1Advanced select componentsCategory selection, user assignment
react-i18next^11.15.3InternationalizationMulti-language support, translations
react-toastify^8.1.0Toast notificationsSuccess/error messages, user feedback

Form and Input Management

PackageVersionPurposeUsage in Questionnaire Module
formik^2.2.9Form state managementForm validation, field management
yup^0.32.11Schema validationInput validation, error messages
react-hook-form^7.27.0Performant formsComplex form handling, validation

Utility Libraries

PackageVersionPurposeUsage in Questionnaire Module
lodash^4.17.21Utility functionsData manipulation, deep cloning, validation
moment^2.29.1Date/time manipulationTimestamp handling, date formatting
uuid^8.3.2Unique ID generationQuestion/category ID generation
clsx^1.1.1Conditional CSS classesDynamic styling, state-based classes

Firebase Integration

PackageVersionPurposeUsage in Questionnaire Module
firebase^9.6.1Firebase SDKAuthentication, real-time database
react-redux-firebase^3.10.0Firebase-Redux integrationAuth state, profile management
redux-firestore^0.15.0Firestore-Redux integrationReal-time data synchronization

Development and Build Tools

PackageVersionPurposeUsage in Questionnaire Module
typescript^4.5.4Type systemType safety, developer experience
@types/react^17.0.38React type definitionsComponent typing, props validation
@types/lodash^4.14.178Lodash type definitionsUtility function typing
eslint^8.6.0Code lintingCode quality, consistency
prettier^2.5.1Code formattingConsistent code style

Nimbly-Specific Dependencies

PackageVersionPurposeUsage in Questionnaire Module
@nimbly-technologies/nimbly-common^latestShared utilities and typesQuestion types, enumerators, common interfaces
@loadable/component^5.15.2Code splittingLazy loading, performance optimization

Testing Dependencies

PackageVersionPurposeUsage in Questionnaire Module
@testing-library/react^12.1.2React testing utilitiesComponent testing, integration tests
@testing-library/jest-dom^5.16.1Jest DOM matchersDOM assertion testing
jest^27.4.7Testing frameworkUnit tests, snapshot testing

Performance and Optimization

PackageVersionPurposeUsage in Questionnaire Module
react-window^1.8.6Virtual scrollingLarge question list optimization
reselect^4.1.5Memoized selectorsState computation optimization
immer^9.0.7Immutable state updatesRedux state management

Analytics and Monitoring

PackageVersionPurposeUsage in Questionnaire Module
react-ga^3.3.0Google AnalyticsUser interaction tracking
@sentry/react^6.17.4Error monitoringError tracking, performance monitoring

Conclusion

The Questionnaire Module represents a sophisticated, enterprise-grade solution for audit questionnaire management. With its comprehensive feature set including 15+ question types, conditional logic, auto assignment capabilities, and rich attachment support, it provides administrators with powerful tools for creating complex audit workflows.

The architecture follows clean code principles with clear separation of concerns, comprehensive validation, and robust state management. The module’s extensible design allows for future enhancements while maintaining backward compatibility and performance optimization.

Key Architectural Strengths:

  • Scalable Component Architecture: Modular design with reusable components
  • Comprehensive State Management: Redux with saga middleware for complex workflows
  • Type Safety: Full TypeScript implementation with strict type checking
  • Performance Optimization: Lazy loading, memoization, and virtual scrolling
  • User Experience: Intuitive drag-and-drop interface with real-time validation
  • Enterprise Features: Multi-level auto assignment, department management, and audit trails

Business Value Delivered:

  • Flexibility: Support for diverse audit scenarios and question types
  • Automation: Intelligent assignment reducing manual intervention
  • Compliance: Comprehensive validation ensuring data integrity
  • Scalability: Architecture supporting large-scale enterprise deployments
  • User Productivity: Streamlined interface reducing questionnaire creation time

This documentation serves as a comprehensive technical reference for developers, architects, and stakeholders working with the Nimbly audit administration platform’s questionnaire system.


GitHub Repository: Nimbly Technologies - Audit Admin

Generated: December 2024
Document Version: 1.0
Total Characters: 47,000+