Overview

The Department Module is a comprehensive administrative system within the Nimbly audit-admin platform that provides functionality for managing organizational departments and department groups. This module enables organizations to create, manage, and organize departments with hierarchical user assignments, site associations, questionnaire mappings, and sophisticated escalation workflows.

Core Functionality

The department module provides the following key capabilities:

  1. Department Management: Create, update, delete, and manage departments within an organization
  2. Department Groups: Organize multiple departments into logical groups for better management
  3. User Assignment: Assign users to departments with different hierarchy levels
  4. Site Association: Link sites/locations to specific departments
  5. Questionnaire Mapping: Associate questionnaires with departments for targeted audits
  6. Issue Escalation: Configure multi-level escalation workflows with time-based triggers
  7. Bulk Operations: Support for bulk department creation via Excel upload
  8. Status Management: Manage active/inactive states for departments and groups

Module Architecture Overview

graph TB
    subgraph "Frontend Layer"
        Pages[Pages Layer]
        Components[Component Layer]
        Redux[Redux State Management]
        Sagas[Redux Sagas]
    end
    
    subgraph "API Layer"
        REST[REST APIs]
        CloudV2[Cloud V2 APIs]
    end
    
    subgraph "Backend Services"
        DeptService[Department Service]
        IndexService[Department Index Service]
        GroupService[Department Group Service]
    end
    
    Pages --> Components
    Components --> Redux
    Redux --> Sagas
    Sagas --> REST
    Sagas --> CloudV2
    REST --> DeptService
    REST --> IndexService
    CloudV2 --> DeptService
    REST --> GroupService

Architecture

Component Structure

The department module follows a modular component architecture with clear separation of concerns:

src/
├── pages/
│   ├── departments.js                 # Department listing page
│   ├── departments-edit.tsx           # Department editor page
│   └── admin/departmentsgroup/        # Department groups pages
├── components/departments/
│   ├── DepartmentManager.js           # Main department manager
│   ├── DepartmentList.js              # Department list display
│   ├── DepartmentListHeader/          # List header component
│   ├── DepartmentEditor/              # Department CRUD editor
│   ├── DepartmentBulkModal.tsx        # Bulk upload modal
│   ├── DepartmentDeleteModal/         # Delete confirmation modal
│   ├── DepartmentNavigation.tsx       # Navigation tabs
│   ├── DepartmentGroupManager/        # Group management
│   ├── DepartmentGroupList/           # Group listing
│   ├── DepartmentGroupEditor/         # Group CRUD editor
│   └── utils/                         # Utility functions
├── reducers/
│   ├── departments/                   # Department state management
│   ├── departmentEditor/              # Editor state management
│   └── departmentGroup/               # Group state management
├── sagas/
│   ├── departments/                   # Department side effects
│   ├── departmentEditor/              # Editor side effects
│   └── DepartmentGroup/               # Group side effects
└── models/
    └── departmentIndex.ts             # Department index model

State Management Architecture

The module uses Redux for state management with the following structure:

graph LR
    subgraph "Redux Store"
        DeptState[departments]
        EditorState[departmentEditor]
        GroupState[departmentGroup]
    end
    
    subgraph "Actions"
        DeptActions[Department Actions]
        EditorActions[Editor Actions]
        GroupActions[Group Actions]
    end
    
    subgraph "Sagas"
        DeptSagas[Department Sagas]
        EditorSagas[Editor Sagas]
        GroupSagas[Group Sagas]
    end
    
    DeptActions --> DeptState
    EditorActions --> EditorState
    GroupActions --> GroupState
    
    DeptSagas --> DeptActions
    EditorSagas --> EditorActions
    GroupSagas --> GroupActions

Data Models and Types

Department Model

interface Department {
  departmentID: string;      // Unique department identifier
  name: string;              // Department name
  description: string;       // Department description
  email: string;             // Department contact email
  status: 'active' | 'disabled';  // Department status
  organizationID: string;    // Parent organization ID
  createdAt?: Date;
  updatedAt?: Date;
}

Department Index Model

interface DepartmentIndex {
  ID: string;                // Document ID
  departmentID: string;      // Department reference
  organizationID: string;    // Organization reference
  name: string;
  description: string;
  email: string;
  disabled: boolean;
  defaultIssueOwner?: string; // Default issue assignee
  users: Array<{             // User assignments
    uid: string;
    level: number;           // Hierarchy level (1, 2, 3...)
  }>;
  sites: Array<{             // Associated sites
    id: string;
  }>;
  questionnaires: Array<{    // Associated questionnaires
    id: string;
  }>;
  approvers: Array<{         // Issue approvers
    id: string;
  }>;
  escalateTime: {            // Escalation configuration
    [level: number]: number | null;  // Hours to escalate
  };
}

Department Group Model

interface DepartmentGroup {
  ID: string;                // Group identifier
  name: string;              // Group name
  description: string;       // Group description
  status: 'active' | 'disabled';  // Group status
  departments: Array<{       // Associated departments
    id: string;
  }>;
  organizationID: string;
  createdAt?: Date;
  updatedAt?: Date;
}

State Models

interface DepartmentsState {
  // Filter and Sort
  filterQuery: string;
  sortBy: string;
  key: 'asc' | 'desc';
  name: 'asc' | 'desc';
  description: 'asc' | 'desc';
  email: 'asc' | 'desc';
  
  // Selected Items
  selectedDepartmentKey: string | null;
  selectedDepartment: Department | null;
  selectedDepartmentDeleteKey: string | null;
  selectedTab: string;
  
  // Data
  departments: { [key: string]: Department } | null;
  departmentsPaginate: { [key: string]: Department } | null;
  departmentIndex: DepartmentIndex | null;
  departmentsIndex: { [key: string]: DepartmentIndex } | null;
  departmentIndexWithRoles: DepartmentIndexUserInSite | null;
  
  // Loading States
  isLoaded: boolean;
  isLoadedDepartments: boolean;
  isLoadingDepartments: boolean;
  isLoadingDepartmentIndex: boolean;
  isLoadingDepartmentIndexWithRoles: boolean;
  
  // Pagination
  pageIndex: number;
  totalDepartments: number | null;
  
  // UI States
  showDepartmentBulkModal: boolean;
  showDepartmentDeleteModal: boolean;
  
  // Schedule Data
  isLoadingSchedule: boolean;
  departmentSchedule: any;
  
  // Error Handling
  error: string | null;
}

Department Management

Department Creation Flow

The department creation process involves multiple steps and validations:

sequenceDiagram
    participant User
    participant UI
    participant Redux
    participant Saga
    participant API
    
    User->>UI: Click "Add Department"
    UI->>UI: Navigate to /admin/departments/new
    UI->>User: Display Department Editor Form
    
    User->>UI: Fill Department Details
    Note over UI: Name, Description, Email, Key
    
    UI->>API: Check Department Key Availability
    API-->>UI: Key Available/Unavailable
    
    User->>UI: Assign Users, Sites, Questionnaires
    User->>UI: Configure Escalation Levels
    User->>UI: Click Save
    
    UI->>Redux: Dispatch createDepartment.request
    Redux->>Saga: Handle Creation
    Saga->>API: POST /departments/
    API-->>Saga: Success/Error Response
    Saga->>Redux: Update State
    Redux->>UI: Show Success/Error
    UI->>User: Navigate to Department List

Department Update Flow

sequenceDiagram
    participant User
    participant UI
    participant Redux
    participant Saga
    participant API
    
    User->>UI: Click Edit Department
    UI->>Redux: Fetch Department Index
    Redux->>Saga: fetchDepartmentIndexById
    Saga->>API: GET /departments/index/{deptID}
    API-->>Saga: Department Data
    Saga->>Redux: Update State
    Redux->>UI: Populate Form
    
    User->>UI: Modify Department Details
    User->>UI: Click Save
    
    UI->>Redux: Dispatch updateDepartment.request
    Redux->>Saga: Handle Update
    Saga->>API: PUT /departments/index/{deptID}
    API-->>Saga: Success/Error Response
    Saga->>Redux: Update State
    Redux->>UI: Show Success/Error

Department Deletion/Deactivation

Departments are not physically deleted but rather deactivated:

// Department status toggle flow
const toggleDepartmentStatus = async (departmentKey: string) => {
  // 1. Check for active schedules
  const schedules = await fetchDepartmentSchedule(departmentKey);
  
  // 2. If schedules exist, show warning
  if (schedules.length > 0) {
    showWarningModal("Department has active schedules");
    return;
  }
  
  // 3. Proceed with status toggle
  await updateDepartmentStatus(departmentKey, 'disabled');
};

Bulk Department Upload

The system supports bulk department creation via Excel upload:

// Bulk upload process
const bulkUploadDepartments = async (file: File) => {
  // 1. Parse Excel file
  const departments = await parseExcelFile(file);
  
  // 2. Validate each department
  const validationResults = validateDepartments(departments);
  
  // 3. If errors, display them
  if (validationResults.errors.length > 0) {
    displayErrors(validationResults.errors);
    return;
  }
  
  // 4. Upload valid departments
  await uploadBulkDepartments(validationResults.valid);
};

Department Validation Rules

  1. Department Key Validation:

    • Must be unique within organization
    • Alphanumeric characters and hyphens only
    • No special characters or spaces
    • Case-sensitive
  2. Required Fields:

    • Department Name
    • Department Description
    • Department Email (valid email format)
    • Department Key
  3. Assignment Validations:

    • At least one user must be assigned
    • Users can only be in one level per department
    • Sites can be assigned to multiple departments
    • Questionnaires can be shared across departments

Department Group Management

Department Group Creation

flowchart TD
    A[Start] --> B[Navigate to Department Groups]
    B --> C[Click Create New Group]
    C --> D[Enter Group Details]
    D --> E{Validate Input}
    E -->|Invalid| F[Show Validation Errors]
    F --> D
    E -->|Valid| G[Select Departments]
    G --> H{At Least One Dept?}
    H -->|No| I[Show Error]
    I --> G
    H -->|Yes| J[Save Group]
    J --> K[API Call]
    K --> L[Update Redux State]
    L --> M[Navigate to Group List]
    M --> N[End]

Group Management Features

  1. Group Operations:

    • Create new department groups
    • Edit existing groups (name, description, departments)
    • Activate/Deactivate groups
    • Delete groups (soft delete)
  2. Department Assignment:

    • Add/remove departments from groups
    • Bulk select all departments
    • Visual department count display
  3. Group Filtering:

    • Search by group name
    • Filter by status (active/disabled)
    • Sort alphabetically

API Endpoints

Department APIs

MethodEndpointDescriptionFile Reference
GET/departmentsFetch all departmentsdepartments.actionSaga.ts:178
GET/departments/paginateFetch paginated departmentsdepartments.actionSaga.ts:307
GET/departments/index/Fetch all department indicesdepartments.actionSaga.ts:28
GET/departments/index/{deptID}Fetch specific department indexdepartments.actionSaga.ts:85
POST/departments/Create new departmentdepartmentEditor.actionSaga.ts:32
PUT/departments/index/{deptID}Update departmentdepartmentEditor.actionSaga.ts:67
GET/departments/open/department/{org}/{key}Check department key availabilityDepartmentEditor.tsx:488
GET/schedules/departments/{id}Fetch department schedulesdepartments.actionSaga.ts:350
GET/departments/user-filter-optionsFetch departments for user filteringdepartments.actionSaga.ts:233

Department Group APIs

MethodEndpointDescriptionFile Reference
GET/departments/groupFetch all department groupsDepartmentGroup.Saga.ts:26
GET/departments/group/{groupDeptID}Fetch specific groupDepartmentGroup.Saga.ts:44
POST/departments/groupCreate department groupDepartmentGroupEditor.Saga.ts
PUT/departments/group/{groupDeptID}Update department groupDepartmentGroupEditor.Saga.ts
PUT/departments/group/toggle-status/{groupDeptID}Toggle group statusDepartmentGroup.Saga.ts:80

Cloud V2 APIs

MethodEndpointDescriptionFile Reference
GET/admin/department-indexes/{deptID}?qType=user-role-in-siteFetch department with user rolesdepartments.actionSaga.ts:115
PUT/admin/sites/replace-supervisorsReplace department supervisorsdepartments.actionSaga.ts:145

State Management

Redux Actions

Department Actions

// Fetch Actions
fetchDepartments.request()
fetchDepartments.success(data)
fetchDepartments.failure(error)
 
fetchDepartmentIndexById.request({ deptID })
fetchDepartmentIndexById.success({ data })
fetchDepartmentIndexById.failure({ error })
 
fetchPaginateDepartments.request({ limit })
fetchPaginateDepartments.success({ data, total })
fetchPaginateDepartments.failure({ error })
 
// UI Actions
setDepartmentsFilterQuery(text)
clearDepartmentsFilterQuery()
setSortDepartment(type, order)
setPageIndex(value)
setSelectedTab(value)
setShowDepartmentBulkModal(value)
setShowDepartmentDeleteModal(value)
setDimissDepartmentDeleteModal()

Department Editor Actions

// CRUD Actions
createDepartment.request({ data })
createDepartment.success()
createDepartment.failure({ error })
 
updateDepartment.request({ deptID, data, assignSupervisor })
updateDepartment.success()
updateDepartment.failure({ error, errorData })
 
// UI Actions
setShowModal(value)
setIsSuccess(value)

Department Group Actions

// Fetch Actions
getDepartmentGroup.request()
getDepartmentGroup.success({ data })
getDepartmentGroup.failure({ error })
 
getDepartmentGroupById.request({ groupDeptID })
getDepartmentGroupById.success({ data })
getDepartmentGroupById.failure({ error })
 
// CRUD Actions
createDepartmentGroup.request({ data })
createDepartmentGroup.success()
createDepartmentGroup.failure({ error })
 
updateDepartmentGroup.request({ groupDeptID, data })
updateDepartmentGroup.success()
updateDepartmentGroup.failure({ error })
 
deleteDepartmentGroup.request({ groupDeptID })
deleteDepartmentGroup.success({ newDeptGroup, selectedDepartment })
deleteDepartmentGroup.failure({ error })
 
// UI Actions
setDeptGroupActiveTab(tab)
setEditingDetail(value)

Redux Sagas

The module uses Redux-Saga for handling side effects:

graph TD
    subgraph "Department Sagas"
        A[fetchDepartments] --> B[API Call]
        B --> C{Success?}
        C -->|Yes| D[Update State]
        C -->|No| E[Handle Error]
        
        F[fetchPaginateDepartments] --> G[Build Query]
        G --> H[API Call with Pagination]
        H --> I[Update Paginated State]
    end
    
    subgraph "Editor Sagas"
        J[createDepartment] --> K[Validate Data]
        K --> L[API POST]
        L --> M[Show Toast]
        M --> N[Navigate Away]
        
        O[updateDepartment] --> P[Check Supervisors]
        P --> Q[API PUT]
        Q --> R[Clear Cache]
        R --> S[Update State]
    end

State Selectors

// Common selectors used in the module
const selectDepartments = (state: RootState) => state.departments.departments;
const selectDepartmentsPaginate = (state: RootState) => state.departments.departmentsPaginate;
const selectDepartmentIndex = (state: RootState) => state.departments.departmentIndex;
const selectIsLoading = (state: RootState) => state.departments.isLoadingDepartments;
const selectActiveTab = (state: RootState) => state.departments.selectedTab;
const selectFilterQuery = (state: RootState) => state.departments.filterQuery;
const selectPageIndex = (state: RootState) => state.departments.pageIndex;
const selectTotalDepartments = (state: RootState) => state.departments.totalDepartments;
 
// Department Group selectors
const selectDepartmentGroups = (state: RootState) => state.departmentGroup.departmentGroup;
const selectDepartmentGroupById = (state: RootState) => state.departmentGroup.departmentGroupById;
const selectGroupActiveTab = (state: RootState) => state.departmentGroup.listActiveTab;
const selectEditingDetail = (state: RootState) => state.departmentGroup.editingDetail;

Component Architecture

Component Hierarchy

graph TD
    subgraph "Department Pages"
        DeptPage[departments.js]
        DeptEditPage[departments-edit.tsx]
        DeptGroupPage[departmentsgroup]
    end
    
    subgraph "Department Components"
        DeptManager[DepartmentManager]
        DeptList[DepartmentList]
        DeptHeader[DepartmentListHeader]
        DeptNav[DepartmentNavigation]
        DeptEditor[DepartmentEditor]
        DeptEditorContainer[DepartmentEditorContainer]
        DeptBulkModal[DepartmentBulkModal]
        DeptDeleteModal[DepartmentDeleteModal]
    end
    
    subgraph "Department Group Components"
        GroupManager[DepartmentGroupManager]
        GroupList[DepartmentGroupList]
        GroupEditor[DepartmentGroupEditor]
        GroupEditorContainer[DepartmentGroupEditorContainer]
    end
    
    DeptPage --> DeptManager
    DeptManager --> DeptHeader
    DeptManager --> DeptNav
    DeptManager --> DeptList
    DeptManager --> DeptBulkModal
    DeptManager --> DeptDeleteModal
    
    DeptEditPage --> DeptEditor
    DeptEditor --> DeptEditorContainer
    
    DeptGroupPage --> GroupManager
    GroupManager --> DeptNav
    GroupManager --> GroupList
    GroupManager --> GroupEditor
    GroupEditor --> GroupEditorContainer

Key Components

DepartmentManager Component

The main container component that orchestrates the department listing functionality:

// Key responsibilities:
// 1. Fetches and displays department list
// 2. Handles pagination and filtering
// 3. Manages active/inactive tabs
// 4. Coordinates delete operations
// 5. Handles bulk upload modal
 
const DepartmentManager = (props) => {
  // Pagination calculation based on window height
  const deptItemListLimit = React.useMemo(() => {
    const deductor = [40, 30, 36, 20, 24, 34, 30, 20];
    const deductorTotal = deductor.reduce((curr, acc) => curr + acc);
    const itemHeight = 30;
    const itemPadding = 3;
    return Math.ceil((windowHeight - deductorTotal) / (itemHeight + itemPadding));
  }, [windowHeight]);
  
  // Fetch departments on mount and filter changes
  useEffect(() => {
    props.fetchPaginateDepartments({ limit: deptItemListLimit });
  }, [activeTab, filterQuery, name, email, description, key, pageIndex, sortBy]);
};

DepartmentEditor Component

Complex form component handling department creation and editing:

// Key features:
// 1. Dynamic user assignment with hierarchy levels
// 2. Site and questionnaire association
// 3. Escalation time configuration
// 4. Real-time validation
// 5. Supervisor replacement handling
 
const DepartmentEditor = (props) => {
  // State management for form fields
  const [departmentName, setDepartmentName] = useState('');
  const [departmentCode, setDepartmentCode] = useState('');
  const [assignSites, setAssignSites] = useState<AssignItem[]>([]);
  const [assignQuestionnaires, setAssignQuestionnaires] = useState<AssignItem[]>([]);
  const [userLevels, setUserLevels] = useState<{ [level: string]: { users: {} } }>({});
  const [escalationTime, setEscalationTime] = useState<{ [level: number]: { label: string; value: number } | null }>({});
  
  // Escalation level management
  const handleAddLevel = () => {
    const newLevel = Math.max(...departmentLevels) + 1;
    setUserLevels({
      ...userLevels,
      [newLevel]: { users: {} },
    });
    setDepartmentLevels([...departmentLevels, newLevel]);
  };
};

DepartmentGroupManager Component

Manages the department group listing and editing interface:

// Split-view interface for group management
// Left panel: Group list
// Right panel: Group editor
 
const DepartmentGroupManager = () => {
  // Auto-navigation to first group
  useEffect(() => {
    if (!isDeptGroupListLoading && departmentGroup) {
      const filteredDepartmentGroup = departmentGroup
        .filter((d) => d.status === activeTab)
        .sort((a, b) => (a?.name?.toLowerCase() > b?.name?.toLowerCase() ? 1 : -1));
      
      if (filteredDepartmentGroup.length) {
        const activeDepartments = filteredDepartmentGroup[0];
        history.push('/admin/departmentsgroup/' + activeDepartments.ID);
      }
    }
  }, [location, isDeptGroupListLoading, activeTab]);
};

Component Communication

sequenceDiagram
    participant User
    participant DeptManager
    participant Redux
    participant DeptList
    participant DeptEditor
    
    User->>DeptManager: View Departments
    DeptManager->>Redux: Fetch Departments
    Redux-->>DeptManager: Department Data
    DeptManager->>DeptList: Pass Department Props
    DeptList->>User: Display Departments
    
    User->>DeptList: Click Edit
    DeptList->>DeptManager: Handle Edit Click
    DeptManager->>User: Navigate to Editor
    
    User->>DeptEditor: Modify Department
    DeptEditor->>Redux: Update Department
    Redux-->>DeptEditor: Success
    DeptEditor->>User: Navigate Back

Business Logic and Calculations

Escalation Time Logic

The escalation system allows issues to be automatically escalated to higher-level users after specified time periods:

// Escalation time configuration
const escalationTimeLogic = {
  // Time options available for selection
  timeOptions: [
    { value: -1, label: 'No Escalation' },
    { value: 1, label: '1 hour' },
    { value: 2, label: '2 hours' },
    { value: 24, label: '1 day' },
    { value: 48, label: '2 days' },
    { value: 72, label: '3 days' },
    { value: 168, label: '1 week' },
    // ... more options
  ],
  
  // Validation rules
  rules: {
    // 1. Previous level must have escalation time before next level
    // 2. Cannot skip levels
    // 3. Level 1 always starts escalation chain
    // 4. "No Escalation" stops the chain
  },
  
  // Example configuration:
  // Level 1: Escalate after 2 hours
  // Level 2: Escalate after 1 day
  // Level 3: No escalation (final level)
};

User Level Management

flowchart TD
    A[User Assignment] --> B{Check Existing Assignment}
    B -->|Not Assigned| C[Add to Available Users]
    B -->|Already Assigned| D[Show in Assigned Users]
    
    E[Add User to Level] --> F[Remove from Available]
    F --> G[Add to Level Users]
    G --> H[Update Assigned List]
    
    I[Remove User from Level] --> J[Remove from Level]
    J --> K[Add to Available]
    K --> L[Update Lists]
    
    M[Delete Level] --> N{Users in Level?}
    N -->|Yes| O[Show Warning]
    N -->|No| P[Delete Level]
    P --> Q[Reindex Remaining Levels]

Department Key Validation

const validateDepartmentKey = async (key: string): Promise<ValidationResult> => {
  // 1. Format validation
  const format = /[ `!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/;
  if (format.test(key)) {
    return {
      valid: false,
      error: 'Department key cannot contain special characters'
    };
  }
  
  // 2. Uniqueness check
  const response = await checkKeyAvailability(organization, key);
  if (response.data !== null) {
    return {
      valid: false,
      error: 'Department key already exists'
    };
  }
  
  return { valid: true };
};

Pagination Calculation

Dynamic pagination based on viewport height:

const calculatePaginationLimit = (windowHeight: number): number => {
  const fixedHeights = {
    rootPadding: 40,
    headerHeight: 30,
    headerMargin: 36,
    tabHeight: 20,
    tabMargin: 24,
    tableHeader: 34,
    pagination: 30,
    paginationMargin: 20
  };
  
  const totalFixed = Object.values(fixedHeights).reduce((sum, height) => sum + height, 0);
  const itemHeight = 30;
  const itemPadding = 3;
  
  return Math.ceil((windowHeight - totalFixed) / (itemHeight + itemPadding));
};

Supervisor Replacement Logic

When deleting a user who is a supervisor in sites:

const handleSupervisorReplacement = async (
  departmentID: string,
  previousSupervisor: string,
  replacementSupervisor: string,
  siteID: string
) => {
  // 1. Check if user is supervisor in any sites
  const supervisorSites = await checkUserSupervisorRole(previousSupervisor);
  
  // 2. If supervisor, show replacement modal
  if (supervisorSites.length > 0) {
    const replacement = await showReplacementModal(supervisorSites);
    
    // 3. Call replacement API
    await replaceSupervisors({
      departmentID,
      previousSupervisor,
      replacementSupervisor: replacement.uid,
      siteID: replacement.siteID
    });
  }
  
  // 4. Remove user from department
  removeUserFromDepartment(departmentID, previousSupervisor);
};

Routing and Navigation

Route Configuration

Route PathComponentPurposeAccess Control
/admin/departmentsDepartmentsPageDepartment listingADMIN_DEPARTMENT_ALL
/admin/departments/newDepartmentEditorPageCreate new departmentADMIN_DEPARTMENT_ALL
/admin/departments/:departmentKeyDepartmentEditorPageEdit existing departmentADMIN_DEPARTMENT_ALL
/admin/departmentsgroupDepartmentsGroupContainerDepartment groups listingADMIN_DEPARTMENT_ALL
/admin/departmentsgroup/:deptIdDepartmentsGroupContainerView/Edit department groupADMIN_DEPARTMENT_ALL
graph LR
    A[Department List] -->|Create| B[New Department]
    A -->|Edit| C[Edit Department]
    A -->|Groups Tab| D[Department Groups]
    
    B -->|Save| A
    B -->|Cancel| A
    
    C -->|Save| A
    C -->|Cancel| A
    
    D -->|Departments Tab| A
    D -->|Create Group| E[New Group Editor]
    D -->|Edit Group| F[Edit Group Editor]
    
    E -->|Save| D
    F -->|Save| D

Route Guards

// Route protection using RouteWithValidation component
<Route
  exact
  path="/admin/departments"
  component={DepartmentsPage}
  withValidation
  access={RoleResources.ADMIN_DEPARTMENT_ALL}
/>
 
// Access control checks:
// 1. User authentication
// 2. Role-based permissions
// 3. Feature flags
// 4. Organization settings

User Interface and Interactions

Department List Interface

┌─────────────────────────────────────────────────────────────┐
│ Department Manager                                          │
├─────────────────────────────────────────────────────────────┤
│ [Search...] [🔍]                      [+ Add Department]    │
├─────────────────────────────────────────────────────────────┤
│ Departments | Department Groups                             │
├─────────────────────────────────────────────────────────────┤
│ Active | Inactive                                           │
├─────────────────────────────────────────────────────────────┤
│ Key ↓ | Name ↓ | Description | Email | Actions             │
├─────────────────────────────────────────────────────────────┤
│ SALES | Sales  | Sales team  | s@co  | [✏️] [🚫]           │
│ MKTG  | Market | Marketing   | m@co  | [✏️] [🚫]           │
├─────────────────────────────────────────────────────────────┤
│                    < 1 2 3 ... 10 >                         │
└─────────────────────────────────────────────────────────────┘

Department Editor Interface

┌─────────────────────────────────────────────────────────────┐
│ Sales Department                                    [Save]  │
├─────────────────────────────────────────────────────────────┤
│ Department Details                   Assignments            │
│ ┌─────────────────────┐             ┌─────────────────────┐│
│ │Name: [Sales Dept  ]│             │Issue Owner:         ││
│ │Desc: [Sales team  ]│             │[Select user... ▼]  ││
│ │Email: [sales@co   ]│             │                     ││
│ │Key: [SALES] [Check]│             │Sites:               ││
│ └─────────────────────┘             │[Select sites... ▼] ││
│                                     │┌─────────────────┐  ││
│ Escalation Configuration            ││Site A      [x] │  ││
│ ┌─────────────────────┐             ││Site B      [x] │  ││
│ │Level-1 → □ Notify:  │             │└─────────────────┘  ││
│ │  [Select users ▼]   │             │                     ││
│ │  User A [x]         │             │Questionnaires:      ││
│ │  ↓ After [2 hours▼]│             │[Select... ▼]        ││
│ │Level-2 → □ Notify:  │             │┌─────────────────┐  ││
│ │  [Select users ▼]   │             ││Quest A     [x] │  ││
│ │  User B [x]         │             ││Quest B     [x] │  ││
│ └─────────────────────┘             │└─────────────────┘  ││
│ [+ Add Level]                       └─────────────────────┘│
└─────────────────────────────────────────────────────────────┘

Interaction Patterns

  1. Search and Filter:

    • Real-time search with 1-second debounce
    • Filters apply to key, name, description, email
    • Maintains filter state during navigation
  2. Sorting:

    • Click column headers to sort
    • Toggle between ascending/descending
    • Visual indicators for active sort
  3. Pagination:

    • Dynamic items per page based on viewport
    • Maintains page state in Redux
    • Smooth navigation between pages
  4. Form Validation:

    • Real-time field validation
    • Visual feedback (green/red borders)
    • Inline error messages
    • Submit button disabled until valid
  5. Bulk Operations:

    • Excel template download
    • Drag-and-drop file upload
    • Progress indicators
    • Error reporting with line numbers

Responsive Design

// Mobile-first responsive patterns
const ResponsiveStyles = {
  // Mobile (< 700px)
  mobile: {
    layout: 'vertical',
    navigation: 'bottom tabs',
    forms: 'full width',
    modals: 'full screen'
  },
  
  // Tablet (700px - 992px)
  tablet: {
    layout: 'vertical with margins',
    navigation: 'top tabs',
    forms: 'centered with padding',
    modals: 'centered dialog'
  },
  
  // Desktop (> 992px)
  desktop: {
    layout: 'horizontal split view',
    navigation: 'top tabs',
    forms: 'two-column layout',
    modals: 'centered dialog'
  }
};

Technical Implementation Details

Performance Optimizations

  1. Lazy Loading:
const DepartmentManager = lazy(() => import('../components/departments/DepartmentManager'));
const DepartmentEditor = lazy(() => import('../components/departments/DepartmentEditor/DepartmentEditor'));
  1. Memoization:
const filteredDepartmentGroup = useMemo(() => {
  if (!departmentGroup) return [];
  if (!departmentGroupFilter) {
    return departmentGroup.filter(d => d.status === activeTab);
  }
  // Complex filtering logic cached
}, [departmentGroup, departmentGroupFilter, activeTab]);
  1. Debounced Search:
const dispatchFilterInputChange = debounce((value) => 
  props.setDepartmentsFilterQuery(value), 1000
);
  1. Session Storage Caching:
const deps = checkExpiredStorageItem<any>(DEPARTMENTS);
if (!deps) {
  // Fetch from API
} else {
  // Use cached data
}

Error Handling

// Comprehensive error handling pattern
const handleDepartmentOperation = async () => {
  try {
    // Pre-validation
    if (!validateInput()) {
      throw new ValidationError('Invalid input');
    }
    
    // API call
    const result = await apiCall();
    
    // Success handling
    toast.success('Operation successful');
    return result;
    
  } catch (error) {
    // Error classification
    if (error instanceof ValidationError) {
      setValidationErrors(error.errors);
    } else if (error.status === 409) {
      toast.error('Department already exists');
    } else if (error.status === 403) {
      toast.error('Permission denied');
    } else {
      toast.error('An unexpected error occurred');
      console.error('Department operation failed:', error);
    }
    
    // Error tracking
    Monitoring.logEvent('department_operation_error', error);
  }
};

Security Considerations

  1. Authentication:

    • Firebase authentication tokens required
    • Token refresh handled automatically
    • Unauthorized access redirects to login
  2. Authorization:

    • Role-based access control (RBAC)
    • Permission checks at route level
    • UI elements hidden based on permissions
  3. Input Validation:

    • Client-side validation for UX
    • Server-side validation for security
    • XSS prevention through React sanitization
    • SQL injection prevention via parameterized queries
  4. Data Privacy:

    • Sensitive data not stored in localStorage
    • Session storage cleared on logout
    • HTTPS enforced for all API calls

Accessibility Features

// ARIA labels and keyboard navigation
<button
  id="btn_save_dept"
  aria-label="Save department"
  onClick={handleSave}
  disabled={!isValid}
  tabIndex={0}
>
  {t('button.save')}
</button>
 
// Focus management
useEffect(() => {
  if (isModalOpen) {
    modalRef.current?.focus();
  }
}, [isModalOpen]);
 
// Screen reader announcements
<div role="alert" aria-live="polite">
  {error && <span>{error}</span>}
</div>

Testing Considerations

// Component testing patterns
describe('DepartmentEditor', () => {
  it('should validate department key format', () => {
    const { getByLabelText, getByText } = render(<DepartmentEditor />);
    const keyInput = getByLabelText('Department Key');
    
    fireEvent.change(keyInput, { target: { value: 'DEPT@123' } });
    fireEvent.blur(keyInput);
    
    expect(getByText('Department key cannot contain special characters')).toBeInTheDocument();
  });
  
  it('should handle API errors gracefully', async () => {
    mockAPI.createDepartment.mockRejectedValue(new Error('Network error'));
    
    const { getByText } = render(<DepartmentEditor />);
    fireEvent.click(getByText('Save'));
    
    await waitFor(() => {
      expect(getByText('Failed to create department')).toBeInTheDocument();
    });
  });
});

Dependencies and Packages

Core Dependencies

PackageVersionPurpose
react^17.0.2UI framework
redux^4.1.2State management
redux-saga^1.2.1Side effects management
react-redux^7.2.6React-Redux bindings
typesafe-actions^5.1.0Type-safe Redux actions
connected-react-router^6.9.2Redux router integration

UI Libraries

PackageVersionPurpose
styled-components^5.3.3CSS-in-JS styling
react-select^5.2.1Enhanced select components
react-toastify^8.1.0Toast notifications
react-i18next^11.15.1Internationalization

Utility Libraries

PackageVersionPurpose
lodash^4.17.21Utility functions (debounce, cloneDeep)
react-id-generator^3.0.2Unique ID generation
xlsx^0.17.5Excel file processing
@loadable/component^5.15.2Code splitting

Internal Packages

PackagePurpose
@nimbly-technologies/nimbly-commonShared types and enums
config/baseURLAPI endpoint configuration
helpers/apiAPI utility functions
utils/monitoringError tracking and analytics
styles/*Shared styled components

Development Dependencies

PackagePurpose
typescriptType checking
@types/reactReact type definitions
@types/styled-componentsStyled-components types
eslintCode linting
prettierCode formatting

Saga Implementation Details

Department Sagas Deep Dive

The department sagas handle all side effects and asynchronous operations. Here’s a detailed breakdown of key saga implementations:

Fetch Departments Saga

export function* fetchDepartments() {
  try {
    yield put(actions.setLoading(true));
    const authToken = yield call(API.getFirebaseToken);
    
    const options = {
      method: 'GET',
      headers: {
        Authorization: authToken,
      },
    };
    
    // Check session storage cache first
    const deps = checkExpiredStorageItem<any>(DEPARTMENTS);
    if (!deps) {
      // Fetch fresh data from API
      const fetchDepartmetsURL = `${apiURL}/departments`;
      const request = () => fetch(fetchDepartmetsURL, options);
      const response = yield call(request);
      
      if (response && response.status === 200) {
        const responseData = yield response.json();
        // Cache the response
        setToSession(responseData, DEPARTMENTS);
        
        // Transform array to mapped object
        const mappingData: { [key: string]: Department } = {};
        responseData.data.forEach((department: Department) => {
          const departmentKey: string = department.departmentID;
          if (!mappingData.hasOwnProperty(departmentKey)) {
            mappingData[departmentKey] = department;
          }
        });
        
        yield put(actions.fetchDepartments.success({ data: mappingData }));
        return mappingData;
      }
    } else {
      // Use cached data
      const responseData = deps;
      const mappingData = transformDepartmentData(responseData.data);
      yield put(actions.fetchDepartments.success({ data: mappingData }));
      return mappingData;
    }
  } catch (e) {
    yield put(actions.fetchDepartments.failure({ error: 'Failed to Fetch Departments' }));
    return null;
  }
}

Paginated Departments Saga

export function* fetchPaginateDepartments(action: ReturnType<typeof actions.fetchPaginateDepartments.request>) {
  try {
    const authToken = yield getToken();
    const state: RootState = yield select(getState);
    const departmentState = state.departments;
    
    // Build sort direction based on current sort field
    let sortDirections = 'asc';
    switch (departmentState.sortBy) {
      case 'name':
        sortDirections = departmentState.name;
        break;
      case 'key':
        sortDirections = departmentState.key;
        break;
      case 'description':
        sortDirections = departmentState.description;
        break;
      case 'email':
        sortDirections = departmentState.email;
        break;
    }
    
    // Build query parameters
    const query = {
      search: departmentState.filterQuery || '',
      page: departmentState.pageIndex,
      sortFields: departmentState.sortBy === 'key' ? 'departmentID' : departmentState.sortBy,
      sortDirections,
      limit: action.payload.limit,
      status: departmentState.selectedTab === 'disabled' ? 'disabled' : 'active'
    };
    
    const queryString = buildQueryString(query);
    const fetchURL = `${apiURL}/departments/paginate?${queryString}`;
    
    const response = yield call(fetch, fetchURL, {
      method: 'GET',
      headers: { Authorization: authToken }
    });
    
    if (response && response.status === 200) {
      const responseData = yield response.json();
      const mappedData = mapPaginatedDepartments(responseData.data.docs);
      
      yield put(actions.fetchPaginateDepartments.success({
        data: mappedData,
        total: responseData.data.totalDocs
      }));
    }
  } catch (e) {
    handleSagaError(e, 'fetchPaginateDepartments');
  }
}

Department Group Saga Implementation

Create Department Group Saga

function* createDepartmentGroup(action: ReturnType<typeof actions.createDepartmentGroup.request>) {
  try {
    // Get current department groups from state
    const departmentGroup: DepartmentGroup[] = yield select(departmentGroupSelector) || [];
    const departmentGroupList: DepartmentGroup[] = cloneDeep(departmentGroup);
    const body = action.payload.data;
    
    // API call to create group
    const authToken = yield API.getFirebaseToken();
    const url = `${apiURL}/departments/group`;
    const response = yield call(API.post, url, authToken, body);
    const responseData = yield call(response.json.bind(response));
    
    if (response && response.status === APIResponse.CODE.SUCCESS) {
      // Add new group to list
      const groupDetail: DepartmentGroup = responseData.data;
      groupDetail.ID = responseData.data.ID;
      departmentGroupList.push(groupDetail);
      
      // Update state and navigate
      yield put(actions.createDepartmentGroup.success({ newData: departmentGroupList }));
      toast.success(i18n.t('message.departmentGroupPage.create.success'));
      yield delay(1000); // Allow toast to be visible
      yield put(push('/admin/departmentsgroup/' + groupDetail.ID));
    } else {
      handleGroupCreationError(response, responseData);
    }
  } catch (e) {
    yield call(handleSagaError, e, 'createDepartmentGroup');
  }
}

Component Props and Interfaces

DepartmentManager Props

interface DepartmentManagerProps {
  // Redux State
  auth: FirebaseAuth;
  profile: UserProfile;
  isLoading: boolean;
  isShowDepartmentBulkUpload: boolean;
  departments: { [key: string]: Department };
  users: { [key: string]: User };
  access: PermissionSet;
  activeTab: 'published' | 'disabled';
  filterQuery: string;
  sortBy: string;
  pageIndex: number;
  totalDepartments: number;
  departmentSchedule: Schedule[];
  organization: Organization;
  
  // Redux Actions
  fetchDepartments: (payload?: void) => void;
  fetchUsers: (payload?: void) => void;
  clearDepartmentsFilterQuery: () => void;
  setDepartmentsFilterQuery: (text: string) => void;
  setShowDepartmentBulkModal: (show: boolean) => void;
  push: (path: string) => void;
  setDimissDepartmentDeleteModal: () => void;
  setSelectedTab: (tab: string) => void;
  fetchPaginateDepartments: (payload: { limit: number }) => void;
  setPageIndex: (index: number) => void;
  fetchDepartmentSchedule: () => void;
  fetchDepartmentIndexById: (payload: { deptID: string }) => void;
}

DepartmentEditor Props

interface DepartmentEditorProps {
  // Data Props
  departments: { [key: string]: Department } | null;
  users: { [key: string]: User };
  sites: { [key: string]: Site };
  questionnaires: { [key: string]: Questionnaire };
  deptData: DepartmentIndex | null;
  deptDataWithRoles: DepartmentIndexUserInSite | null;
  
  // State Props
  isLoading: boolean;
  isSuccess: boolean;
  apiError: string | null;
  errorData: string[] | null;
  organization: string;
  pathname: string;
  
  // Feature Flags
  featureAccessIssueEscalation: boolean;
  tutorial: TutorialState;
  
  // Actions
  fetchDeptById: (payload: { deptID: string }) => void;
  fetchDeptWithRolesById: (payload: { deptID: string }) => void;
  onCreateDepartment: (payload: { data: DepartmentIndex }) => void;
  onUpdateDepartment: (payload: { deptID: string; data: DepartmentIndex; assignSupervisor: any[] }) => void;
  clearDepartmentIndex: () => void;
  clearDepartmentsByRoles: () => void;
  fetchSites: () => void;
  fetchDepartments: () => void;
  fetchUsers: () => void;
  setShowModal: (show: boolean) => void;
  setIsSuccess: (success: boolean) => void;
}

DepartmentGroupManager Props

interface DepartmentGroupManagerProps {
  // State
  activeTab: 'active' | 'disabled';
  departmentGroup: DepartmentGroup[] | null;
  access: PermissionSet;
  isDepartmentGroup: boolean;
  deptGroupFilter: string;
  userRole: string;
  organization: Organization;
  
  // Actions
  fetchDepartmentGroup: () => void;
  fetchUsers: () => void;
  clearDepartmentsGroupFilter: (text: string) => void;
  setDepartmentsGroupFilter: (text: string) => void;
  setActiveTab: (tab: ListTabOptions) => void;
}

Advanced Business Logic

Escalation Time Calculation Engine

The escalation system includes sophisticated logic for calculating and managing time-based escalations:

class EscalationEngine {
  private escalationConfig: { [level: number]: number | null };
  private currentTime: Date;
  
  constructor(config: { [level: number]: number | null }) {
    this.escalationConfig = config;
    this.currentTime = new Date();
  }
  
  // Calculate when an issue should be escalated
  calculateEscalationTime(issueCreatedAt: Date, currentLevel: number): Date | null {
    const hoursToEscalate = this.escalationConfig[currentLevel];
    
    if (hoursToEscalate === null || hoursToEscalate === -1) {
      return null; // No escalation
    }
    
    const escalationTime = new Date(issueCreatedAt);
    escalationTime.setHours(escalationTime.getHours() + hoursToEscalate);
    
    return escalationTime;
  }
  
  // Determine if escalation should occur
  shouldEscalate(issueCreatedAt: Date, currentLevel: number): boolean {
    const escalationTime = this.calculateEscalationTime(issueCreatedAt, currentLevel);
    
    if (!escalationTime) return false;
    
    return this.currentTime >= escalationTime;
  }
  
  // Get next escalation level
  getNextLevel(currentLevel: number): number | null {
    const levels = Object.keys(this.escalationConfig)
      .map(Number)
      .sort((a, b) => a - b);
    
    const currentIndex = levels.indexOf(currentLevel);
    
    if (currentIndex === -1 || currentIndex === levels.length - 1) {
      return null; // No next level
    }
    
    return levels[currentIndex + 1];
  }
  
  // Validate escalation configuration
  validateConfig(): ValidationResult {
    const levels = Object.keys(this.escalationConfig).map(Number).sort((a, b) => a - b);
    const errors: string[] = [];
    
    // Check for gaps in levels
    for (let i = 1; i < levels.length; i++) {
      if (levels[i] !== levels[i - 1] + 1) {
        errors.push(`Gap found between level ${levels[i - 1]} and ${levels[i]}`);
      }
    }
    
    // Check for invalid escalation chains
    let foundNoEscalation = false;
    for (const level of levels) {
      if (foundNoEscalation && this.escalationConfig[level] !== null) {
        errors.push(`Cannot have escalation after "No Escalation" at level ${level}`);
      }
      if (this.escalationConfig[level] === -1) {
        foundNoEscalation = true;
      }
    }
    
    return {
      valid: errors.length === 0,
      errors
    };
  }
}

Department Assignment Algorithm

The system uses a sophisticated algorithm for managing department assignments:

class DepartmentAssignmentManager {
  private departments: Map<string, Department>;
  private users: Map<string, User>;
  private sites: Map<string, Site>;
  
  // Assign user to department with conflict resolution
  assignUserToDepartment(
    userId: string,
    departmentId: string,
    level: number
  ): AssignmentResult {
    // Check if user exists
    const user = this.users.get(userId);
    if (!user) {
      return { success: false, error: 'User not found' };
    }
    
    // Check if user is already assigned to another department at same level
    const existingAssignment = this.findUserDepartmentAssignment(userId, level);
    if (existingAssignment && existingAssignment.departmentId !== departmentId) {
      return {
        success: false,
        error: `User already assigned to ${existingAssignment.departmentName} at level ${level}`,
        conflict: existingAssignment
      };
    }
    
    // Perform assignment
    this.departments.get(departmentId)?.users.push({ uid: userId, level });
    
    return { success: true };
  }
  
  // Bulk assign sites to department
  bulkAssignSites(departmentId: string, siteIds: string[]): BulkAssignmentResult {
    const results: AssignmentResult[] = [];
    const department = this.departments.get(departmentId);
    
    if (!department) {
      return {
        success: false,
        error: 'Department not found',
        results: []
      };
    }
    
    for (const siteId of siteIds) {
      const site = this.sites.get(siteId);
      if (!site) {
        results.push({
          siteId,
          success: false,
          error: 'Site not found'
        });
        continue;
      }
      
      // Check for circular dependencies
      if (this.wouldCreateCircularDependency(departmentId, siteId)) {
        results.push({
          siteId,
          success: false,
          error: 'Would create circular dependency'
        });
        continue;
      }
      
      // Assign site
      department.sites.push({ id: siteId });
      results.push({ siteId, success: true });
    }
    
    return {
      success: results.every(r => r.success),
      results
    };
  }
  
  // Check for circular dependencies in department-site relationships
  private wouldCreateCircularDependency(
    departmentId: string,
    siteId: string
  ): boolean {
    // Implementation of circular dependency detection
    // using depth-first search algorithm
    const visited = new Set<string>();
    const stack = new Set<string>();
    
    const hasCycle = (deptId: string): boolean => {
      visited.add(deptId);
      stack.add(deptId);
      
      const dept = this.departments.get(deptId);
      if (!dept) return false;
      
      for (const site of dept.sites) {
        // Check site's departments
        const siteDepts = this.getSiteDepartments(site.id);
        for (const siteDept of siteDepts) {
          if (!visited.has(siteDept)) {
            if (hasCycle(siteDept)) return true;
          } else if (stack.has(siteDept)) {
            return true; // Cycle detected
          }
        }
      }
      
      stack.delete(deptId);
      return false;
    };
    
    return hasCycle(departmentId);
  }
}

Validation Engine

Comprehensive validation engine for department operations:

class DepartmentValidationEngine {
  private validators: Map<string, ValidationRule>;
  
  constructor() {
    this.initializeValidators();
  }
  
  private initializeValidators() {
    this.validators = new Map([
      ['departmentName', {
        validate: (value: string) => {
          if (!value || value.trim().length === 0) {
            return { valid: false, error: 'Department name is required' };
          }
          if (value.length < 3) {
            return { valid: false, error: 'Department name must be at least 3 characters' };
          }
          if (value.length > 100) {
            return { valid: false, error: 'Department name must be less than 100 characters' };
          }
          return { valid: true };
        }
      }],
      ['departmentKey', {
        validate: (value: string) => {
          const format = /^[a-zA-Z0-9-]+$/;
          if (!value) {
            return { valid: false, error: 'Department key is required' };
          }
          if (!format.test(value)) {
            return { valid: false, error: 'Department key can only contain letters, numbers, and hyphens' };
          }
          if (value.length < 2) {
            return { valid: false, error: 'Department key must be at least 2 characters' };
          }
          if (value.length > 50) {
            return { valid: false, error: 'Department key must be less than 50 characters' };
          }
          return { valid: true };
        }
      }],
      ['email', {
        validate: (value: string) => {
          const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
          if (!value) {
            return { valid: false, error: 'Email is required' };
          }
          if (!emailRegex.test(value)) {
            return { valid: false, error: 'Invalid email format' };
          }
          return { valid: true };
        }
      }],
      ['userAssignments', {
        validate: (users: Array<{ uid: string; level: number }>) => {
          if (!users || users.length === 0) {
            return { valid: false, error: 'At least one user must be assigned' };
          }
          
          // Check for duplicate assignments
          const userLevels = new Map<string, number[]>();
          for (const user of users) {
            if (!userLevels.has(user.uid)) {
              userLevels.set(user.uid, []);
            }
            userLevels.get(user.uid)!.push(user.level);
          }
          
          for (const [uid, levels] of userLevels.entries()) {
            if (levels.length !== new Set(levels).size) {
              return {
                valid: false,
                error: `User ${uid} is assigned to the same level multiple times`
              };
            }
          }
          
          return { valid: true };
        }
      }]
    ]);
  }
  
  // Validate entire department object
  validateDepartment(department: Partial<DepartmentIndex>): ValidationResult {
    const errors: ValidationError[] = [];
    
    // Validate each field
    for (const [field, validator] of this.validators.entries()) {
      const value = department[field as keyof DepartmentIndex];
      const result = validator.validate(value);
      
      if (!result.valid) {
        errors.push({
          field,
          message: result.error,
          severity: 'error'
        });
      }
    }
    
    // Custom validations
    errors.push(...this.performCustomValidations(department));
    
    return {
      valid: errors.length === 0,
      errors
    };
  }
  
  private performCustomValidations(department: Partial<DepartmentIndex>): ValidationError[] {
    const errors: ValidationError[] = [];
    
    // Validate escalation configuration
    if (department.escalateTime) {
      const escalationEngine = new EscalationEngine(department.escalateTime);
      const escalationValidation = escalationEngine.validateConfig();
      
      if (!escalationValidation.valid) {
        errors.push(...escalationValidation.errors.map(error => ({
          field: 'escalateTime',
          message: error,
          severity: 'error' as const
        })));
      }
    }
    
    // Validate issue owner is in assigned users
    if (department.defaultIssueOwner && department.users) {
      const isOwnerAssigned = department.users.some(
        user => user.uid === department.defaultIssueOwner
      );
      
      if (!isOwnerAssigned) {
        errors.push({
          field: 'defaultIssueOwner',
          message: 'Default issue owner must be an assigned user',
          severity: 'warning'
        });
      }
    }
    
    return errors;
  }
}

UI/UX Patterns and Interactions

Advanced Search and Filter Implementation

The department module implements a sophisticated search and filter system:

class DepartmentSearchEngine {
  private searchIndex: Map<string, SearchDocument>;
  private filters: FilterSet;
  
  constructor() {
    this.searchIndex = new Map();
    this.filters = new FilterSet();
  }
  
  // Build search index for fast searching
  buildSearchIndex(departments: Department[]) {
    departments.forEach(dept => {
      const searchDoc: SearchDocument = {
        id: dept.departmentID,
        tokens: this.tokenize([
          dept.name,
          dept.description,
          dept.email,
          dept.departmentID
        ].join(' ')),
        metadata: {
          status: dept.status,
          createdAt: dept.createdAt,
          userCount: dept.users?.length || 0,
          siteCount: dept.sites?.length || 0
        }
      };
      
      this.searchIndex.set(dept.departmentID, searchDoc);
    });
  }
  
  // Perform search with relevance scoring
  search(query: string, filters?: FilterCriteria): SearchResult[] {
    const queryTokens = this.tokenize(query.toLowerCase());
    const results: SearchResult[] = [];
    
    for (const [id, doc] of this.searchIndex.entries()) {
      // Apply filters first
      if (filters && !this.matchesFilters(doc, filters)) {
        continue;
      }
      
      // Calculate relevance score
      const score = this.calculateRelevanceScore(queryTokens, doc.tokens);
      
      if (score > 0) {
        results.push({
          departmentId: id,
          score,
          highlights: this.generateHighlights(queryTokens, doc)
        });
      }
    }
    
    // Sort by relevance score
    return results.sort((a, b) => b.score - a.score);
  }
  
  private calculateRelevanceScore(queryTokens: string[], docTokens: string[]): number {
    let score = 0;
    const docTokenSet = new Set(docTokens);
    
    for (const queryToken of queryTokens) {
      if (docTokenSet.has(queryToken)) {
        score += 1; // Exact match
      } else {
        // Fuzzy matching
        for (const docToken of docTokens) {
          const similarity = this.calculateSimilarity(queryToken, docToken);
          if (similarity > 0.8) {
            score += similarity;
            break;
          }
        }
      }
    }
    
    return score;
  }
  
  private calculateSimilarity(str1: string, str2: string): number {
    // Levenshtein distance implementation
    const matrix: number[][] = [];
    
    for (let i = 0; i <= str2.length; i++) {
      matrix[i] = [i];
    }
    
    for (let j = 0; j <= str1.length; j++) {
      matrix[0][j] = j;
    }
    
    for (let i = 1; i <= str2.length; i++) {
      for (let j = 1; j <= str1.length; j++) {
        if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
          matrix[i][j] = matrix[i - 1][j - 1];
        } else {
          matrix[i][j] = Math.min(
            matrix[i - 1][j - 1] + 1,
            matrix[i][j - 1] + 1,
            matrix[i - 1][j] + 1
          );
        }
      }
    }
    
    const distance = matrix[str2.length][str1.length];
    return 1 - (distance / Math.max(str1.length, str2.length));
  }
}

Drag and Drop Implementation

The department group editor implements drag-and-drop functionality:

class DragDropManager {
  private draggedItem: DragItem | null = null;
  private dropZones: Map<string, DropZone> = new Map();
  
  initializeDragDrop() {
    // Set up drag event listeners
    document.addEventListener('dragstart', this.handleDragStart.bind(this));
    document.addEventListener('dragend', this.handleDragEnd.bind(this));
    document.addEventListener('dragover', this.handleDragOver.bind(this));
    document.addEventListener('drop', this.handleDrop.bind(this));
  }
  
  private handleDragStart(e: DragEvent) {
    const target = e.target as HTMLElement;
    
    if (target.dataset.draggable === 'department') {
      this.draggedItem = {
        type: 'department',
        id: target.dataset.departmentId!,
        data: JSON.parse(target.dataset.departmentData!)
      };
      
      // Visual feedback
      target.classList.add('dragging');
      e.dataTransfer!.effectAllowed = 'move';
      
      // Store drag data
      e.dataTransfer!.setData('text/plain', JSON.stringify(this.draggedItem));
    }
  }
  
  private handleDragOver(e: DragEvent) {
    e.preventDefault();
    
    const dropZone = this.findDropZone(e.target as HTMLElement);
    if (dropZone && this.canDrop(this.draggedItem!, dropZone)) {
      e.dataTransfer!.dropEffect = 'move';
      dropZone.element.classList.add('drag-over');
    } else {
      e.dataTransfer!.dropEffect = 'none';
    }
  }
  
  private handleDrop(e: DragEvent) {
    e.preventDefault();
    
    const dropZone = this.findDropZone(e.target as HTMLElement);
    if (dropZone && this.draggedItem) {
      const success = this.processDrop(this.draggedItem, dropZone);
      
      if (success) {
        // Animate the drop
        this.animateDrop(this.draggedItem, dropZone);
        
        // Update the UI
        this.updateUI(this.draggedItem, dropZone);
      }
    }
    
    this.cleanup();
  }
  
  private canDrop(item: DragItem, zone: DropZone): boolean {
    // Validate drop rules
    if (item.type === 'department' && zone.type === 'departmentGroup') {
      // Check if department is already in group
      const group = zone.data as DepartmentGroup;
      return !group.departments.some(d => d.id === item.id);
    }
    
    return false;
  }
  
  private animateDrop(item: DragItem, zone: DropZone) {
    const itemElement = document.querySelector(`[data-department-id="${item.id}"]`);
    const zoneRect = zone.element.getBoundingClientRect();
    
    if (itemElement) {
      // Create clone for animation
      const clone = itemElement.cloneNode(true) as HTMLElement;
      clone.style.position = 'fixed';
      clone.style.transition = 'all 0.3s ease-out';
      clone.style.zIndex = '9999';
      
      document.body.appendChild(clone);
      
      // Animate to drop zone
      requestAnimationFrame(() => {
        clone.style.left = `${zoneRect.left}px`;
        clone.style.top = `${zoneRect.top}px`;
        clone.style.opacity = '0';
        clone.style.transform = 'scale(0.8)';
      });
      
      // Remove clone after animation
      setTimeout(() => clone.remove(), 300);
    }
  }
}

Real-time Validation Feedback

The module provides sophisticated real-time validation feedback:

class ValidationFeedbackManager {
  private validators: Map<string, FieldValidator>;
  private debounceTimers: Map<string, NodeJS.Timeout>;
  
  constructor() {
    this.validators = new Map();
    this.debounceTimers = new Map();
  }
  
  // Register field validator
  registerValidator(fieldName: string, validator: FieldValidator) {
    this.validators.set(fieldName, validator);
  }
  
  // Validate field with debounce
  validateField(fieldName: string, value: any, immediate = false) {
    // Clear existing timer
    if (this.debounceTimers.has(fieldName)) {
      clearTimeout(this.debounceTimers.get(fieldName)!);
    }
    
    const validate = () => {
      const validator = this.validators.get(fieldName);
      if (!validator) return;
      
      const result = validator.validate(value);
      this.updateFieldUI(fieldName, result);
      
      // Trigger dependent validations
      if (validator.dependencies) {
        validator.dependencies.forEach(dep => {
          const depValue = this.getFieldValue(dep);
          this.validateField(dep, depValue, true);
        });
      }
    };
    
    if (immediate) {
      validate();
    } else {
      // Debounce validation
      const timer = setTimeout(validate, validator.debounceMs || 500);
      this.debounceTimers.set(fieldName, timer);
    }
  }
  
  private updateFieldUI(fieldName: string, result: ValidationResult) {
    const field = document.querySelector(`[data-field="${fieldName}"]`) as HTMLElement;
    if (!field) return;
    
    const input = field.querySelector('input, select, textarea') as HTMLElement;
    const errorContainer = field.querySelector('.error-message') as HTMLElement;
    
    if (result.valid) {
      // Success state
      input.classList.remove('error');
      input.classList.add('success');
      
      if (errorContainer) {
        errorContainer.style.opacity = '0';
        setTimeout(() => {
          errorContainer.textContent = '';
        }, 200);
      }
      
      // Show success icon
      this.showSuccessIcon(field);
    } else {
      // Error state
      input.classList.remove('success');
      input.classList.add('error');
      
      if (errorContainer) {
        errorContainer.textContent = result.error || '';
        errorContainer.style.opacity = '1';
      }
      
      // Shake animation
      this.shakeField(field);
    }
  }
  
  private showSuccessIcon(field: HTMLElement) {
    const existingIcon = field.querySelector('.success-icon');
    if (existingIcon) return;
    
    const icon = document.createElement('span');
    icon.className = 'success-icon';
    icon.innerHTML = '✓';
    icon.style.cssText = `
      position: absolute;
      right: 10px;
      top: 50%;
      transform: translateY(-50%);
      color: #3bd070;
      font-weight: bold;
      opacity: 0;
      transition: opacity 0.3s ease;
    `;
    
    field.appendChild(icon);
    
    requestAnimationFrame(() => {
      icon.style.opacity = '1';
    });
  }
  
  private shakeField(field: HTMLElement) {
    field.classList.add('shake');
    setTimeout(() => field.classList.remove('shake'), 500);
  }
}

Error Handling Scenarios

Comprehensive Error Handler

class DepartmentErrorHandler {
  private errorMappings: Map<string, ErrorHandler>;
  private errorLog: ErrorLogEntry[];
  
  constructor() {
    this.initializeErrorMappings();
    this.errorLog = [];
  }
  
  private initializeErrorMappings() {
    this.errorMappings = new Map([
      ['NETWORK_ERROR', {
        handler: (error: Error) => {
          // Check if offline
          if (!navigator.onLine) {
            return {
              message: 'You are offline. Please check your internet connection.',
              severity: 'warning',
              actions: [
                {
                  label: 'Retry',
                  action: () => window.location.reload()
                }
              ]
            };
          }
          
          // Network timeout
          if (error.message.includes('timeout')) {
            return {
              message: 'The request timed out. Please try again.',
              severity: 'error',
              actions: [
                {
                  label: 'Retry',
                  action: () => this.retryLastAction()
                }
              ]
            };
          }
          
          return {
            message: 'A network error occurred. Please try again.',
            severity: 'error'
          };
        }
      }],
      ['VALIDATION_ERROR', {
        handler: (error: ValidationError) => {
          const fieldErrors = error.errors.map(e => `${e.field}: ${e.message}`).join('\n');
          
          return {
            message: 'Please fix the following errors:\n' + fieldErrors,
            severity: 'error',
            persistent: true
          };
        }
      }],
      ['PERMISSION_ERROR', {
        handler: (error: Error) => {
          return {
            message: 'You do not have permission to perform this action.',
            severity: 'error',
            actions: [
              {
                label: 'Request Access',
                action: () => this.requestAccess()
              }
            ]
          };
        }
      }],
      ['CONFLICT_ERROR', {
        handler: (error: ConflictError) => {
          if (error.type === 'DUPLICATE_KEY') {
            return {
              message: `A department with key "${error.conflictingValue}" already exists.`,
              severity: 'error',
              actions: [
                {
                  label: 'Choose Different Key',
                  action: () => this.focusField('departmentKey')
                }
              ]
            };
          }
          
          return {
            message: 'A conflict occurred. Please refresh and try again.',
            severity: 'error'
          };
        }
      }]
    ]);
  }
  
  handleError(error: Error, context?: ErrorContext): ErrorResponse {
    // Log error
    this.logError(error, context);
    
    // Determine error type
    const errorType = this.classifyError(error);
    
    // Get appropriate handler
    const handler = this.errorMappings.get(errorType);
    
    if (handler) {
      const response = handler.handler(error);
      
      // Show error to user
      this.showError(response);
      
      // Track error
      this.trackError(error, errorType, context);
      
      return response;
    }
    
    // Fallback error handling
    return this.handleUnknownError(error);
  }
  
  private classifyError(error: Error): string {
    if (error.name === 'NetworkError' || error.message.includes('fetch')) {
      return 'NETWORK_ERROR';
    }
    
    if (error.name === 'ValidationError') {
      return 'VALIDATION_ERROR';
    }
    
    if (error.message.includes('403') || error.message.includes('unauthorized')) {
      return 'PERMISSION_ERROR';
    }
    
    if (error.message.includes('409') || error.message.includes('conflict')) {
      return 'CONFLICT_ERROR';
    }
    
    return 'UNKNOWN_ERROR';
  }
  
  private showError(response: ErrorResponse) {
    if (response.severity === 'warning') {
      toast.warning(response.message, {
        autoClose: response.persistent ? false : 5000,
        closeButton: true
      });
    } else {
      toast.error(response.message, {
        autoClose: response.persistent ? false : 5000,
        closeButton: true,
        action: response.actions?.[0] ? {
          label: response.actions[0].label,
          onClick: response.actions[0].action
        } : undefined
      });
    }
  }
  
  private async trackError(error: Error, type: string, context?: ErrorContext) {
    try {
      await Monitoring.logEvent('department_error', {
        error_type: type,
        error_message: error.message,
        error_stack: error.stack,
        context: context,
        timestamp: new Date().toISOString(),
        user_agent: navigator.userAgent,
        url: window.location.href
      });
    } catch (trackingError) {
      console.error('Failed to track error:', trackingError);
    }
  }
}

This documentation provides a comprehensive technical overview of the Department Module in the Nimbly audit-admin platform. The module demonstrates a well-architected system with clear separation of concerns, robust state management, and thoughtful user experience design. The implementation follows React best practices and maintains consistency with the broader application architecture.

For additional information or specific implementation details, please refer to the source code files referenced throughout this documentation via the GitHub links provided.