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
| Route | Component | Purpose | Access Control |
|---|---|---|---|
/admin/questionnaires | QuestionnairesPage | Main questionnaires listing | RoleResources.ADMIN_QUESTIONNAIRE_ALL |
/admin/questionnaires/edit | QuestionnaireEditPage | Questionnaire editor | RoleResources.ADMIN_QUESTIONNAIRE_ALL |
/admin/questionnaires/edit?questionnaire=new | QuestionnaireEditPage | Create new questionnaire | RoleResources.ADMIN_QUESTIONNAIRE_ALL |
/admin/questionnaires/edit?questionnaire={id} | QuestionnaireEditPage | Edit existing questionnaire | RoleResources.ADMIN_QUESTIONNAIRE_ALL |
/admin/formula-builder | FormulaListPage | Formula management | RoleResources.ADMIN_QUESTIONNAIRE_ALL |
/admin/formula-builder/{formulaId} | FormulaBuilderPage | Formula builder | RoleResources.ADMIN_QUESTIONNAIRE_ALL |
Customer Feedback Routes
| Route | Component | Purpose | Access Control |
|---|---|---|---|
/admin/customerFeedback | CustomerFeedbackCampaignPage | Campaign management | Features.CUSTOMER_FEEDBACK |
/admin/customerFeedback/qr-editor | CustomerFeedbackQrEditorPage | QR code editor | RoleResources.ADMIN_CUSTOMERFEEDBACK_QR |
/admin/customerFeedback/{questionnaireId} | CustomerFeedbackCampaignDetailPage | Campaign details | Features.CUSTOMER_FEEDBACK |
Route Protection
- Feature Flags:
Features.CUSTOMIZABLE_QUESTIONNAIRE - Role-Based Access: Admin and Superadmin roles
- Supervisor Override:
allowSupervisorEditQuestionnairesetting
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 editinghandleSave(): Orchestrates questionnaire persistencevalidateQuestion(): Comprehensive question validationrenderQuestionnaireValidation(): 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 categorieshandleAddQuestion(): Adds questions to categorieshandleDragEnd(): 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:
- Selection: Dropdown populated from organization departments
- Addition: Selected departments added to questionnaire scope
- Display: Departments shown as removable tags
- Auto Assignment: Triggers auto assignment recalculation
- 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:
- Score Recalculation: Updates total questionnaire score weights
- Video Quota: Adjusts video usage quotas
- Auto Assignment: Removes category-level assignments
- 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
- Issue Creation: When questionnaire generates an issue
- Level Resolution: Check question → category → questionnaire level assignments
- Type Processing: Execute assignment logic based on type
- Site Matching: For site-specific assignments, match issue site to rules
- User Assignment: Assign to specified users or schedule-based logic
- Member Notification: Notify assigned members
- 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:
QuestionnaireQuestionsConditional.tsx- Main conditional logic handlerQuestionnaireConditionalQuestions.tsx- Individual conditional question containerQuestionnaireConditionalQuestionsHead.tsx- Question header managementQuestionnaireConditionalQuestionsBody.tsx- Question content management
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
- Parent Question Setup: Configure parent question with responses
- Conditional Toggle: Enable conditional questions on parent
- Condition Generation: System creates condition slots for each parent response
- Child Question Addition: Add questions to specific conditions
- Configuration: Configure child questions independently
Editing Workflow
- Selection: Click “Show More” to expand conditional questions
- Edit Mode: Enter edit mode for specific child question
- Configuration: Full question configuration interface
- Validation: Real-time validation during editing
- 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_DETECTIONfeature flag - Maximum 3 keywords (comma-separated)
- AI keyword recommendations via
/vertex-ai/imagevalidation/recommend-keywordsAPI - 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
bnnrestaurantandsustainnovationorganizations - 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_DETECTIONfeature 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:
- Trigger: Click “Get AI Recommendations” button
- API Call: POST to
/vertex-ai/imagevalidation/recommend-keywords - Response Processing: Parse recommended keywords
- User Selection: Apply or cancel recommendation workflow
- 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
additionalValidTypesparameter
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
maxFilesparameter - 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_DETECTIONfeature 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
| Endpoint | Method | Purpose | File Reference |
|---|---|---|---|
/questionnaires | GET | Fetch all questionnaires | questionnaire.service.ts |
/questionnaires/ | POST | Create/Update questionnaire | questionnaire.service.ts |
/questionnaires/{questionnaireID} | DELETE | Delete questionnaire | deleteQuestionnaire.ts |
/questionnaires/questionnaireIndexes | GET | Get all questionnaire indexes | questionnaire.service.ts |
/questionnaires/questionnaireIndexes/{id} | GET | Get specific questionnaire by ID | fetchQuestionnaireByID.ts |
/questionnaires/questionnaireIndexes/{id}/latest | GET | Get latest questionnaire version | questionnaire.service.ts |
/questionnaires/questionnaireIndexes/minified | GET | Get minified questionnaire data with props | fetchQuestionnaireByProps.ts |
/questionnaires/questionnaireIndexes/paginate | GET | Get paginated questionnaires | questionnaire.service.ts |
Template Management APIs
| Endpoint | Method | Purpose | Authentication |
|---|---|---|---|
/questionnaires/questionnairetemplates | GET | Fetch questionnaire templates | Firebase Token |
/questionnaires/questionnairetemplates/template | GET | Download questionnaire template by title | Firebase Token |
Department Integration APIs
| Endpoint | Method | Purpose | Authentication |
|---|---|---|---|
/departments/questionnaire/{questionnaireKey} | GET | Get questionnaire departments | Firebase Token |
/departments/questionnaire-index | POST | Update questionnaire departments | Firebase Token |
Bulk Operations APIs
| Endpoint | Method | Purpose | File Reference |
|---|---|---|---|
/questionnaires/download | POST | Download multiple questionnaires | downloadQuestionnaires.ts |
/questionnaires/bulk-upload | POST | Upload bulk questionnaires | uploadBulkQuestionnaire.ts |
/bulk-edit/download | POST | Bulk download questionnaires (revamp) | bulkDownloadQuestionnaire.service.ts |
Customer Feedback APIs
| Endpoint | Method | Purpose | Authentication |
|---|---|---|---|
/customer-feedback/campaign | GET | Get campaign list | Firebase Token |
/customer-feedback/campaign/{id} | GET | Get specific campaign | Firebase Token |
/customer-feedback/campaign/status/{questionnaireID} | PUT | Update campaign status | Firebase Token |
/customer-feedback/trends/{questionnaireId} | POST | Get campaign report data | Firebase Token |
AI and Analytics APIs
| Endpoint | Method | Purpose | Authentication |
|---|---|---|---|
/vertex-ai/imagevalidation/recommend-keywords | POST | Get AI-recommended keywords for object detection | Firebase Token |
/statistics/issues/questionnaires | GET | Get questionnaire usage statistics | Firebase 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 retrievalupsertQuestionnaire(): Create/update questionnairedeleteQuestionnaire(): Questionnaire removalfetchTemplates(): 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
- Primary Saga:
/src/sagas/questionnaire/questionnaire.actionSaga.ts - Index Saga:
/src/sagas/questionnaireIndex/questionnaireIndex.actionSaga.ts - Clone Saga:
/src/reducers/questionnaire/questionnaire.saga.cloneQuestionnaire.ts
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:
- Form State Persistence: Automatic save on field changes
- Draft Management: Periodic draft saves during editing
- Conflict Resolution: Optimistic updates with rollback capability
- 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
- Memoized Selectors: Using
reselectfor computed values - Shallow Comparisons: Preventing unnecessary re-renders
- Batched Updates: Grouping related state changes
- 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
- Client-Side Real-Time Validation: Immediate feedback during form interaction
- Pre-Save Validation: Comprehensive validation before persistence
- Server-Side Validation: Final validation at API level
- 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
| Package | Version | Purpose | Usage in Questionnaire Module |
|---|---|---|---|
react | ^17.0.2 | Core React library | Component architecture, hooks, context |
react-dom | ^17.0.2 | React DOM renderer | Component rendering, portal usage |
react-router-dom | ^5.3.0 | Client-side routing | Questionnaire navigation, route protection |
react-redux | ^7.2.6 | React-Redux integration | State management, component connection |
redux | ^4.1.2 | State management | Central state store, predictable updates |
redux-saga | ^1.1.3 | Side effect management | Async operations, API calls, business logic |
UI and Styling Dependencies
| Package | Version | Purpose | Usage in Questionnaire Module |
|---|---|---|---|
styled-components | ^5.3.0 | CSS-in-JS styling | Component styling, theme management |
react-beautiful-dnd | ^13.1.0 | Drag and drop functionality | Question/category reordering |
react-select | ^5.2.1 | Advanced select components | Category selection, user assignment |
react-i18next | ^11.15.3 | Internationalization | Multi-language support, translations |
react-toastify | ^8.1.0 | Toast notifications | Success/error messages, user feedback |
Form and Input Management
| Package | Version | Purpose | Usage in Questionnaire Module |
|---|---|---|---|
formik | ^2.2.9 | Form state management | Form validation, field management |
yup | ^0.32.11 | Schema validation | Input validation, error messages |
react-hook-form | ^7.27.0 | Performant forms | Complex form handling, validation |
Utility Libraries
| Package | Version | Purpose | Usage in Questionnaire Module |
|---|---|---|---|
lodash | ^4.17.21 | Utility functions | Data manipulation, deep cloning, validation |
moment | ^2.29.1 | Date/time manipulation | Timestamp handling, date formatting |
uuid | ^8.3.2 | Unique ID generation | Question/category ID generation |
clsx | ^1.1.1 | Conditional CSS classes | Dynamic styling, state-based classes |
Firebase Integration
| Package | Version | Purpose | Usage in Questionnaire Module |
|---|---|---|---|
firebase | ^9.6.1 | Firebase SDK | Authentication, real-time database |
react-redux-firebase | ^3.10.0 | Firebase-Redux integration | Auth state, profile management |
redux-firestore | ^0.15.0 | Firestore-Redux integration | Real-time data synchronization |
Development and Build Tools
| Package | Version | Purpose | Usage in Questionnaire Module |
|---|---|---|---|
typescript | ^4.5.4 | Type system | Type safety, developer experience |
@types/react | ^17.0.38 | React type definitions | Component typing, props validation |
@types/lodash | ^4.14.178 | Lodash type definitions | Utility function typing |
eslint | ^8.6.0 | Code linting | Code quality, consistency |
prettier | ^2.5.1 | Code formatting | Consistent code style |
Nimbly-Specific Dependencies
| Package | Version | Purpose | Usage in Questionnaire Module |
|---|---|---|---|
@nimbly-technologies/nimbly-common | ^latest | Shared utilities and types | Question types, enumerators, common interfaces |
@loadable/component | ^5.15.2 | Code splitting | Lazy loading, performance optimization |
Testing Dependencies
| Package | Version | Purpose | Usage in Questionnaire Module |
|---|---|---|---|
@testing-library/react | ^12.1.2 | React testing utilities | Component testing, integration tests |
@testing-library/jest-dom | ^5.16.1 | Jest DOM matchers | DOM assertion testing |
jest | ^27.4.7 | Testing framework | Unit tests, snapshot testing |
Performance and Optimization
| Package | Version | Purpose | Usage in Questionnaire Module |
|---|---|---|---|
react-window | ^1.8.6 | Virtual scrolling | Large question list optimization |
reselect | ^4.1.5 | Memoized selectors | State computation optimization |
immer | ^9.0.7 | Immutable state updates | Redux state management |
Analytics and Monitoring
| Package | Version | Purpose | Usage in Questionnaire Module |
|---|---|---|---|
react-ga | ^3.3.0 | Google Analytics | User interaction tracking |
@sentry/react | ^6.17.4 | Error monitoring | Error 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+