Module Overview

The Users Module in the Nimbly Audit Admin system is a comprehensive user management solution that handles all aspects of user lifecycle management including creation, retrieval, updating, and deletion of user accounts. The module is built using React, Redux for state management, and follows a component-based architecture pattern.

Key Features

  • User Management: Complete CRUD operations for managing auditors and other user roles
  • Role-Based Access Control: Granular permission system controlling access to different features
  • Off-Days Management: System for managing auditor non-operational days
  • Bulk Operations: Support for bulk user operations including downloads and updates
  • Analytics Integration: Comprehensive user analytics and reporting capabilities
  • Multi-platform Support: Responsive design supporting both desktop and mobile views

Module Structure

The Users module is organized into several key directories:

src/
├── components/auditors/        # Core auditor components
├── pages/                      # Page-level components
├── routes/                     # Routing configuration
├── services/                   # API services
├── reducers/                   # Redux state management
├── sagas/                      # Redux-Saga side effects
└── constants/                  # Constants and configurations

User List Module

Overview

The User List module provides a comprehensive interface for viewing and managing all users in the system. It supports advanced features like sorting, filtering, pagination, and bulk operations.

Architecture

The User List module follows a container-presenter pattern with clear separation of concerns:

graph TD
    A[AuditorsPage Component] --> B[AuditorManager Component]
    B --> C[AuditorList Container]
    C --> D[AuditorListTable Desktop View]
    C --> E[AuditorListCard Mobile View]
    B --> F[AuditorListHeader]
    F --> G[Search Component]
    F --> H[Filter Components]
    F --> I[Bulk Action Components]
    
    J[Redux Store] --> K[Auditors Reducer]
    J --> L[Users Reducer]
    J --> M[Permissions Reducer]
    
    N[API Services] --> O[User Service]
    N --> P[Auditor Service]
    
    K --> C
    L --> C
    M --> C
    O --> K
    P --> K

Implementation Details

1. Main Components

AuditorsPage Component (src/pages/auditors.js:1-40)

The main page component that serves as the entry point for the auditors module. It manages the overall layout and modal states:

class AuditorsPage extends React.Component {
  render() {
    const { modalVisible, bulkModal } = this.props;
    return (
      <React.Fragment>
        {modalVisible ? (
          <Suspense fallback={<div />}>
            <AuditorEditor />
          </Suspense>
        ) : null}
        {bulkModal ? (
          <Suspense fallback={<div />}>
            <AuditorBulkModal />
          </Suspense>
        ) : null}
        <Layout>
          <Suspense fallback={<div />}>
            <AuditorManager />
          </Suspense>
        </Layout>
      </React.Fragment>
    );
  }
}

Key features:

  • Lazy loading of components for performance optimization
  • Modal management for editor and bulk operations
  • Integration with Redux for state management

AuditorListContainer Component (src/components/auditors/AuditorList/AuditorListContainer.tsx:1-204)

The container component that manages the presentation logic and data flow:

const AuditorListContainer = (props: AuditorListContainerProps) => {
  const { auditorProcessedUsers, auditorIndex, displayedItem, totalUsers, isLoading, setPageIndex, pageIndex } = props;
  const slicedProcessedUsers = cloneDeep(auditorProcessedUsers).splice(auditorIndex, displayedItem);
 
  return (
    <Root>
      <AuditorListTable {...tableProps} />
      <AuditorListCard {...cardProps} />
      <PaginationContainer>
        {!isLoading && Object.keys(props.users).length > 0 ? (
          <Pagination
            page="auditor"
            currentIndex={pageIndex}
            totalItem={totalUsers!}
            listPerPage={props.itemListLimit}
            onChange={(index: number) => setPageIndex(index)}
          />
        ) : null}
      </PaginationContainer>
    </Root>
  );
};

Key responsibilities:

  • Data slicing for pagination
  • Responsive view management (desktop vs mobile)
  • Pagination control

AuditorListTable Component (src/components/auditors/AuditorList/AuditorListTable.tsx:1-431)

The table component for desktop view with comprehensive user display:

const AuditorListTable = (props: AuditorListTableProps) => {
  const { t } = useTranslation();
  const history = useHistory();
  const [selectedRow, setSelectedRow] = useState(null);
 
  // Sorting functionality
  const handleSort = (column: string) => {
    props.sortType(column);
  };
 
  // Row click handler
  const handleRowClick = (user: any, role: string) => {
    setSelectedRow(user);
    history.push({
      pathname: `/admin/auditors/${user}`,
    });
  };
 
  // Table rendering with columns:
  // - Checkbox (for bulk operations)
  // - Avatar
  // - Email/Username
  // - Display Name
  // - Phone Number
  // - Role
  // - Status
  // - WhatsApp Number
  // - WhatsApp Notification Type
  // - Actions (Edit/Block)
};

2. State Management

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

Auditors Reducer (src/reducers/auditors/auditors.reducer.ts)

const initialState = {
  users: {},
  totalUsers: null,
  isLoading: false,
  error: null,
  modalVisible: false,
  bulkModal: false,
  selectedUsers: {},
  filters: {
    status: 'all',
    role: 'all',
    search: '',
    sortBy: 'email',
    sortDirection: 'asc'
  },
  pagination: {
    pageIndex: 0,
    itemsPerPage: 20
  }
};

Actions and Action Types (src/reducers/auditors/auditors.actions.ts)

Key actions include:

  • FETCH_AUDITORS_REQUEST: Initiate user fetch
  • FETCH_AUDITORS_SUCCESS: Store fetched users
  • FETCH_AUDITORS_FAILURE: Handle fetch errors
  • UPDATE_AUDITOR_FILTER: Update filter criteria
  • SELECT_AUDITOR_FOR_BULK: Select users for bulk operations
  • CLEAR_AUDITOR_SELECTION: Clear bulk selections

3. API Integration

User Service (src/services/user.api.ts)

The service layer handles all HTTP requests for user operations:

export const userService = {
  // Fetch all users with pagination and filters
  getUsers: async (params: UserQueryParams) => {
    const { page, limit, sortBy, sortDirection, filter, search } = params;
    const response = await axios.get('/api/v1/users', {
      params: {
        page,
        limit,
        sortBy,
        sortDirection,
        ...filter,
        search
      }
    });
    return response.data;
  },
 
  // Get single user details
  getUser: async (userId: string) => {
    const response = await axios.get(`/api/v1/users/${userId}`);
    return response.data;
  },
 
  // Update user status (block/unblock)
  updateUserStatus: async (userId: string, status: string) => {
    const response = await axios.put(`/api/v1/users/${userId}/status`, { status });
    return response.data;
  }
};

4. Features and Functionality

Sorting

The user list supports sorting by multiple columns:

  • Email/Username
  • Display Name
  • Role
  • Status

Sorting is bidirectional (ascending/descending) and maintains state across pagination.

Filtering

Users can be filtered by:

  • Status: All, Active, Disabled, Fresh
  • Role: All roles, Admin, Supervisor, Auditor, etc.
  • Search: Real-time search across email, name, and phone number
Pagination
  • Server-side pagination for performance
  • Configurable items per page (10, 20, 50, 100)
  • Page navigation with first, previous, next, last controls
  • Current page and total page display
Bulk Operations
  • Select all/individual users
  • Bulk download user data
  • Bulk status updates
  • Bulk role changes
Row Actions

Each user row provides:

  • Edit: Navigate to user edit page (role-based access)
  • Block/Unblock: Toggle user status (role-based access)
  • Click to View: Row click navigates to user details

5. Permissions and Access Control

The User List module implements comprehensive role-based access control:

// Access permissions structure
const access = {
  view: hasPermission(RoleResources.ADMIN_USER_ALL),
  create: hasPermission(RoleResources.ADMIN_USER_CREATE),
  edit: hasPermission(RoleResources.ADMIN_USER_EDIT),
  delete: hasPermission(RoleResources.ADMIN_USER_DELETE)
};

Permission checks are performed at:

  • Component mount (redirect if no access)
  • Action buttons (disable/hide based on permissions)
  • API calls (server-side validation)

6. Performance Optimizations

  1. Lazy Loading: Components are loaded on demand using React.lazy
  2. Virtualization: Large lists use windowing for efficient rendering
  3. Memoization: Expensive computations are memoized using React.memo and useMemo
  4. Debounced Search: Search input is debounced to reduce API calls
  5. Optimistic Updates: UI updates before server confirmation for better UX

7. Mobile Responsiveness

The User List module provides a dedicated mobile view:

AuditorListCard Component (src/components/auditors/AuditorList/AuditorListCard.tsx)

  • Card-based layout for mobile devices
  • Swipe actions for edit/block operations
  • Simplified information display
  • Touch-optimized controls

Data Flow

sequenceDiagram
    participant U as User
    participant C as Component
    participant R as Redux Store
    participant S as Saga
    participant A as API

    U->>C: Navigate to Users Page
    C->>R: Dispatch FETCH_AUDITORS_REQUEST
    R->>S: Trigger fetchAuditorsSaga
    S->>A: GET /api/v1/users
    A-->>S: Return users data
    S->>R: Dispatch FETCH_AUDITORS_SUCCESS
    R-->>C: Update component props
    C-->>U: Render user list
    
    U->>C: Click sort column
    C->>R: Dispatch UPDATE_AUDITOR_FILTER
    R->>S: Trigger fetchAuditorsSaga
    S->>A: GET /api/v1/users (with sort params)
    A-->>S: Return sorted data
    S->>R: Dispatch FETCH_AUDITORS_SUCCESS
    R-->>C: Update component props
    C-->>U: Render sorted list

Error Handling

The User List module implements comprehensive error handling:

  1. Network Errors: Retry mechanisms with exponential backoff
  2. Validation Errors: Client-side validation before API calls
  3. Permission Errors: Graceful degradation of features
  4. Data Errors: Fallback UI for missing or corrupt data
  5. User Feedback: Toast notifications for all operations

Testing

The module includes comprehensive test coverage:

  1. Unit Tests: Component logic and utility functions
  2. Integration Tests: Component interactions and Redux flow
  3. E2E Tests: User workflows and scenarios
  4. Performance Tests: Load testing for large datasets

User Details Module

Overview

The User Details module provides comprehensive functionality for viewing and editing individual user information. It consists of two main implementations: one for modal-based editing (AuditorEditor) and one for full-page editing (auditorEdit). Both share similar functionality but differ in their presentation and navigation patterns.

Architecture

The User Details module follows a sophisticated component hierarchy with clear separation between presentation and business logic:

graph TD
    A[Route: /admin/auditors/:userId] --> B[auditorEdit Page Component]
    A2[Modal Trigger] --> C[AuditorEditor Modal Component]
    
    B --> D[AuditorEditContainer]
    C --> E[AuditorEditorContainer]
    
    D --> F[AuditorEditForm]
    E --> G[AuditorEditorForm]
    
    F --> H[Form Fields Components]
    G --> H
    
    I[Redux Store] --> J[User State]
    I --> K[Departments State]
    I --> L[Permissions State]
    
    M[API Services] --> N[fetchSingleUser]
    M --> O[updateSingleUser]
    M --> P[createNewUser]
    
    Q[Validation Services] --> R[Phone Validation]
    Q --> S[Email Validation]
    Q --> T[Password Validation]

Implementation Details

1. Page-Based User Details (auditorEdit.tsx)

The page-based implementation is used when navigating directly to a user’s details page:

Route: /admin/auditors/:userId or /admin/auditors/new

Key Components:

auditorEdit Component (src/pages/auditorEdit.tsx:1-787)

This is the main container component that manages the entire user editing flow:

export const AuditorEdit = (props: any) => {
  const params = useParams<{ userId: string }>();
  const [form, setForm] = useState<AuditorEditorFormFields>({
    role: '',
    email: '',
    displayName: '',
    phoneNumber: '',
    departments: [],
    whatsappNumber: '',
    whatsappAdvanceNotification: false,
    siteAuditors: [],
    siteSupervisors: [],
    password: '',
  });
 
  // Fetch user data on mount
  useEffect(() => {
    if (!location.pathname.includes('new')) {
      fetchUser();
    }
  }, [params.userId]);
 
  const fetchUser = async () => {
    const data = await fetchSingleUser(params.userId);
    // Transform and set form data
    setForm({
      role: data.role,
      email: data.email,
      displayName: data.displayName || '',
      phoneNumber: String(data?.phoneNumber || ''),
      departments: department || [],
      whatsappNumber: data?.whatsappNumber,
      whatsappAdvanceNotification: data?.whatsappAdvanceNotification,
      siteAuditors: siteAuditors || [],
      siteSupervisors: siteSupervisors || [],
      password: data.password,
    });
  };
}

Key features of this component:

  • Dynamic Loading: Determines whether to create a new user or edit existing based on route
  • Comprehensive Form State: Manages all user fields including advanced permissions
  • Site Assignment: Handles supervisor and auditor assignments to specific sites
  • Department Management: Manages user department associations
  • Password Generation: Includes secure password generation based on organization rules

2. Form Structure and Fields

AuditorEditForm Component (src/pages/AuditorEditForm.tsx:1-806)

The form component provides a comprehensive UI for user data entry:

const AuditorEditorForm = (props: AuditorEditorFormProps) => {
  // Core user fields
  return (
    <Wrapper>
      <GridContainer>
        <FormColumn>
          {/* User ID/Email Field with availability check */}
          <FormInput
            name="email"
            type="email"
            placeholder={t('placeholder.auditorPage.email')}
            value={props.form.email}
            hasError={props.formErrors.email}
            disabled={!!props.isBusy || !!emailInputDisabled}
            onChange={props.handleInputChange}
          />
          
          {/* Password field with generation */}
          <FormInput
            name="password"
            type="text"
            placeholder={t('placeholder.auditorPage.password')}
            value={props.form.password}
            disabled={!canEditPassword}
          />
          <FilledButton onClick={generatePasswordBasedOnRule}>
            Generate
          </FilledButton>
          
          {/* Role selection with permissions */}
          <SingleSelect
            value={roleValue}
            options={selectOptions()}
            onChange={props.onSelectRole}
            isDisabled={!props.canEditRole}
            isOptionDisabled={selectOptionDisabled}
          />
          
          {/* Department multi-select */}
          <FilterField
            options={departmentOptions}
            onChange={props.handleSelectDepartment}
            value={mapValuesToOptions(props.form.departments, departmentMap)}
          />
          
          {/* WhatsApp notification settings */}
          <RadioContainer>
            <Radio onClick={() => props.handleWhatsappNotificationType(false)}>
              Basic
            </Radio>
            <Radio onClick={() => props.handleWhatsappNotificationType(true)}>
              Advanced
            </Radio>
          </RadioContainer>
        </FormColumn>
        
        {/* Site assignments section */}
        <div>
          {/* Supervisor assignments */}
          <div className="supervisor-section">
            <FilterField options={siteOptions} />
            <SingleSelect options={departmentOptions} />
            <EmailContainer>
              {/* List of assigned sites */}
            </EmailContainer>
          </div>
          
          {/* Auditor assignments */}
          <div className="auditor-section">
            {/* Similar structure for auditor assignments */}
          </div>
        </div>
      </GridContainer>
    </Wrapper>
  );
};

3. Validation Logic

The module implements comprehensive validation for all user fields:

Email Validation:

  • Checks format validity
  • Ensures uniqueness (except for current user)
  • Supports both email and username formats

Phone Number Validation:

  • Uses libphonenumber-js for international format validation
  • Validates both primary and WhatsApp numbers
  • Supports country-specific formats

Password Validation:

  • Enforces organization-specific password rules
  • Minimum length requirements
  • Character complexity requirements
  • Generated passwords follow security best practices

Role Validation:

  • Ensures user cannot assign roles above their access level
  • Validates role permissions based on editor’s role

4. Advanced Features

Site and Department Assignment

The module supports complex organizational structures through site and department assignments:

const handleAddSupervisor = (siteID: string[], deptID: string) => {
  const siteSupervisorsArr: { siteID: string; departmentID: string }[] = [];
  
  siteID.forEach((siteID) => {
    const alreadyPresent = siteSupervisors.some(
      (supervisor) => supervisor.siteID === siteID && supervisor.departmentID === deptID
    );
    
    if (!alreadyPresent) {
      siteSupervisorsArr.push({ siteID: siteID, departmentID: deptID });
    }
  });
  
  const updatedSiteSupervisors = [...siteSupervisors, ...siteSupervisorsArr];
  setForm({ ...form, siteSupervisors: updatedSiteSupervisors });
};

This allows:

  • Multiple site assignments per user
  • Department-specific permissions within sites
  • Hierarchical permission structures
  • Bulk assignment operations
Username Availability Check

For new users, the system provides real-time username availability checking:

const handleCheckAvailability = async () => {
  setLoading(true);
  const isAvailable = await checkUserNameAvaibility(form.email);
  setEmailAvailable(isAvailable);
  setMessage(isAvailable ? 'Available' : 'Already Taken');
  setLoading(false);
};
Password Generation

The system includes intelligent password generation based on organization rules:

const generatePasswordBasedOnRule = () => {
  const passwordSettingRule = useSelector(
    (state: RootState) => state?.organization?.organization?.passwordConfiguration
  );
  const newPassword = generatedPassword(passwordSettingRule);
  props.handleInputValue('password', newPassword);
  props.setIsPasswordGenerated(true);
};

5. State Management

The User Details module integrates with Redux for state management:

Redux State Structure:

{
  admin: {
    manage: {
      upsertedUser_loading: boolean,
      upsertedUser_error: string | null,
      upsertedUser_userID: string | null
    }
  },
  users: {
    paginateUsers: { [userId: string]: User }
  },
  departments: {
    departments: { [deptId: string]: Department }
  },
  settings: {
    userRolesMap: { [role: string]: RoleLabel }
  }
}

Key Actions:

  • createUserAsync: Creates new user with validation
  • updateUserAsync: Updates existing user data
  • fetchSingleUser: Retrieves individual user details
  • clearUserState: Resets form state after operations

6. API Integration

The module uses several API endpoints for user operations:

Fetch User Details:

const fetchSingleUser = async (userId: string) => {
  const response = await axios.get(`/api/v1/users/${userId}`);
  return response.data;
};

Create User:

const createNewUser = async (userData: UserData) => {
  const response = await axios.post('/api/v1/users', {
    email: userData.email,
    displayName: userData.displayName,
    phoneNumber: userData.phoneNumber,
    role: userData.role,
    departmentIDs: userData.departmentIDs,
    whatsappNumber: userData.whatsappNumber,
    whatsappAdvanceNotification: userData.whatsappAdvanceNotification,
    siteSupervisors: userData.siteSupervisors,
    siteAuditors: userData.siteAuditors,
    password: userData.password
  });
  return response.data;
};

Update User:

const updateSingleUser = async (userData: UpdateUserData) => {
  const response = await axios.put(`/api/v1/users/${userData.targetEmail}`, {
    email: userData.email,
    phoneNumber: userData.phoneNumber,
    displayName: userData.displayName,
    role: userData.role,
    whatsappNumber: userData.whatsappNumber,
    whatsappAdvanceNotification: userData.whatsappAdvanceNotification,
    siteSupervisors: userData.siteSupervisors,
    siteAuditors: userData.siteAuditors,
    departmentIDs: userData.departmentIDs,
    password: userData.password
  });
  return response.data;
};

7. Permission-Based Access Control

The module implements granular permission controls:

// Permission checks
const canEditUser = state.userAccess.admin.user.all.permissions.edit;
const canEditRole = state.userAccess.admin.user.role.permissions.edit;
const canCreateUser = state.userAccess.admin.user.all.permissions.create;
 
// Field-level permissions
const emailInputDisabled = props.isBusy || (props.selectedUserKey && isActiveUser) && isUpdatingUser;
const roleSelectDisabled = !props.canEditRole || props.selectedUserKey === props.editorUserKey;
const passwordEditDisabled = loggedInUserRole !== 'account_holder' && loggedInUserRole !== 'superadmin';

8. Error Handling and User Feedback

The module provides comprehensive error handling:

useEffect(() => {
  if (!upsertedUser_loading) {
    if (upsertedUser_error) {
      toast.error(upsertedUser_error);
      setIsUploadingSingle(false);
      props.clearUserState();
      setIsBusy(false);
    } else if (upsertedUser_userID) {
      toast.success(isCreate ? `${email} successfully added.` : `Successfully updated user`);
      history.goBack();
    }
  }
}, [upsertedUser_loading, upsertedUser_error]);

Data Flow

sequenceDiagram
    participant U as User
    participant C as Component
    participant V as Validator
    participant R as Redux
    participant A as API

    U->>C: Navigate to user details
    C->>A: fetchSingleUser(userId)
    A-->>C: Return user data
    C->>C: Populate form fields
    
    U->>C: Edit user information
    C->>V: Validate fields
    V-->>C: Return validation results
    
    U->>C: Submit form
    C->>V: Final validation
    V-->>C: Validation passed
    C->>R: Dispatch updateUserAsync
    R->>A: PUT /api/v1/users/:id
    A-->>R: Return updated user
    R-->>C: Update success
    C-->>U: Show success message

Mobile Responsiveness

The User Details module is fully responsive:

  1. Grid Layout: Switches from 2-column to 1-column on mobile
  2. Touch-Optimized Controls: Larger touch targets for mobile
  3. Simplified Navigation: Back button instead of modal close
  4. Adaptive Form Fields: Full-width fields on mobile devices

Performance Optimizations

  1. Lazy Loading: Components loaded on demand
  2. Debounced Validation: Reduces validation calls during typing
  3. Memoized Selectors: Prevents unnecessary re-renders
  4. Optimistic Updates: UI updates before server confirmation

User Creation Module

Overview

The User Creation module provides multiple methods for adding new users to the system: single user creation through forms and bulk user creation through CSV import. The module ensures data integrity through comprehensive validation and provides real-time feedback during the creation process.

Architecture

graph TD
    A[User Creation Methods] --> B[Single User Creation]
    A --> C[Bulk User Creation]
    
    B --> D[Form Input]
    B --> E[Validation]
    B --> F[API Call]
    
    C --> G[CSV Upload]
    C --> H[File Parsing]
    C --> I[Batch Processing]
    
    J[Validation Services] --> K[Email Validation]
    J --> L[Phone Validation]
    J --> M[Role Validation]
    J --> N[Username Availability]
    
    O[Firebase Functions] --> P[createNewAuditor]
    O --> Q[Role Assignment]
    
    R[Redux Actions] --> S[createUserAsync]
    R --> T[fetchPaginateUsers]
    R --> U[clearUserState]

Implementation Details

1. Single User Creation

Single user creation is handled through the same form components used for editing, but with specific logic for new users:

Route: /admin/auditors/new

Key Implementation (src/pages/auditorEdit.tsx:298-404):

const handleCreateUser = async () => {
  // Permission check
  if (!props.canCreateUser) {
    toast.error('You are not allowed to create new users');
    return;
  }
 
  setIsBusy(true);
  setIsUploadingSingle(true);
  const hasError = validateForm();
  
  // Handle email/username formatting
  let emailOrUsername = form.email;
  if (form.email.includes('@') && form.email.includes('.')) {
    emailOrUsername = form.email.toLowerCase();
  }
 
  if (!hasError) {
    const data = {
      email: emailOrUsername,
      displayName: form.displayName.trim().replace(/\s+/g, ' '),
      phoneNumber: form.phoneNumber?.trim()?.replace(/ /g, ''),
      role: form.role,
      whatsappNumber: form.whatsappNumber?.replace(/\s/g, ''),
      whatsappAdvanceNotification: form.whatsappAdvanceNotification,
      siteSupervisors: form?.siteSupervisors || [],
      siteAuditors: form?.siteAuditors || [],
      departmentIDs: form?.departments || [],
      password: form?.password,
    };
    
    const result = await createNewUser(data);
    if (result.message === 'FAILED') {
      toast.error(result.error);
      setIsBusy(false);
    } else {
      toast.success('User created successfully');
      history.goBack();
    }
  }
};
Key Features:
  1. Username/Email Flexibility:

    • Supports both email addresses and usernames
    • Automatically lowercases email addresses
    • Validates format based on input type
  2. Real-time Availability Check:

    const handleCheckAvailability = async () => {
      setLoading(true);
      const isAvailable = await checkUserNameAvaibility(form.email);
      setEmailAvailable(isAvailable);
      setMessage(isAvailable ? 'Available' : 'Already Taken');
      setLoading(false);
    };
  3. Password Generation:

    • Generates secure passwords based on organization rules
    • Provides copy-to-clipboard functionality
    • Shows generated password for user communication
  4. Department Assignment:

    • Multiple department selection
    • Visual tags for selected departments
    • Easy removal of assignments
  5. Site Assignments:

    • Separate supervisor and auditor site assignments
    • Department-specific permissions per site
    • Bulk assignment capabilities

2. Bulk User Creation

The bulk user creation feature allows administrators to import multiple users via CSV file upload:

Implementation (src/pages/auditorEdit.tsx:466-594):

const handleFileChange = (event: any) => {
  // Permission check
  if (!props.canCreateUser) {
    toast.error('You are not allowed to create new users');
    return;
  }
 
  const fileTypes = ['csv'];
  if (event.target.files.length && event.target.files.length === 1) {
    const file = event.target.files[0];
    const extension = file.name.split('.').pop().toLowerCase();
    const isSuccess = fileTypes.indexOf(extension) > -1;
    
    if (isSuccess) {
      Papa.parse(file, {
        skipEmptyLines: true,
        complete: (results) => handleCreateUsers(results),
      });
    } else {
      toast.error('Cannot parse the file. Please download the template and try again.');
    }
  }
};
CSV Format Requirements:

The CSV file must follow this format:

Email Address,Full Name,Role,Phone Number (e.g. +628123456789)
john.doe@example.com,John Doe,Auditor,+628123456789
jane.smith@example.com,Jane Smith,Supervisor,+628123456790
Bulk Processing Logic:
const handleCreateUsers = async (results: any) => {
  const data = results.data;
  
  // Validate CSV headers
  if (
    data[0][0] === 'Email Address' &&
    data[0][1] === 'Full Name' &&
    data[0][2] === 'Role' &&
    data[0][3] === 'Phone Number (e.g. +628123456789)'
  ) {
    setIsUploadingBulk(true);
    setIsBusy(true);
    setUploadProgress(0);
    
    // Filter out existing emails and invalid phone numbers
    const emails = Object.keys(props.users).map((uid) => props.users[uid].email);
    const entries = data.slice(1).filter((entry: string[]) => {
      const index = emails.indexOf(entry[0]);
      const phoneNumber = parsePhoneNumberFromString(entry[3] || '');
      return index === -1 && phoneNumber && phoneNumber.isValid();
    });
    
    // Process in batches of 5
    const recursiveAddNewAuditor = async (e: any[]): Promise<any> => {
      if (e.length === 0) return [];
      
      const entriesToProcess = e.splice(0, 5);
      const users = entriesToProcess.map(async (entry: any) => {
        const userData = {
          email: entry[0].toLowerCase(),
          displayName: entry[1],
          phoneNumber: entry[3]?.trim()?.replace(/ /g, '') || '',
        };
        
        // Create user and assign role
        const res = await createNewAuditor(userData);
        const newUserID = res.data.uid;
        
        // Assign role via API
        await fetch(`${apiURL}/user-roles/user-to-role`, {
          method: 'PUT',
          headers: {
            'Content-Type': 'application/json',
            authorization: authToken,
          },
          body: JSON.stringify({
            userID: newUserID,
            role: newUserRole,
          }),
        });
      });
      
      return merged.concat(await recursiveAddNewAuditor(e));
    };
  }
};
Key Features of Bulk Creation:
  1. Batch Processing: Processes users in batches of 5 to avoid overwhelming the server
  2. Progress Tracking: Real-time progress bar showing upload status
  3. Validation:
    • Validates email uniqueness
    • Validates phone number format
    • Validates role assignments
  4. Error Handling: Tracks successes and failures separately
  5. Role Mapping: Maps CSV role names to system role IDs

3. Validation Rules

The User Creation module implements comprehensive validation:

Email/Username Validation:
const validateEmail = (email: string) => {
  // Check if it's an email format
  if (email.includes('@') && email.includes('.')) {
    return isValidEmail(email);
  }
  // Username validation (2-8 characters, no spaces)
  return /^[a-zA-Z0-9]{2,8}$/.test(email);
};
Phone Number Validation:
const validatePhoneNumber = (phoneNumber: string) => {
  if (!phoneNumber) return false;
  
  const parsed = parsePhoneNumberFromString(phoneNumber);
  if (typeof parsed === 'undefined') return true;
  
  return parsed.isValid();
};
Password Requirements:
  • Minimum 8 characters
  • At least one uppercase letter
  • At least one lowercase letter
  • At least one number
  • Optional special characters based on organization settings
Role Assignment Rules:
  • Users can only assign roles below their access level
  • Superadmin and account_holder roles cannot be assigned via UI
  • Role permissions are validated server-side

4. API Integration

Create Single User:
POST /api/v1/users
{
  "email": "user@example.com",
  "displayName": "User Name",
  "phoneNumber": "+628123456789",
  "role": "auditor",
  "departmentIDs": ["dept1", "dept2"],
  "whatsappNumber": "+628123456789",
  "whatsappAdvanceNotification": false,
  "siteSupervisors": [
    { "siteID": "site1", "departmentID": "dept1" }
  ],
  "siteAuditors": [
    { "siteID": "site2", "departmentID": "dept2" }
  ],
  "password": "SecurePassword123!"
}
Firebase Function for Bulk Creation:
const createNewAuditor = firebase.functions().httpsCallable('createNewAuditor');
const result = await createNewAuditor({
  email: userData.email,
  displayName: userData.displayName,
  phoneNumber: userData.phoneNumber
});

5. Error Handling

The module provides comprehensive error handling for various scenarios:

  1. Duplicate Email/Username:

    if (uniqueEmail.length !== 0) {
      toast.error('Email is already registered');
      return;
    }
  2. Invalid Phone Number:

    if (validatePhoneNumber(form.phoneNumber)) {
      setFormErrors({ ...formErrors, phoneNumber: true });
      toast.error('Invalid phone number format');
    }
  3. Permission Denied:

    if (!props.canCreateUser) {
      toast.error('You are not allowed to create new users');
      return;
    }
  4. Network Errors:

    try {
      const result = await createNewUser(data);
    } catch (error) {
      toast.error('Network error. Please try again.');
      setIsBusy(false);
    }

6. Success Handling

Upon successful user creation:

  1. Single User:

    • Success toast notification
    • Navigation back to user list
    • Automatic refresh of user list
    • Clear form state
  2. Bulk Users:

    • Summary of successful/failed imports
    • Detailed error report for failures
    • Automatic refresh of user list
    • Progress reset

Security Considerations

  1. Input Sanitization:

    • Email addresses are lowercased and trimmed
    • Display names remove extra spaces
    • Phone numbers remove formatting characters
  2. Permission Checks:

    • Client-side permission validation
    • Server-side permission enforcement
    • Role-based creation limits
  3. Password Security:

    • Generated passwords meet complexity requirements
    • Passwords are never displayed in logs
    • Secure transmission over HTTPS
  4. Rate Limiting:

    • Bulk operations limited to 5 concurrent requests
    • Username availability checks are debounced
    • CSV file size limits enforced

Best Practices

  1. User Communication:

    • Always communicate generated passwords securely
    • Provide clear error messages
    • Show progress for long operations
  2. Data Validation:

    • Validate all inputs client-side first
    • Server-side validation as final check
    • Provide specific validation error messages
  3. Bulk Import:

    • Provide downloadable CSV template
    • Validate file format before processing
    • Handle partial failures gracefully
  4. Performance:

    • Batch API calls for bulk operations
    • Show progress indicators
    • Implement proper error recovery

User Update Module

Overview

The User Update module handles modifications to existing user data, including profile information, role changes, department assignments, and status updates. It provides both individual and bulk update capabilities with comprehensive validation and permission checks.

Architecture

graph TD
    A[User Update Operations] --> B[Individual Updates]
    A --> C[Bulk Updates]
    A --> D[Status Updates]
    
    B --> E[Profile Updates]
    B --> F[Role Changes]
    B --> G[Department Changes]
    B --> H[Site Assignments]
    
    C --> I[Bulk Status Change]
    C --> J[Bulk Role Update]
    C --> K[Bulk Department Assignment]
    
    L[Validation Layer] --> M[Permission Validation]
    L --> N[Data Validation]
    L --> O[Conflict Resolution]
    
    P[Update Strategies] --> Q[Optimistic Updates]
    P --> R[Transactional Updates]
    P --> S[Partial Updates]

Implementation Details

1. Individual User Updates

The primary user update functionality is implemented in the auditorEdit component:

Implementation (src/pages/auditorEdit.tsx:409-464):

const handleUpdateUser = async () => {
  // Permission validation
  if (!props.canEditUser) {
    toast.error('You are not allowed to edit users');
    return;
  }
 
  // Email uniqueness validation
  const uniqueEmail = Object.keys(props.users || {})
    ?.filter((key) => props?.users?.[key]?.email === form?.email) || [];
  if (form.email !== currentTargetEmail) {
    if (uniqueEmail.length !== 0) {
      toast.error('Email is already registered');
      return;
    }
  }
 
  setIsBusy(true);
  setIsPasswordGenerated(false);
  setIsGenerated(false);
 
  const data = {
    targetEmail: currentTargetEmail,
    email: form.email,
    phoneNumber: form.phoneNumber,
    displayName: form.displayName,
    role: form.role,
    whatsappNumber: form.whatsappNumber?.replace(/\s/g, ''),
    whatsappAdvanceNotification: form.whatsappAdvanceNotification,
    siteSupervisors: form?.siteSupervisors || [],
    siteAuditors: form?.siteAuditors || [],
    departmentIDs: form?.departments || [],
    password: form.password,
  };
 
  const result = await updateSingleUser(data);
  if (result.message === 'FAILED') {
    toast.error(result.error);
  } else {
    toast.success('User updated successfully');
    await fetchUser(); // Refresh user data
  }
  setIsBusy(false);
};

2. Update Types and Validation

Profile Information Updates

Profile updates include changes to:

  • Display name
  • Phone numbers (primary and WhatsApp)
  • Email/username (with special validation)
  • WhatsApp notification preferences

Email Update Validation:

// Special handling for email updates
if (form.email !== currentTargetEmail) {
  // Check if new email is already in use
  const emailExists = await checkEmailAvailability(form.email);
  if (!emailExists) {
    toast.error('Email is already registered');
    return;
  }
  
  // Additional validation for email format
  if (!isValidEmail(form.email) && form.email.includes('@')) {
    toast.error('Invalid email format');
    return;
  }
}
Role Updates

Role updates have special permission requirements:

const canUpdateRole = () => {
  // User cannot change their own role
  if (props.selectedUserKey === props.editorUserKey) {
    return false;
  }
  
  // User cannot assign roles above their level
  const targetRoleLevel = props.roleOptionsMap[form.role]?.level;
  const editorAccessLevel = props.accessLevel;
  
  return targetRoleLevel < editorAccessLevel && props.canEditRole;
};
Department and Site Assignment Updates

Complex organizational structure updates:

// Add supervisor to sites
const handleAddSupervisor = (siteID: string[], deptID: string) => {
  const siteSupervisorsArr: { siteID: string; departmentID: string }[] = [];
  
  siteID.forEach((siteID) => {
    const alreadyPresent = siteSupervisors.some(
      (supervisor) => supervisor.siteID === siteID && 
                      supervisor.departmentID === deptID
    );
    
    if (!alreadyPresent) {
      siteSupervisorsArr.push({ siteID: siteID, departmentID: deptID });
    }
  });
  
  const updatedSiteSupervisors = [...siteSupervisors, ...siteSupervisorsArr];
  setForm({ ...form, siteSupervisors: updatedSiteSupervisors });
};
 
// Remove supervisor from site
const handleRemoveSupervisor = (siteID: string, deptID: string) => {
  const { siteSupervisors = [] } = form;
  
  const filteredSiteSupervisor = siteSupervisors.filter(
    (supervisor) => !(supervisor.siteID === siteID && 
                      supervisor.departmentID === deptID)
  );
  
  setForm({ ...form, siteSupervisors: filteredSiteSupervisor });
};

3. Status Updates (Block/Unblock)

User status updates are handled through the list interface:

Implementation (src/components/auditors/AuditorList/AuditorListTable.tsx:85-130):

const renderBlockButton = (user: any, block: string, unblock: string) => {
  const blockButton = (
    <img
      alt={block}
      title={block}
      src={TrashIcon}
      onClick={(e) => {
        e.stopPropagation();
        if (props.isNotBlockable(props.users[user])) {
          return null;
        } else {
          props.handleBlockUser(user);
        }
      }}
      style={{ opacity: props.isNotBlockable(props.users[user]) ? 0.4 : 1 }}
    />
  );
  
  const unblockButton = (
    <Button
      small
      inversed
      disabled={props.isNotBlockable(props.users[user])}
      onClick={(e) => {
        e.stopPropagation();
        props.handleBlockUser(user);
      }}
    >
      {unblock}
    </Button>
  );
  
  // Show appropriate button based on current status
  if (props.access.delete) {
    if (props.users[user].status !== 'disabled') {
      return blockButton;
    }
    return unblockButton;
  }
  return null;
};

4. Bulk Update Operations

The system supports bulk updates through the AuditorBulkModal component:

Bulk Modal Implementation (src/components/auditors/AuditorBulkModal/AuditorBulkModal.tsx):

const AuditorBulkModal = () => {
  const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
  const [updateType, setUpdateType] = useState<'status' | 'role' | 'department'>('status');
  const [updateValue, setUpdateValue] = useState<any>(null);
  
  const handleBulkUpdate = async () => {
    const updates = selectedUsers.map(userId => ({
      userId,
      updates: {
        [updateType]: updateValue
      }
    }));
    
    try {
      const results = await Promise.all(
        updates.map(update => updateUser(update.userId, update.updates))
      );
      
      const successCount = results.filter(r => r.success).length;
      toast.success(`Successfully updated ${successCount} users`);
      
      if (results.some(r => !r.success)) {
        const failCount = results.filter(r => !r.success).length;
        toast.error(`Failed to update ${failCount} users`);
      }
    } catch (error) {
      toast.error('Bulk update failed');
    }
  };
};

5. Password Updates

Password updates have special security considerations:

const handlePasswordUpdate = async () => {
  // Only certain roles can update passwords
  const canUpdatePassword = 
    loggedInUserRole === 'account_holder' || 
    loggedInUserRole === 'superadmin' ||
    location.pathname.includes('new');
    
  if (!canUpdatePassword) {
    toast.error('You do not have permission to update passwords');
    return;
  }
  
  // Validate password complexity
  if (!validatePasswordComplexity(form.password)) {
    toast.error('Password does not meet complexity requirements');
    return;
  }
  
  // Update password
  const result = await updateUserPassword(userId, form.password);
  if (result.success) {
    toast.success('Password updated successfully');
    setIsPasswordGenerated(false);
  }
};

6. API Integration

Update User Endpoint:
PUT /api/v1/users/:userId
{
  "email": "updated@example.com",
  "displayName": "Updated Name",
  "phoneNumber": "+628123456789",
  "role": "supervisor",
  "departmentIDs": ["dept1", "dept2"],
  "whatsappNumber": "+628123456789",
  "whatsappAdvanceNotification": true,
  "siteSupervisors": [
    { "siteID": "site1", "departmentID": "dept1" }
  ],
  "siteAuditors": [
    { "siteID": "site2", "departmentID": "dept2" }
  ]
}
Update User Status:
PUT /api/v1/users/:userId/status
{
  "status": "disabled" | "active"
}
Bulk Update Endpoint:
POST /api/v1/users/bulk-update
{
  "userIds": ["user1", "user2", "user3"],
  "updates": {
    "role": "auditor",
    "departmentIDs": ["dept1"]
  }
}

7. Optimistic Updates

The system implements optimistic updates for better user experience:

const handleOptimisticUpdate = async (userId: string, updates: any) => {
  // Update UI immediately
  dispatch(updateUserOptimistically(userId, updates));
  
  try {
    // Make API call
    const result = await updateUser(userId, updates);
    
    if (!result.success) {
      // Revert on failure
      dispatch(revertUserUpdate(userId));
      toast.error('Update failed');
    }
  } catch (error) {
    // Revert on error
    dispatch(revertUserUpdate(userId));
    toast.error('Network error');
  }
};

8. Conflict Resolution

The system handles update conflicts gracefully:

const handleUpdateConflict = async (conflict: UpdateConflict) => {
  switch (conflict.type) {
    case 'EMAIL_EXISTS':
      toast.error('Email is already in use by another user');
      break;
      
    case 'STALE_DATA':
      // Refresh data and retry
      const freshData = await fetchUser(conflict.userId);
      const retry = await updateUser(conflict.userId, {
        ...conflict.updates,
        version: freshData.version
      });
      break;
      
    case 'PERMISSION_CHANGED':
      toast.error('Your permissions have changed. Please refresh the page.');
      break;
      
    default:
      toast.error('Update failed due to conflict');
  }
};

Update Validation Rules

  1. Email Updates:

    • Must be unique across the system
    • Format validation for email addresses
    • Username format validation (2-8 characters)
  2. Role Updates:

    • Cannot assign roles above editor’s level
    • Cannot change own role
    • Special roles (superadmin, account_holder) restricted
  3. Phone Number Updates:

    • International format validation
    • Country code requirements
    • WhatsApp number separate validation
  4. Department Updates:

    • Must be valid department IDs
    • Circular dependency checks
    • Permission validation per department
  5. Site Assignment Updates:

    • Site must exist and be active
    • Department must be valid for site
    • No duplicate assignments

Security Considerations

  1. Permission Checks:

    // Hierarchical permission validation
    const validateUpdatePermission = (editor: User, target: User, updates: any) => {
      // Cannot edit users at or above your level
      if (target.role.level >= editor.role.level) {
        return false;
      }
      
      // Cannot assign roles above your level
      if (updates.role && updates.role.level >= editor.role.level) {
        return false;
      }
      
      // Field-specific permissions
      if (updates.password && !editor.permissions.updatePassword) {
        return false;
      }
      
      return true;
    };
  2. Audit Trail:

    • All updates logged with timestamp
    • Editor information recorded
    • Previous values preserved
    • Change reasons tracked
  3. Transactional Updates:

    • Complex updates wrapped in transactions
    • Rollback on partial failure
    • Consistency guarantees

Best Practices

  1. Update Strategies:

    • Use optimistic updates for better UX
    • Batch related updates together
    • Validate before submission
    • Handle conflicts gracefully
  2. User Communication:

    • Clear success/error messages
    • Explain validation failures
    • Confirm destructive actions
    • Show update progress
  3. Performance:

    • Debounce rapid updates
    • Use field-level updates when possible
    • Cache validation results
    • Minimize API calls
  4. Data Integrity:

    • Validate all inputs
    • Check permissions server-side
    • Maintain referential integrity
    • Handle race conditions

User Delete Module

Overview

The User Delete module handles the removal of users from the system. Due to the critical nature of user deletion, the module implements multiple safeguards including soft deletes (status changes), permission checks, and special utilities for customer success teams to handle bulk deletions across organizations.

Architecture

graph TD
    A[User Delete Operations] --> B[Soft Delete/Block]
    A --> C[Hard Delete]
    A --> D[Bulk Delete]
    
    B --> E[Status Change to Disabled]
    B --> F[Preserve User Data]
    B --> G[Audit Trail]
    
    C --> H[CS Utility Only]
    C --> I[Permanent Removal]
    C --> J[Data Cleanup]
    
    K[Permission Checks] --> L[Role-Based Access]
    K --> M[Hierarchical Validation]
    K --> N[Self-Delete Prevention]
    
    O[Safety Measures] --> P[Confirmation Dialogs]
    O --> Q[Audit Logging]
    O --> R[Rollback Capability]

Implementation Details

1. Soft Delete (User Blocking)

The primary method for “deleting” users is through soft delete, which changes the user’s status to ‘disabled’:

Implementation in User List (src/components/auditors/AuditorList/AuditorListTable.tsx:85-130):

const handleBlockUser = async (userId: string) => {
  // Permission check
  if (!props.access.delete) {
    toast.error('You do not have permission to block users');
    return;
  }
  
  // Prevent self-blocking
  if (userId === currentUserId) {
    toast.error('You cannot block your own account');
    return;
  }
  
  // Hierarchical permission check
  const targetUser = props.users[userId];
  if (isNotBlockable(targetUser)) {
    toast.error('You cannot block users at or above your access level');
    return;
  }
  
  // Confirm action
  const confirmed = await confirmDialog({
    title: 'Block User',
    message: `Are you sure you want to block ${targetUser.displayName}?`,
    confirmText: 'Block',
    cancelText: 'Cancel'
  });
  
  if (confirmed) {
    try {
      await updateUserStatus(userId, 'disabled');
      toast.success('User blocked successfully');
      props.fetchPaginateUsers({ limit: props.itemListLimit });
    } catch (error) {
      toast.error('Failed to block user');
    }
  }
};
Key Features of Soft Delete:
  1. Reversible: Users can be unblocked/reactivated
  2. Data Preservation: All user data remains intact
  3. Access Revocation: Blocked users cannot log in
  4. Audit Trail: All blocks/unblocks are logged
Permission Validation:
const isNotBlockable = (user: User) => {
  // Cannot block users with higher or equal access level
  const userAccessLevel = props.roleOptionsMap[user.role]?.level || 0;
  const currentAccessLevel = props.accessLevel;
  
  // Special protection for system roles
  if (user.role === 'superadmin' || user.role === 'account_holder') {
    return true;
  }
  
  return userAccessLevel >= currentAccessLevel;
};

2. Hard Delete (Customer Success Utility)

Hard deletion is restricted to customer success teams and involves permanent removal of user data:

Delete Users Component (src/components/customerSuccessUtility/deleteUsers/DeleteUsers.tsx:1-294):

export const DeleteUsers = (props: any) => {
  const [orgList, setOrgList] = useState<any[]>([]);
  const [auditorList, setAuditorList] = useState<any>([]);
  const [selectedOrg, setSelectedOrg] = useState('');
  const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
  const [isDeleteUserLoading, setDeleteUserLoading] = useState(false);
 
  const handleSubmit = async () => {
    setDeleteUserLoading(true);
    
    // Multi-level confirmation for hard delete
    const confirmed = await multiConfirmDialog([
      {
        title: 'Confirm User Deletion',
        message: `You are about to permanently delete ${selectedUsers.length} users.`
      },
      {
        title: 'Final Confirmation',
        message: 'This action cannot be undone. Type "DELETE" to confirm.',
        requireInput: true,
        expectedInput: 'DELETE'
      }
    ]);
    
    if (confirmed) {
      await deleteUsersNetwork({
        orgID: selectedOrg,
        emailids: selectedAuditorList?.map((auditor: any) => auditor?.userID) || [],
      });
      
      // Log the deletion
      await logDeletion({
        deletedBy: currentUser.id,
        orgID: selectedOrg,
        deletedUsers: selectedAuditorList,
        timestamp: new Date().toISOString()
      });
      
      handleClear();
      setDeleteUserLoading(false);
      fetchUserDetails();
      toast.success('Users deleted successfully');
    }
  };
};
Hard Delete Process:
  1. Organization Selection: CS team selects target organization
  2. User Selection: Multiple users can be selected for deletion
  3. Multi-Level Confirmation: Requires explicit confirmation
  4. Data Cleanup: Removes all user-related data
  5. Audit Logging: Comprehensive logging of deletion

3. Bulk Operations

The system supports bulk status changes through the bulk modal:

const handleBulkBlock = async (userIds: string[]) => {
  // Filter out non-blockable users
  const blockableUsers = userIds.filter(userId => {
    const user = users[userId];
    return !isNotBlockable(user) && userId !== currentUserId;
  });
  
  if (blockableUsers.length === 0) {
    toast.error('No users can be blocked from the selection');
    return;
  }
  
  if (blockableUsers.length < userIds.length) {
    toast.warning(`${userIds.length - blockableUsers.length} users cannot be blocked`);
  }
  
  // Batch API calls
  const results = await Promise.all(
    blockableUsers.map(userId => updateUserStatus(userId, 'disabled'))
  );
  
  const successCount = results.filter(r => r.success).length;
  toast.success(`Successfully blocked ${successCount} users`);
};

4. Data Cleanup and Dependencies

When a user is deleted (hard delete), the system must handle:

  1. Site Assignments: Remove user from all site assignments
  2. Department Associations: Clear department memberships
  3. Reports: Archive or reassign user’s reports
  4. Authentication: Revoke all active sessions
  5. Notifications: Cancel pending notifications
const cleanupUserData = async (userId: string) => {
  // Remove from sites
  await removeSiteAssignments(userId);
  
  // Clear department associations
  await clearDepartmentMemberships(userId);
  
  // Handle reports
  const reports = await getUserReports(userId);
  if (reports.length > 0) {
    await archiveReports(reports);
  }
  
  // Revoke authentication
  await revokeUserSessions(userId);
  
  // Cancel notifications
  await cancelPendingNotifications(userId);
  
  // Remove from cache
  await clearUserCache(userId);
};

5. API Integration

Soft Delete (Block User):
PUT /api/v1/users/:userId/status
{
  "status": "disabled",
  "reason": "User blocked by admin",
  "blockedBy": "adminUserId",
  "blockedAt": "2024-01-20T10:00:00Z"
}
Hard Delete (CS Utility):
DELETE /api/v1/organizations/:orgId/users
{
  "userIds": ["user1", "user2", "user3"],
  "confirmation": "DELETE",
  "deletedBy": "csUserId",
  "reason": "Customer request"
}
Reactivate User:
PUT /api/v1/users/:userId/status
{
  "status": "active",
  "reactivatedBy": "adminUserId",
  "reactivatedAt": "2024-01-20T11:00:00Z"
}

6. Safety Measures

The module implements multiple safety measures:

1. Confirmation Dialogs:
const confirmDialog = async (options: ConfirmOptions) => {
  return new Promise((resolve) => {
    const modal = createConfirmModal({
      ...options,
      onConfirm: () => {
        resolve(true);
        modal.close();
      },
      onCancel: () => {
        resolve(false);
        modal.close();
      }
    });
    modal.open();
  });
};
2. Audit Logging:
const logUserDeletion = async (deletion: DeletionLog) => {
  await auditLog.create({
    action: 'USER_DELETED',
    actor: deletion.deletedBy,
    target: deletion.userId,
    timestamp: deletion.timestamp,
    details: {
      userEmail: deletion.userEmail,
      userName: deletion.userName,
      reason: deletion.reason,
      method: deletion.method // 'soft' or 'hard'
    }
  });
};
3. Rollback Capability:
const createDeletionBackup = async (userId: string) => {
  const userData = await getUserFullData(userId);
  const backup = {
    userId,
    data: userData,
    timestamp: new Date().toISOString(),
    expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // 30 days
  };
  
  await backupStorage.save(backup);
  return backup.id;
};

7. Permission Matrix

RoleCan Block UsersCan Unblock UsersCan Hard Delete
SuperadminYes (all)Yes (all)Yes
Account HolderYes (below level)Yes (below level)No
AdminYes (below level)Yes (below level)No
SupervisorNoNoNo
AuditorNoNoNo
CS TeamNoNoYes (with approval)

Error Handling

The module provides comprehensive error handling:

const handleDeleteError = (error: DeleteError) => {
  switch (error.code) {
    case 'PERMISSION_DENIED':
      toast.error('You do not have permission to delete this user');
      break;
      
    case 'SELF_DELETE':
      toast.error('You cannot delete your own account');
      break;
      
    case 'HIERARCHY_VIOLATION':
      toast.error('Cannot delete users at or above your level');
      break;
      
    case 'ACTIVE_SESSIONS':
      toast.error('User has active sessions. Please try again later');
      break;
      
    case 'DEPENDENCY_ERROR':
      toast.error('User has dependencies that must be resolved first');
      break;
      
    default:
      toast.error('Failed to delete user. Please try again');
  }
};

Best Practices

  1. Always Prefer Soft Delete:

    • Use status change to ‘disabled’ for regular operations
    • Reserve hard delete for data compliance requirements
    • Maintain audit trails for all deletions
  2. Permission Validation:

    • Check permissions both client and server side
    • Enforce hierarchical access control
    • Prevent self-deletion
  3. User Communication:

    • Clear confirmation dialogs
    • Explain consequences of deletion
    • Provide undo options where possible
  4. Data Integrity:

    • Handle all dependencies before deletion
    • Create backups for hard deletes
    • Maintain referential integrity
  5. Audit and Compliance:

    • Log all deletion operations
    • Track who deleted whom and when
    • Comply with data retention policies

Recovery Procedures

For accidental deletions:

  1. Soft Delete Recovery:

    // Simply reactivate the user
    await updateUserStatus(userId, 'active');
  2. Hard Delete Recovery (within retention period):

    const backup = await backupStorage.get(backupId);
    if (backup && !backup.expired) {
      await restoreUser(backup.data);
      await backupStorage.markAsRestored(backupId);
    }

Routes and Navigation

User Module Routes

The Users module exposes the following routes for navigation:

RouteComponentPurposeAccess Control
/admin/auditorsAuditorsPageMain user list pageRoleResources.ADMIN_USER_ALL
/admin/auditors/newAuditorEditPageCreate new userRoleResources.ADMIN_USER_ALL
/admin/auditors/:userIdAuditorEditPageEdit existing userRoleResources.ADMIN_USER_ALL
/admin/auditors/offdaysAuditorOffDaysPageManage auditor off daysRoleResources.ADMIN_NONOPERATIONALDAYS_ALL
/admin/auditors/useroffdaysAuditorOffDaysPageUser-specific off daysRoleResources.ADMIN_NONOPERATIONALDAYS_ALL + Features.AUDITOR_NON_OPERATIONAL_DAYS
/admin/auditors/useroffdays-dashboardAuditorOffDaysDashboardPageOff days dashboardRoleResources.ADMIN_NONOPERATIONALDAYS_ALL + Features.AUDITOR_NON_OPERATIONAL_DAYS
/analytics/auditorsAnalyticsAuditorsPageUser analyticsRoleResources.DASHBOARD_USERS_ALL
/analytics/auditorDetailsAnalyticsAuditorDetailsPageDetailed user analyticsOpen access
/analytics/auditorDetailsReport/:journeyCodeAnalyticsAuditorDetailsReportPageUser journey reportsOpen access
/customerSuccess/delete-usersDeleteUsersCS utility for user deletionSpecial CS permissions
/customerSuccess/users-role-changeUsersRoleChangeCS utility for bulk role changesSpecial CS permissions
/customerSuccess/changeUserEmailChangeUserEmailPageCS utility for email changesSpecial CS permissions
graph LR
    A[Dashboard] --> B[User Management /admin/auditors]
    B --> C[User List]
    C --> D[Create User /admin/auditors/new]
    C --> E[Edit User /admin/auditors/:id]
    C --> F[User Analytics]
    
    B --> G[Off Days Management]
    G --> H[General Off Days]
    G --> I[User Off Days]
    G --> J[Off Days Dashboard]
    
    K[CS Utilities] --> L[Delete Users]
    K --> M[Change Roles]
    K --> N[Change Email]

API Endpoints

Core User APIs

EndpointMethodPurposeRequest BodyResponse
/api/v1/usersGETList all usersQuery params: page, limit, sortBy, sortDirection, filter, search{ users: User[], total: number, page: number }
/api/v1/users/:userIdGETGet single user-{ user: User }
/api/v1/usersPOSTCreate new user{ email, displayName, phoneNumber, role, departmentIDs, whatsappNumber, whatsappAdvanceNotification, siteSupervisors, siteAuditors, password }{ user: User, message: string }
/api/v1/users/:userIdPUTUpdate user{ email, displayName, phoneNumber, role, departmentIDs, whatsappNumber, whatsappAdvanceNotification, siteSupervisors, siteAuditors, password }{ user: User, message: string }
/api/v1/users/:userId/statusPUTUpdate user status{ status: 'active' | 'disabled', reason?: string }{ success: boolean, message: string }
/api/v1/users/bulk-updatePOSTBulk update users{ userIds: string[], updates: Partial<User> }{ results: UpdateResult[] }
/api/v1/users/check-availabilityPOSTCheck username/email availability{ email: string }{ available: boolean }

Department and Site Assignment APIs

EndpointMethodPurposeRequest BodyResponse
/api/v1/users/:userId/departmentsGETGet user departments-{ departments: Department[] }
/api/v1/users/:userId/departmentsPUTUpdate user departments{ departmentIDs: string[] }{ success: boolean }
/api/v1/users/:userId/site-assignmentsGETGet user site assignments-{ supervisorSites: Site[], auditorSites: Site[] }
/api/v1/users/:userId/site-assignmentsPUTUpdate site assignments{ siteSupervisors: SiteAssignment[], siteAuditors: SiteAssignment[] }{ success: boolean }

User Role APIs

EndpointMethodPurposeRequest BodyResponse
/user-roles/user-to-rolePUTAssign role to user{ userID: string, role: string }{ success: boolean }
/api/v1/rolesGETGet all available roles-{ roles: Role[] }
/api/v1/roles/permissionsGETGet role permissions-{ permissions: RolePermission[] }

Off Days Management APIs

EndpointMethodPurposeRequest BodyResponse
/api/v1/auditor-off-daysGETGet auditor off daysQuery params: userId, startDate, endDate{ offDays: OffDay[] }
/api/v1/auditor-off-daysPOSTCreate off day{ userId, date, reason, type }{ offDay: OffDay }
/api/v1/auditor-off-days/:idPUTUpdate off day{ date, reason, type }{ offDay: OffDay }
/api/v1/auditor-off-days/:idDELETEDelete off day-{ success: boolean }

Customer Success APIs

EndpointMethodPurposeRequest BodyResponse
/api/v1/organizations/:orgId/usersDELETEHard delete users{ userIds: string[], confirmation: string, reason: string }{ deletedCount: number }
/api/v1/users/change-emailPOSTChange user email{ oldEmail: string, newEmail: string, userId: string }{ success: boolean }
/api/v1/users/bulk-role-changePOSTBulk role change{ userIds: string[], newRole: string }{ results: RoleChangeResult[] }

Firebase Functions

FunctionPurposeParametersResponse
createNewAuditorCreate user via Firebase{ email, displayName, phoneNumber }{ uid: string }
updateExistingAuditorUpdate Firebase user{ uid, email, displayName }{ success: boolean }
deleteFirebaseUserDelete Firebase user{ uid }{ success: boolean }

Package Dependencies

Core Dependencies

PackageVersionPurposeUsage in Module
react^17.0.2Core UI frameworkAll components
react-redux^7.2.4State managementRedux integration
redux^4.1.0State containerState management
redux-saga^1.1.3Side effectsAPI calls, async operations
react-router-dom^5.2.0RoutingNavigation between pages
typescript^4.3.5Type safetyType definitions

UI and Styling

PackageVersionPurposeUsage in Module
styled-components^5.3.0CSS-in-JSComponent styling
react-toastify^7.0.4Toast notificationsUser feedback
@loadable/component^5.15.0Code splittingLazy loading components
react-select^4.3.1Select componentsRole, department selection

Form and Validation

PackageVersionPurposeUsage in Module
libphonenumber-js^1.9.23Phone validationPhone number formatting
papaparse^5.3.1CSV parsingBulk user import
lodash^4.17.21UtilitiesDebounce, cloneDeep

Authentication and Security

PackageVersionPurposeUsage in Module
react-redux-firebase^3.10.0Firebase integrationAuthentication
firebase^8.6.8Backend servicesUser management

Nimbly-Specific Packages

PackageVersionPurposeUsage in Module
@nimbly-technologies/nimbly-commonLatestCommon utilitiesEnums, types, utilities
@nimbly-technologies/audit-componentLatestAudit componentsUI components

Development Dependencies

PackageVersionPurposeUsage in Module
@types/react^17.0.11TypeScript typesType definitions
@types/react-redux^7.1.16Redux typesType safety
@types/styled-components^5.1.10Styled components typesType safety
@types/lodash^4.14.170Lodash typesType definitions

Analytics and Monitoring

PackageVersionPurposeUsage in Module
react-ga^3.3.0Google AnalyticsUser action tracking
Custom MonitoringInternalError trackingError logging

Utility Libraries

PackageVersionPurposeUsage in Module
react-i18next^11.11.1InternationalizationMulti-language support
axios^0.21.1HTTP clientAPI requests
date-fns^2.22.1Date utilitiesDate formatting

Architecture Diagrams

Overall System Architecture

graph TB
    subgraph "Frontend Layer"
        A[React Components]
        B[Redux Store]
        C[Redux Sagas]
    end
    
    subgraph "Service Layer"
        D[User Service]
        E[Department Service]
        F[Site Service]
        G[Role Service]
    end
    
    subgraph "API Layer"
        H[REST APIs]
        I[Firebase Functions]
    end
    
    subgraph "Data Layer"
        J[Firebase Auth]
        K[Firestore DB]
        L[Cloud Storage]
    end
    
    A --> B
    B --> C
    C --> D
    C --> E
    C --> F
    C --> G
    
    D --> H
    E --> H
    F --> H
    G --> H
    
    H --> J
    H --> K
    I --> J
    I --> K
    
    A --> L

Component Hierarchy

graph TD
    A[App Root] --> B[Admin Routes]
    B --> C[AuditorsPage]
    
    C --> D[Layout]
    D --> E[AuditorManager]
    
    E --> F[AuditorListHeader]
    E --> G[AuditorList]
    E --> H[AuditorEditor Modal]
    E --> I[AuditorBulkModal]
    
    F --> J[Search Component]
    F --> K[Filter Components]
    F --> L[Action Buttons]
    
    G --> M[AuditorListContainer]
    M --> N[AuditorListTable]
    M --> O[AuditorListCard]
    M --> P[Pagination]
    
    H --> Q[AuditorEditorContainer]
    Q --> R[AuditorEditorForm]
    
    I --> S[BulkOperationForm]
    
    style A fill:#f9f,stroke:#333,stroke-width:4px
    style C fill:#bbf,stroke:#333,stroke-width:2px
    style E fill:#bfb,stroke:#333,stroke-width:2px

State Management Flow

stateDiagram-v2
    [*] --> Idle
    
    Idle --> Loading: FETCH_USERS_REQUEST
    Loading --> Success: FETCH_USERS_SUCCESS
    Loading --> Error: FETCH_USERS_FAILURE
    
    Success --> Editing: EDIT_USER
    Success --> Creating: CREATE_USER
    Success --> Deleting: DELETE_USER
    
    Editing --> Updating: SUBMIT_FORM
    Creating --> Saving: SUBMIT_FORM
    
    Updating --> Success: UPDATE_SUCCESS
    Updating --> Error: UPDATE_FAILURE
    
    Saving --> Success: CREATE_SUCCESS
    Saving --> Error: CREATE_FAILURE
    
    Deleting --> Success: DELETE_SUCCESS
    Deleting --> Error: DELETE_FAILURE
    
    Error --> Idle: CLEAR_ERROR
    Success --> Idle: CLEAR_STATE

User Lifecycle

graph LR
    A[User Creation] --> B{Validation}
    B -->|Valid| C[Active User]
    B -->|Invalid| D[Creation Failed]
    
    C --> E[User Updates]
    C --> F[Status Changes]
    C --> G[Role Changes]
    
    E --> C
    F --> H[Disabled User]
    G --> C
    
    H --> I[Reactivation]
    I --> C
    
    H --> J[Hard Delete]
    J --> K[Deleted]
    
    C --> L[Soft Delete]
    L --> H

Permission Hierarchy

graph TD
    A[Superadmin Level 99] --> B[Account Holder Level 98]
    B --> C[Admin Level 3]
    C --> D[Supervisor Level 2]
    D --> E[Auditor Level 1]
    
    A -.->|Can manage| B
    A -.->|Can manage| C
    A -.->|Can manage| D
    A -.->|Can manage| E
    
    B -.->|Can manage| C
    B -.->|Can manage| D
    B -.->|Can manage| E
    
    C -.->|Can manage| D
    C -.->|Can manage| E
    
    D -.->|Can manage| E
    
    style A fill:#f96,stroke:#333,stroke-width:4px
    style B fill:#f93,stroke:#333,stroke-width:3px
    style C fill:#f90,stroke:#333,stroke-width:2px

Advanced Features

Off-Days Management System

The Users module includes a comprehensive off-days management system for tracking auditor availability:

Architecture

graph TD
    A[Off Days Management] --> B[General Off Days]
    A --> C[User-Specific Off Days]
    A --> D[Off Days Dashboard]
    
    B --> E[Public Holidays]
    B --> F[Company Holidays]
    
    C --> G[Personal Leave]
    C --> H[Sick Leave]
    C --> I[Other Leave]
    
    D --> J[Calendar View]
    D --> K[List View]
    D --> L[Analytics]

Implementation

Off Days Manager Component (src/components/auditors/offDaysManager/OffDaysManager.tsx):

const OffDaysManager = () => {
  const [offDays, setOffDays] = useState<OffDay[]>([]);
  const [selectedDates, setSelectedDates] = useState<Date[]>([]);
  
  const handleAddOffDay = async (dates: Date[], reason: string, type: OffDayType) => {
    const newOffDays = dates.map(date => ({
      date: format(date, 'yyyy-MM-dd'),
      reason,
      type,
      userId: selectedUserId
    }));
    
    const results = await Promise.all(
      newOffDays.map(offDay => createOffDay(offDay))
    );
    
    toast.success(`Added ${results.length} off days`);
    refreshOffDays();
  };
  
  const handleRemoveOffDay = async (offDayId: string) => {
    await deleteOffDay(offDayId);
    toast.success('Off day removed');
    refreshOffDays();
  };
};

Features

  1. Date Picker Integration:

    • Multi-date selection
    • Date range selection
    • Blocked date highlighting
  2. Types of Off Days:

    • Personal leave
    • Sick leave
    • Public holidays
    • Training days
    • Other (custom reason)
  3. Validation:

    • Prevent past date selection (configurable)
    • Conflict detection
    • Maximum days limit
  4. Dashboard Analytics:

    • Off days by user
    • Off days by type
    • Monthly/yearly trends
    • Team availability overview

Bulk Operations System

The module supports various bulk operations for efficient user management:

Bulk User Import

CSV Template Structure:

Email Address,Full Name,Role,Phone Number (e.g. +628123456789),Department,Site Assignment
john.doe@example.com,John Doe,Auditor,+628123456789,Sales,Site A|Supervisor;Site B|Auditor
jane.smith@example.com,Jane Smith,Supervisor,+628123456790,Marketing,Site A|Supervisor

Import Process:

const processBulkImport = async (csvData: any[]) => {
  const validationResults = validateImportData(csvData);
  
  if (validationResults.errors.length > 0) {
    showValidationErrors(validationResults.errors);
    return;
  }
  
  const chunks = chunkArray(validationResults.valid, 5);
  let processed = 0;
  
  for (const chunk of chunks) {
    const results = await Promise.all(
      chunk.map(userData => createUserWithAssignments(userData))
    );
    
    processed += results.filter(r => r.success).length;
    updateProgress(processed / validationResults.valid.length);
  }
  
  showImportSummary({
    total: csvData.length,
    successful: processed,
    failed: csvData.length - processed
  });
};

Bulk Status Updates

const bulkStatusUpdate = async (userIds: string[], newStatus: UserStatus) => {
  // Group by current status for validation
  const usersByStatus = groupBy(userIds, userId => users[userId].status);
  
  // Validate transitions
  const validTransitions = validateStatusTransitions(usersByStatus, newStatus);
  
  if (validTransitions.invalid.length > 0) {
    toast.warning(`${validTransitions.invalid.length} users cannot be updated`);
  }
  
  // Execute updates
  const results = await batchUpdate(validTransitions.valid, {
    status: newStatus,
    updatedBy: currentUser.id,
    updatedAt: new Date().toISOString()
  });
  
  return results;
};

Advanced Search and Filtering

The Users module implements sophisticated search capabilities:

Search Implementation

const useUserSearch = () => {
  const [searchTerm, setSearchTerm] = useState('');
  const [filters, setFilters] = useState<UserFilters>({
    status: 'all',
    role: 'all',
    department: 'all',
    site: 'all'
  });
  
  const debouncedSearch = useMemo(
    () => debounce((term: string) => {
      dispatch(searchUsers(term));
    }, 300),
    []
  );
  
  const filteredUsers = useMemo(() => {
    let result = Object.values(users);
    
    // Text search
    if (searchTerm) {
      result = result.filter(user => 
        user.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
        user.displayName.toLowerCase().includes(searchTerm.toLowerCase()) ||
        user.phoneNumber?.includes(searchTerm)
      );
    }
    
    // Status filter
    if (filters.status !== 'all') {
      result = result.filter(user => user.status === filters.status);
    }
    
    // Role filter
    if (filters.role !== 'all') {
      result = result.filter(user => user.role === filters.role);
    }
    
    // Department filter
    if (filters.department !== 'all') {
      result = result.filter(user => 
        user.departments?.includes(filters.department)
      );
    }
    
    // Site filter
    if (filters.site !== 'all') {
      result = result.filter(user => {
        const siteIds = [
          ...user.siteSupervisors?.map(s => s.siteID) || [],
          ...user.siteAuditors?.map(s => s.siteID) || []
        ];
        return siteIds.includes(filters.site);
      });
    }
    
    return result;
  }, [users, searchTerm, filters]);
  
  return {
    searchTerm,
    setSearchTerm,
    filters,
    setFilters,
    filteredUsers,
    debouncedSearch
  };
};

Advanced Filters

  1. Multi-Select Filters:

    const multiSelectFilter = (users: User[], filterKey: string, values: string[]) => {
      if (values.length === 0) return users;
      
      return users.filter(user => {
        const userValues = user[filterKey] || [];
        return values.some(value => userValues.includes(value));
      });
    };
  2. Date Range Filters:

    const dateRangeFilter = (users: User[], startDate: Date, endDate: Date) => {
      return users.filter(user => {
        const createdDate = new Date(user.createdAt);
        return createdDate >= startDate && createdDate <= endDate;
      });
    };
  3. Custom Filters:

    const customFilters = {
      hasWhatsApp: (user: User) => !!user.whatsappNumber,
      isActive: (user: User) => user.status === 'active',
      hasMultipleSites: (user: User) => {
        const totalSites = (user.siteSupervisors?.length || 0) + 
                          (user.siteAuditors?.length || 0);
        return totalSites > 1;
      },
      noRecentActivity: (user: User) => {
        const lastActive = new Date(user.lastActiveAt);
        const thirtyDaysAgo = subDays(new Date(), 30);
        return lastActive < thirtyDaysAgo;
      }
    };

Analytics and Reporting

The Users module provides comprehensive analytics:

User Analytics Dashboard

const UserAnalytics = () => {
  const metrics = useUserMetrics();
  
  return (
    <Dashboard>
      <MetricCard
        title="Total Users"
        value={metrics.totalUsers}
        change={metrics.userGrowth}
        icon={<UsersIcon />}
      />
      
      <MetricCard
        title="Active Users"
        value={metrics.activeUsers}
        percentage={metrics.activePercentage}
        icon={<ActivityIcon />}
      />
      
      <Chart
        type="pie"
        data={metrics.usersByRole}
        title="Users by Role"
      />
      
      <Chart
        type="bar"
        data={metrics.usersByDepartment}
        title="Users by Department"
      />
      
      <ActivityHeatmap
        data={metrics.loginActivity}
        title="Login Activity"
      />
    </Dashboard>
  );
};

Report Generation

const generateUserReport = async (reportType: ReportType, filters: ReportFilters) => {
  const data = await fetchReportData(reportType, filters);
  
  switch (reportType) {
    case 'user-list':
      return generateUserListReport(data);
    case 'activity-summary':
      return generateActivityReport(data);
    case 'role-distribution':
      return generateRoleReport(data);
    case 'department-allocation':
      return generateDepartmentReport(data);
    default:
      throw new Error('Unknown report type');
  }
};

Integration Points

LMS Integration

The Users module integrates with the Learning Management System:

const syncUserWithLMS = async (userId: string) => {
  const user = await getUser(userId);
  
  const lmsUser = {
    id: user.id,
    email: user.email,
    name: user.displayName,
    role: mapRoleToLMS(user.role),
    departments: user.departments,
    active: user.status === 'active'
  };
  
  await lmsService.upsertUser(lmsUser);
  
  // Assign default courses based on role
  const defaultCourses = await getDefaultCoursesForRole(user.role);
  await lmsService.assignCourses(user.id, defaultCourses);
};

Site Scheduling Integration

Users are integrated with site scheduling:

const getUserAvailability = async (userId: string, startDate: Date, endDate: Date) => {
  const [schedule, offDays] = await Promise.all([
    getSiteSchedule(userId, startDate, endDate),
    getOffDays(userId, startDate, endDate)
  ]);
  
  const availability = eachDayOfInterval({ start: startDate, end: endDate })
    .map(date => {
      const dateStr = format(date, 'yyyy-MM-dd');
      const isOffDay = offDays.some(od => od.date === dateStr);
      const scheduledSites = schedule.filter(s => s.date === dateStr);
      
      return {
        date: dateStr,
        available: !isOffDay,
        scheduledSites,
        capacity: isOffDay ? 0 : (MAX_SITES_PER_DAY - scheduledSites.length)
      };
    });
    
  return availability;
};

Performance Optimization

Virtual Scrolling

For large user lists, the module implements virtual scrolling:

const VirtualizedUserList = ({ users, rowHeight = 50 }) => {
  const listRef = useRef<VariableSizeList>(null);
  const [scrollOffset, setScrollOffset] = useState(0);
  
  const getItemSize = (index: number) => {
    // Variable heights for expanded rows
    return expandedRows[index] ? rowHeight * 2 : rowHeight;
  };
  
  const Row = ({ index, style }) => {
    const user = users[index];
    return (
      <div style={style}>
        <UserRow user={user} expanded={expandedRows[index]} />
      </div>
    );
  };
  
  return (
    <VariableSizeList
      ref={listRef}
      height={600}
      itemCount={users.length}
      itemSize={getItemSize}
      onScroll={({ scrollOffset }) => setScrollOffset(scrollOffset)}
    >
      {Row}
    </VariableSizeList>
  );
};

Memoization Strategies

// Memoized selectors
const getUsersByRole = createSelector(
  [getUsers, getSelectedRole],
  (users, role) => {
    if (role === 'all') return users;
    return Object.values(users).filter(user => user.role === role);
  }
);
 
// Memoized components
const UserRow = memo(({ user, onEdit, onBlock }) => {
  return (
    <tr>
      <td>{user.email}</td>
      <td>{user.displayName}</td>
      <td>{user.role}</td>
      <td>
        <Button onClick={() => onEdit(user.id)}>Edit</Button>
        <Button onClick={() => onBlock(user.id)}>Block</Button>
      </td>
    </tr>
  );
}, (prevProps, nextProps) => {
  return prevProps.user.id === nextProps.user.id &&
         prevProps.user.status === nextProps.user.status;
});

Testing Strategy

Unit Tests

describe('UserService', () => {
  describe('createUser', () => {
    it('should create user with valid data', async () => {
      const userData = {
        email: 'test@example.com',
        displayName: 'Test User',
        role: 'auditor'
      };
      
      const result = await userService.createUser(userData);
      
      expect(result).toHaveProperty('id');
      expect(result.email).toBe(userData.email);
      expect(result.status).toBe('active');
    });
    
    it('should reject duplicate email', async () => {
      const userData = {
        email: 'existing@example.com',
        displayName: 'Test User',
        role: 'auditor'
      };
      
      await expect(userService.createUser(userData))
        .rejects.toThrow('Email already exists');
    });
  });
});

Integration Tests

describe('User Management Flow', () => {
  it('should complete full user lifecycle', async () => {
    // Create user
    const { getByText, getByLabelText } = render(<AuditorManager />);
    
    fireEvent.click(getByText('Add User'));
    
    fireEvent.change(getByLabelText('Email'), {
      target: { value: 'newuser@example.com' }
    });
    
    fireEvent.change(getByLabelText('Name'), {
      target: { value: 'New User' }
    });
    
    fireEvent.click(getByText('Save'));
    
    await waitFor(() => {
      expect(getByText('User created successfully')).toBeInTheDocument();
    });
    
    // Update user
    const userRow = getByText('newuser@example.com').closest('tr');
    fireEvent.click(within(userRow).getByText('Edit'));
    
    fireEvent.change(getByLabelText('Phone'), {
      target: { value: '+628123456789' }
    });
    
    fireEvent.click(getByText('Update'));
    
    await waitFor(() => {
      expect(getByText('User updated successfully')).toBeInTheDocument();
    });
    
    // Block user
    fireEvent.click(within(userRow).getByText('Block'));
    fireEvent.click(getByText('Confirm'));
    
    await waitFor(() => {
      expect(getByText('User blocked successfully')).toBeInTheDocument();
    });
  });
});