1. Overview

The Team Audit feature enables collaborative inspection of sites by multiple team members simultaneously. Team audits distribute sections among members, track progress collectively, and finalize reports with team leader approval.

Feature Dependencies:

Clean Architecture Approach

  • Presentation layer: UI components and screens
  • Business logic: Controllers and use cases
  • Data management: Repositories and services
  • Domain models: Entities and data structures

Functional Workflow

flowchart LR
    A[Team Lobby] --> B[Section Distribution]
    B --> C[Questionnaire Completion]
    C --> D[Data Aggregation]
    D --> E[Final Review]
    E --> F[Submission & Distribution]

    style A fill:#f9d5e5,stroke:#333,stroke-width:2px
    style C fill:#d5e5f9,stroke:#333,stroke-width:2px
    style E fill:#e5f9d5,stroke:#333,stroke-width:2px
    style F fill:#f9e5d5,stroke:#333,stroke-width:2px

Each phase uses specialized components that ensure data integrity, user collaboration, and accurate reporting.


2. Technical Architecture

2.1 Architecture Diagram

The Team Audit feature follows clean architecture principles with distinct layers:

flowchart TD
    A[UI Components] --> B[Controllers]
    B --> C[Use Cases]
    C --> D[Repositories]
    D --> E[API Clients]

    style A fill:#f9d5e5,stroke:#333,stroke-width:2px
    style B fill:#d5e5f9,stroke:#333,stroke-width:2px
    style C fill:#e5f9d5,stroke:#333,stroke-width:2px
    style D fill:#f9e5d5,stroke:#333,stroke-width:2px
    style E fill:#e5d5f9,stroke:#333,stroke-width:2px

2.2 Core Principles

  • Separation of Concerns: UI, business logic, and data access are clearly separated
  • Domain-Driven Design: Organization around business domains with well-defined models
  • Unidirectional Data Flow: Data flows from UI through controllers to use cases to repositories
  • State Management: Jotai for atomic state, React Query for server state
  • Form Handling: React Hook Form with Zod validation

2.3 Project Structure

packages/app/features/team-report/
├── _controllers/           # Business logic controllers
├── _domain/                # Domain models & interfaces
├── _use-cases/             # Business logic use cases
├── lobby/                  # Team lobby feature
│   ├── _components/        # UI components
│   ├── _controllers/       # Lobby controllers
│   ├── _repository/        # Data access
├── summary/                # Audit summary feature
│   ├── _components/        # Summary UI components
│   ├── _controllers/       # Summary controllers
│   ├── _repository/        # Data access
├── state.tsx               # Global state

2.4 Key Technologies

  1. React Native + Expo: Cross-platform mobile framework
  2. Tamagui: UI component library for consistent design
  3. Jotai: Atomic state management for component state
  4. React Query: Data fetching, caching, and synchronization
  5. React Hook Form: Form state management and validation
  6. Zod: Schema validation for forms and API responses
  7. TypeScript: Static typing for robust code

3. Key Components and Their Functions

3.1 Team Report Controllers

Controllers manage business logic between UI and data layers.

3.1.1 Team Start Controller

Located at _controllers/team-report-start-controller.ts

// Initializes a new team report
public useStartReport = () => {
  const mutation = useMutation({
    mutationFn: async (schedule: SelectedTeamSchedule) => {
      return teamReportRepository.startTeamReport({
        scheduleID: schedule.id,
        siteID: schedule.siteID,
      });
    },
    onSuccess: (data) => {
      setAtom(this.reportIDAtom, data.reportID);
      this.navigateToOverview(data.reportID);
    },
  });
 
  return {
    startReport: mutation.mutate,
    mutation,
  };
};

3.1.2 Team Summary Controller

Located at summary/_controllers/team-summary-controller.ts

// Fetches team audit summary data
public useTeamAuditSummary = () => {
  const mongoReportId = this.useMongoReportId();
 
  return useQuery({
    queryKey: ['team-audit-summary', mongoReportId],
    queryFn: async () => {
      return teamSummaryService.getTeamAuditReportSummaryUIData({
        reportID: mongoReportId
      });
    },
    refetchOnReconnect: 'always',
  });
};
 
// Handles report form for signature and email config
public useTeamReportForm = () => {
  const teamReportForm = useForm<TeamReportFormModel>({
    resolver: zodResolver(teamReportFormSchema),
  });
 
  const submissionMutation = useMutation({
    mutationFn: async (formData: TeamReportFormModel) => {
      const result = await submitTeamReportUseCase.execute({
        formData,
        summaryData,
        generateSignaturePromise,
        leaderId,
      });
      return result.payoffData;
    },
    // Mutation lifecycle handlers
  });
 
  return {
    form: teamReportForm,
    isSubmitting: submissionMutation.isPending,
    error: submissionMutation.error,
    handleSubmit: teamReportForm.handleSubmit(submissionMutation.mutateAsync),
  };
};

3.2 Use Cases

Use cases implement specific business logic operations.

3.2.1 Submit Team Report Section Use Case

Located at _use-cases/submit-team-report-section-usecase.ts

// Handles section submission with attachment validation and upload
public execute = async (params: ExecuteParams) => {
  // Validate attachments if enabled
  if (!params.forceSubmit && this.validateAttachments) {
    await this.validateLocalAttachment(params);
  }
 
  // Upload attachments with progress tracking
  const questions = await this.uploadAllQuestionAttachment(params);
 
  // Submit the complete section data
  return this.submit({
    report: { ...params.payload, questions },
    siteID: params.siteID,
    firebaseReportID: params.firebaseReportID,
    sectionIndex: params.sectionIndex,
  });
};

3.2.2 Submit Team Report Use Case

Located at summary/_use-cases/summary-usecase.ts

// Handles final report submission
public execute = async (params: SubmitParams): Promise<SubmitResult> => {
  try {
    // Prepare submission data and process signatures
    const requestBody = await teamSummaryService.prepareSubmissionData(
      {
        signatures: params.formData.signatures,
        emailTargets: params.formData.emailTargets,
      },
      params.generateSignaturePromise
    );
 
    // Submit the report to the API
    const response = await teamSummaryRepository.postTeamAuditReport({
      reportId: params.summaryData.reportID,
      siteId: params.summaryData.siteID,
      requestBody,
    });
 
    // Process the response for UI payoff
    const payoffData = teamSummaryService.parsedSubmitResponseForPayoff(response);
 
    return {
      success: true,
      payoffData,
    };
  } catch (error) {
    return {
      success: false,
      error: error.message || 'Failed to submit report',
    };
  }
};

3.3 Domain Models

Domain models define the data structures used in the application.

3.3.1 Team Summary View Model

Located at _domain/team-summary-view-model.ts

// Core data structures for the Team Audit Summary
interface IFlagCount {
  red: number;
  green: number;
  yellow: number;
}
 
interface ISection {
  approvalIndex: number;
  sectionIndex: number;
  sectionName: string;
  isRequired: boolean;
  approvalStatus: string;
}
 
interface ISignature {
  isSigned: boolean;
  name: string;
  path: string;
  position: string;
}
 
interface ITeamAuditSummary {
  auditName: string;
  siteName: string;
  datetimeIn: string;
  flagCount: IFlagCount;
  sections: ISection[];
  signatures: ISignature[];
  emailTargets: IEmailTarget[] | null;
  reportID: string;
  siteID: string;
}

3.3.2 Team Summary Request/Response Models

Located at summary/_domain/team-summary-model.ts

// Form data model with Zod validation
export const teamReportFormSchema = z.object({
  signatures: z.array(
    z.object({
      name: z.string().min(1, "Name is required"),
      position: z.string().min(1, "Position is required"),
      isSigned: z.boolean(),
      path: z.string().min(1, "Signature is required"),
    })
  ),
  emailTargets: z.array(
    z.object({
      email: z.string().email("Invalid email"),
      enabled: z.boolean(),
      setByAdmin: z.boolean().optional(),
      status: z.enum(["unsent", "sent", "failed"]).optional(),
    })
  ),
});
 
// Submit request model
export class TeamAuditRequestModel {
  signatures: ReportSignature[];
  emailTargets: EmailTarget[];
}
 
// API response schema
export const teamAuditSubmitResponse = z.object({
  data: z.object({
    siteName: z.string(),
    flagCount: z.object({
      red: z.number(),
      yellow: z.number(),
      green: z.number(),
    }),
    dateOfVisit: z.string(),
    timeOfVisit: z.string(),
    duration: z.string(),
  }),
  message: z.string(),
});

3.4 Core UI Components

The Team Audit feature includes several specialized UI components.

3.4.1 Team Audit Summary Component

Located at summary/team-audit-summary.tsx

const TeamAuditSummary = () => {
  const { LL } = useLocalization();
  const { data: summaryData } = teamSummaryController.useTeamAuditSummary();
  const { form } = teamSummaryController.useTeamReportForm();
 
  return (
    <YStack flex={1} position="relative">
      <FormProvider {...form}>
        <ScrollView flex={1}>
          {/* Site details showing location and audit info */}
          <SiteDetailsCard>
            <XStack alignItems="center" gap="$tw3">
              <LocationIconContainer>
                <Octicons name="location" size={18} color="white" />
              </LocationIconContainer>
              <YStack flex={1} gap="$tw1">
                <Text bold color="white">
                  {summaryData?.siteName}
                </Text>
                <Text caption bold color="white">
                  {summaryData?.auditName}
                </Text>
                <Text caption color="white">
                  {`Report Started: ${dayjs(summaryData?.datetimeIn).format(
                    "hh:mm A Z"
                  )}`}
                </Text>
              </YStack>
              <Image source={SiteDetails} marginRight={-8} />
            </XStack>
          </SiteDetailsCard>
 
          {/* Flag summary showing audit health */}
          <YStack paddingHorizontal="$tw4">
            <Text bold>{LL.questionnaire.summary.reportSummary()}</Text>
            <XStack justifyContent="center" paddingVertical="$tw2">
              <FlagSummary
                totalGreenFlag={summaryData?.flagCount.green}
                totalYellowFlag={summaryData?.flagCount.yellow}
                totalRedFlag={summaryData?.flagCount.red}
              />
            </XStack>
          </YStack>
 
          {/* Section preview, signature and email components */}
          <SectionPreview />
          <TeamLeaderSignature summaryData={summaryData} />
          <EmailSection />
          <ConfirmSubmit />
        </ScrollView>
      </FormProvider>
 
      {/* Upload progress indicator */}
      <UploadProgress summaryData={summaryData} />
    </YStack>
  );
};

3.4.2 Sections List Component

Located at lobby/_components/sections-list.ts

const QuestionnaireSectionsList = () => {
  const { LL } = useLocalization();
  const {
    data: sectionData,
    isLoading,
    isRefetching,
  } = teamLobbyController.useSectionList();
  const { height } = useWindowDimensions();
 
  if (isLoading && !isRefetching) {
    return (
      <SectionListContainer height={height / 2} alignItems="center">
        <Spinner size="large" />
      </SectionListContainer>
    );
  }
 
  if (!sectionData) {
    return (
      <SectionListContainer height={height / 2} alignItems="center">
        <Text>{LL.schedule.schedule.teamAudit.noSections()}</Text>
      </SectionListContainer>
    );
  }
 
  return (
    <SectionListScrollView>
      {/* Required sections */}
      <SectionItemList
        title={LL.schedule.schedule.teamAudit.required()}
        items={sectionData.filter((item) => item.required === true)}
        isLoading={isRefetching}
      />
 
      {/* Optional sections */}
      <SectionItemList
        title={LL.schedule.schedule.teamAudit.optional()}
        items={sectionData.filter((item) => item.required !== true)}
        isLoading={isRefetching}
      />
    </SectionListScrollView>
  );
};

4. Detailed Team Audit Workflow

The team audit process follows a specific flow from initialization to completion. This section details each stage of the workflow, the components involved, and the data flow between them.

4.1 Workflow Sequence Diagram

sequenceDiagram
    participant TL as Team Lobby
    participant SS as Schedule Selection
    participant TRI as Team Report Init
    participant QD as Questionnaire Distribution
    participant SC as Section Completion
    participant SUM as Summary Compilation
    participant FR as Final Review
    participant TLS as Team Leader Signature
    participant EC as Email Configuration
    participant RD as Report Distribution

    TL->>SS: Select Schedule
    SS->>TRI: Initialize Team Report
    TRI->>QD: Distribute Sections
    QD->>SC: Complete Sections
    SC->>SUM: Compile Summary
    SUM->>FR: Review
    FR->>TLS: Add Signature
    TLS->>EC: Configure Emails
    EC->>RD: Distribute Report

4.2 Initialization Phase

4.2.1 Team Lobby Screen

Located at packages/app/features/team-report/lobby/lobby-screen.tsx, this screen serves as the gathering point for team members.

Data Flow:

  1. Team members access the lobby screen
  2. Team leader is identified based on role permissions
  3. Available schedules are fetched and displayed
  4. Team leader selects a schedule for auditing

API Integration:

  • The lobby screen fetches available schedules using a query like:

    const { data: schedules } = useQuery({
      queryKey: ["schedules", "active"],
      queryFn: () => scheduleRepository.getActiveSchedules(),
    });

4.2.2 Schedule Selection

The schedule selection process is managed by the useSelectSchedule hook from ReportTeamStartController:

Implementation:

// In team-report-start-controller.ts
public useSelectSchedule = () => {
  return {
    select: (params: SelectedTeamSchedule) => {
      // Implementation to select a schedule and update state
      setAtom(this.selectedScheduleAtom, params);
      return { success: true };
    },
  };
};

Data Flow:

  1. Team leader selects a schedule from the list
  2. The select function is called with the schedule details
  3. The selected schedule is stored in the selectedScheduleAtom
  4. The UI updates to show schedule details and enable the “Start Audit” button

4.2.3 Team Report Initialization

When the team leader starts the audit, the useStartReport hook is used:

Implementation:

// In team-report-start-controller.ts
public useStartReport = () => {
  const mutation = useMutation({
    mutationFn: async (selectedSchedule: SelectedTeamSchedule) => {
      // Call the API to start a team report
      const response = await teamReportRepository.startTeamReport({
        scheduleID: selectedSchedule.id,
        siteID: selectedSchedule.siteID,
      });
      return response;
    },
    onSuccess: (data) => {
      // Handle successful initialization
      // Store report ID and navigate to first section
    },
  });
 
  return {
    startReport: (selectedSchedule: SelectedTeamSchedule) => {
      mutation.mutate(selectedSchedule);
    },
    mutation,
  };
};

API Request:

// Sample API request for starting a team report
const startTeamReport = async (params: {
  scheduleID: string;
  siteID: string;
}) => {
  const response = await api.post("/v1.0/team-reports/start", params);
  if (response.ok && response.data) {
    return response.data;
  }
  throw new Error("Failed to start team report");
};

API Response:

{
  "data": {
    "reportID": "team-report-123",
    "startTime": "2023-04-15T09:30:00Z",
    "sections": [
      {
        "sectionIndex": 0,
        "sectionName": "General Information",
        "status": "not_started"
      },
      {
        "sectionIndex": 1,
        "sectionName": "Safety Protocols",
        "status": "not_started"
      }
      // Additional sections...
    ]
  },
  "success": true
}

4.3 Questionnaire Completion Phase

4.3.1 Section Distribution

After initialization, team members can access different sections of the questionnaire:

Implementation:

// In team-report-start-controller.ts
public useStartSection = () => {
  const mutation = useMutation({
    mutationFn: async (params: { sectionIndex: number }) => {
      // API call to claim a section for editing
      const response = await teamReportRepository.claimSection({
        reportID: currentReportID,
        sectionIndex: params.sectionIndex,
      });
      return response;
    },
    onSuccess: (data, variables) => {
      // Navigate to the section questionnaire
      router.push(`/questionnaire/team/${currentReportID}/section/${variables.sectionIndex}`);
    },
  });
 
  return {
    start: (params: { sectionIndex: number }) => {
      mutation.mutate(params);
    },
    mutationState: mutation,
  };
};

Data Flow:

  1. Team members view available sections on the main screen
  2. A team member selects a section to work on
  3. The start function is called with the section index
  4. The API is called to claim the section for that team member
  5. On success, navigation occurs to the section questionnaire

4.3.2 Question Answering

Team members complete the questionnaire sections by answering questions:

UI Implementation:

// Sample question component
const QuestionItem = ({ question, onChange }) => {
  return (
    <YStack gap="$2">
      <Text bold>{question.text}</Text>
      {question.type === "text" && (
        <Input
          value={question.answer}
          onChangeText={(text) => onChange(question.id, text)}
        />
      )}
      {question.type === "yesno" && (
        <XStack gap="$2">
          <Button
            variant={question.answer === "yes" ? "filled" : "outlined"}
            onPress={() => onChange(question.id, "yes")}
          >
            Yes
          </Button>
          <Button
            variant={question.answer === "no" ? "filled" : "outlined"}
            onPress={() => onChange(question.id, "no")}
          >
            No
          </Button>
        </XStack>
      )}
      {/* Other question types */}
    </YStack>
  );
};

Data Management:

  • Answers are stored in local state using React Hook Form
  • File attachments are handled with a separate component
  • Real-time validation provides feedback to users

4.3.3 Section Submission

When a team member completes a section, they submit it using the SubmitTeamReportSectionUsecase:

Implementation Flow:

  1. User clicks the “Submit Section” button
  2. The controller calls the use case’s execute method
  3. The use case validates attachments
  4. Attachments are uploaded with progress tracking
  5. Section data is submitted to the backend
  6. User is redirected to the section selection screen

Sequence Diagram:

┌─────────────┐    ┌─────────────────┐    ┌──────────────────────────┐    ┌───────────────────┐
│             │    │                 │    │                          │    │                   │
│ UI Component│    │ Controller      │    │ Submit Section Use Case  │    │ Repository        │
│             │    │                 │    │                          │    │                   │
└──────┬──────┘    └────────┬────────┘    └───────────┬──────────────┘    └─────────┬─────────┘
       │                    │                         │                             │
       │ Submit Section     │                         │                             │
       │───────────────────▶│                         │                             │
       │                    │ Execute Use Case        │                             │
       │                    │────────────────────────▶│                             │
       │                    │                         │                             │
       │                    │                         │ Validate Attachments        │
       │                    │                         │◀─────────────────────────  │
       │                    │                         │                             │
       │                    │                         │ Upload Attachments          │
       │                    │                         │──────────────────────────  ▶│
       │                    │                         │                             │
       │                    │                         │ Submit Section Data         │
       │                    │                         │──────────────────────────  ▶│
       │                    │ Return Result           │                             │
       │                    │◀────────────────────────│                             │
       │ Update UI          │                         │                             │
       │◀───────────────────│                         │                             │
       │                    │                         │                             │
┌──────┴──────┐    ┌────────┴────────┐    ┌───────────┴──────────────┐    ┌─────────┴─────────┐
│             │    │ Controller      │    │ Submit Section Use Case  │    │ Repository        │
│ UI Component│    │                 │    │                          │    │                   │
└─────────────┘    └─────────────────┘    └──────────────────────────┘    └───────────────────┘

4.4 Summary and Finalization Phase

4.4.1 Summary Compilation

After all sections are completed, the team leader accesses the summary screen:

Implementation:

// In team-summary-controller.ts
public useTeamAuditSummary = () => {
  const { reportID } = useReportContext();
 
  return useQuery({
    queryKey: ['team-report', reportID, 'summary'],
    queryFn: async () => {
      const response = await teamReportRepository.getTeamAuditSummary(reportID);
      return response.data;
    },
  });
};

Data Flow:

  1. Team leader navigates to the summary screen
  2. The controller fetches summary data from the API
  3. Data is transformed to match the ITeamAuditSummary interface
  4. The UI renders the complete audit summary

API Response Structure:

{
  "data": {
    "auditName": "Quarterly Safety Audit",
    "siteName": "Main Manufacturing Plant",
    "datetimeIn": "2023-04-15T09:30:00Z",
    "flagCount": {
      "red": 3,
      "yellow": 5,
      "green": 42
    },
    "sections": [
      {
        "approvalIndex": 1,
        "sectionIndex": 0,
        "sectionName": "General Information",
        "isRequired": true,
        "approvalStatus": "approved"
      }
      // More sections...
    ],
    "signatures": [
      {
        "isSigned": false,
        "name": "",
        "path": "",
        "position": "Team Leader"
      }
    ],
    "emailTargets": [
      {
        "email": "manager@example.com",
        "enabled": true,
        "setByAdmin": true,
        "status": "unsent"
      }
    ],
    "reportID": "team-report-123",
    "siteID": "site-456"
  }
}

4.4.2 Final Review and Signature

The team leader reviews the complete report and provides a signature:

Signature Component:

// In team-leader-signature.tsx
const TeamLeaderSignature = ({ summaryData }) => {
  const { form } = teamSummaryController.useTeamReportForm();
  const signature = form.watch("signature");
 
  return (
    <YStack gap="$3" padding="$4">
      <Text bold>Team Leader Signature</Text>
 
      {!signature ? (
        <Button onPress={() => navigation.push("/signature")}>
          Add Signature
        </Button>
      ) : (
        <XStack>
          <Image source={{ uri: signature.path }} width={150} height={80} />
          <YStack>
            <Text>{signature.name}</Text>
            <Text caption>{signature.position}</Text>
          </YStack>
        </XStack>
      )}
    </YStack>
  );
};

Signature Capture Flow:

  1. Team leader clicks “Add Signature” button
  2. Navigation to signature capture screen
  3. Team leader draws signature and enters name/position
  4. Signature is saved as an image and stored with form data
  5. UI updates to show the captured signature

4.4.3 Email Configuration

The team leader configures email recipients for report distribution:

Email Section Component:

// In email-section.tsx
const EmailSection = () => {
  const { form } = teamSummaryController.useTeamReportForm();
  const { fields, append, remove } = useFieldArray({
    control: form.control,
    name: "emailTargets",
  });
 
  return (
    <YStack gap="$3" padding="$4">
      <Text bold>Email Report To</Text>
 
      {fields.map((field, index) => (
        <XStack key={field.id} alignItems="center">
          <Controller
            control={form.control}
            name={`emailTargets.${index}.email`}
            render={({ field }) => (
              <Input
                value={field.value}
                onChangeText={(text) =>
                  field.onChange({ ...field.value, email: text })
                }
                placeholder="Email address"
                flex={1}
              />
            )}
          />
          <Controller
            control={form.control}
            name={`emailTargets.${index}.enabled`}
            render={({ field }) => (
              <Switch checked={field.value} onCheckedChange={field.onChange} />
            )}
          />
          <Button icon="trash" onPress={() => remove(index)} />
        </XStack>
      ))}
 
      <Button
        onPress={() => append({ email: "", enabled: true, setByAdmin: false })}
      >
        Add Email
      </Button>
    </YStack>
  );
};

Email Configuration Flow:

  1. Pre-configured emails (set by admin) are displayed and can’t be removed
  2. Team leader can add additional email recipients
  3. Each recipient can be enabled/disabled
  4. Email validation ensures correct format
  5. Configuration is saved with the report data

4.4.4 Report Submission with JSON Export

Finally, the team leader submits the complete report, which now includes automatic JSON export when the feature flag is enabled:

Submit Implementation with Export:

// In team-summary-controller.ts
public useSubmitTeamReport = () => {
  const { reportID } = useReportContext();
  const { LL } = useLocalization();
  const allOrgFeatures = useOrgFeatures();
 
  return useMutation({
    mutationFn: async (data: SubmitTeamReportParams) => {
      // Check if JSON export is enabled
      if (allOrgFeatures?.features?.EXPORT_JSON_REPORT_ON_SUBMIT_SYNC) {
        try {
          // Export team audit data to JSON
          const auditDataExportUseCase = auditDataExportFactory.getInstance(LL);
          const exportResult = await auditDataExportUseCase.exportAuditData({
            summaryData: data.summaryData,
            leaderId: data.leaderId,
          });
 
          // Log export result (non-blocking)
          if (exportResult.success) {
            observabilitySentry.logEvent('Team audit data exported', {
              filePath: exportResult.filePath,
              fileSize: exportResult.fileSize,
            });
          }
        } catch (error) {
          // Export failure doesn't block submission
          observabilitySentry.logEvent('Export failed', error);
        }
      }
 
      // Continue with normal submission
      const response = await teamReportRepository.submitTeamReport({
        reportID,
        signature: data.signature,
        emailTargets: data.emailTargets,
      });
      return response;
    },
    onSuccess: () => {
      // Handle successful submission
      // Navigate to confirmation screen
    },
  });
};

Submission Flow with Export:

sequenceDiagram
    participant TL as Team Leader
    participant TSC as TeamSummaryController
    participant Export as AuditDataExportUseCase
    participant FS as FileSystem
    participant API as Backend API

    TL->>TSC: Click Submit
    TSC->>TSC: Validate Sections
    TSC->>TSC: Check Feature Flag

    alt Export Enabled
        TSC->>Export: exportAuditData(summaryData)
        Export->>FS: Save JSON File
        FS-->>TSC: Export Result
        Note over TSC: Log result (non-blocking)
    end

    TSC->>API: Submit Team Report
    API-->>TSC: Submission Confirmed
    TSC-->>TL: Navigate to Payoff
  1. Team leader clicks “Submit Report” button
  2. Validation ensures all required sections are complete
  3. Validation checks for team leader signature
  4. If feature flag enabled: JSON export initiated (non-blocking)
  5. The controller calls the repository to submit the report
  6. Success/error state is displayed to the user
  7. On success, navigation to completion confirmation screen

Exported Team Audit Data Structure:

{
  "auditName": "Monthly Team Audit",
  "siteName": "Main Branch",
  "datetimeIn": "2025-01-19T10:00:00Z",
  "flagCount": {
    "red": 5,
    "green": 20,
    "yellow": 3
  },
  "sections": [
    {
      "sectionIndex": 0,
      "sectionName": "Kitchen",
      "isRequired": true,
      "approvalStatus": "approved",
      "auditor": "user_123"
    }
  ],
  "signatures": [...],
  "emailTargets": [...],
  "reportID": "team_123",
  "siteID": "site_456"
}

Export Features:

  • Automatic Export: Triggered during team report submission
  • Complete Summary: Exports the full ITeamAuditSummary object
  • Non-Blocking: Export failures don’t prevent submission
  • Platform Support: Android SAF, iOS sharing, Web download
  • File Naming: team_report_[reportID]_[timestamp].json
  • Feature Flag: Controlled by EXPORT_JSON_REPORT_ON_SUBMIT_SYNC

4.5 Report Distribution Phase

After successful submission, the report is distributed according to the configured email settings:

API Implementation:

// In team-report-repository.ts
public sendReportEmails = async (params: { reportID: string }) => {
  const response = await this.api.post(`/v1.0/team-reports/${params.reportID}/send-emails`);
  if (response.ok && response.data) {
    return response.data;
  }
  throw new Error('Failed to send report emails');
};

5. Data Flow and API Integration

5.1 Data Flow Architecture

The Team Audit feature follows a unidirectional data flow pattern:

flowchart TD
    UI[UI Components] --> CTRL[Controllers]
    CTRL --> UC[Use Cases]
    UC --> REPO[Repositories]
    REPO --> API[API Client]
    API --> BS[Backend Services]

    style UI fill:#f9d5e5,stroke:#333,stroke-width:2px
    style CTRL fill:#d5e5f9,stroke:#333,stroke-width:2px
    style UC fill:#e5f9d5,stroke:#333,stroke-width:2px
    style REPO fill:#f9e5d5,stroke:#333,stroke-width:2px
    style API fill:#e5d5f9,stroke:#333,stroke-width:2px
    style BS fill:#f5f5f5,stroke:#333,stroke-width:2px

5.2 Key API Endpoints

EndpointMethodPurposeLink
/v1.0/team-[reports](../../Reports/ReportsOverview.md)/startPOSTInitialize team auditAPI Definition
/v1.0/team-[reports](../../Reports/ReportsOverview.md)/{reportID}/sections/{sectionIndex}/claimPOSTClaim sectionAPI Definition
/v1.0/team-[reports](../../Reports/ReportsOverview.md)/{reportID}/summaryGETGet audit summaryAPI Definition
/v1.0/team-[reports](../../Reports/ReportsOverview.md)/{reportID}/submitPOSTSubmit auditAPI Definition

5.3 State Management

The Team Audit feature uses a combination of Jotai atoms for UI state and React Query for server state:

// Core state atoms in state.tsx
export const teamAuditProgressUploadAtom = atom<boolean>(false);
export const mongoReportIdAtom = atom<string>("");
export const auditorColorsAtom = atom<Record<string, string>>({});
 
// React Query usage for server state
const useTeamAuditSummary = () => {
  const mongoReportId = useAtomValue(mongoReportIdAtom);
 
  return useQuery({
    queryKey: ["team-audit-summary", mongoReportId],
    queryFn: () =>
      teamSummaryService.getTeamAuditReportSummaryUIData({
        reportID: mongoReportId,
      }),
    refetchOnReconnect: "always",
  });
};

5.4 Attachment Handling Process

File attachments follow a specialized workflow:

flowchart LR
    A[Select Files] --> B[Validate] --> C[Upload]
    C --> D[Update Progress] --> E[Update References]

    style A fill:#f9d5e5,stroke:#333,stroke-width:2px
    style C fill:#d5e5f9,stroke:#333,stroke-width:2px
    style E fill:#e5f9d5,stroke:#333,stroke-width:2px

Implementation summary:

  • Files are selected using device native pickers
  • Validation ensures proper format and size
  • Upload process shows progress to the user
  • Successfully uploaded files receive secure URLs
  • References are stored with the question data

6. Form Management and Validation

The Team Audit feature utilizes a comprehensive form management system built on React Hook Form with Zod validation schema.

6.1 Form Architecture

flowchart TD
    FP[FormProvider] --> HF[React Hook Form]
    HF --> ZV[Zod Validation]
    HF --> FC[Form Components]
    FC --> CMP[Controller Pattern]
    FC --> FLD[Field Arrays]
    CMP --> I[Inputs]
    CMP --> S[Selectors]
    CMP --> F[File Uploads]
    CMP --> CB[Custom Buttons]

    style FP fill:#f9d5e5,stroke:#333,stroke-width:2px
    style HF fill:#d5e5f9,stroke:#333,stroke-width:2px
    style ZV fill:#e5f9d5,stroke:#333,stroke-width:2px
    style FC fill:#f9e5d5,stroke:#333,stroke-width:2px
    style CMP fill:#e5d5f9,stroke:#333,stroke-width:2px

6.2 Core Form Implementation

Located at packages/app/features/team-report/summary/team-summary-controller.ts:

// Form initialization with validation
public useTeamReportForm = () => {
  const { data: summaryData } = this.useTeamAuditSummary();
 
  // Define validation schema using Zod
  const validationSchema = z.object({
    signature: z.object({
      isSigned: z.boolean(),
      name: z.string().min(1, { message: "Name is required" }),
      path: z.string().min(1, { message: "Signature is required" }),
      position: z.string().min(1, { message: "Position is required" }),
    }),
    emailTargets: z.array(
      z.object({
        email: z.string().email({ message: "Invalid email address" }),
        enabled: z.boolean(),
        setByAdmin: z.boolean().optional(),
        status: z.enum(['unsent', 'sent', 'failed']).optional(),
      })
    ).refine(
      (targets) => targets.some(target => target.enabled),
      { message: "At least one email recipient must be enabled" }
    ),
  });
 
  // Initialize form with default values
  const form = useForm({
    resolver: zodResolver(validationSchema),
    defaultValues: {
      signature: summaryData?.signatures[0] || {
        isSigned: false,
        name: '',
        path: '',
        position: ''
      },
      emailTargets: summaryData?.emailTargets || [],
    },
  });
 
  return { form };
};

6.3 Form Component Integration

Forms are used in components via the FormProvider to ensure context is properly passed down:

// Example from TeamAuditSummary component
const TeamAuditSummary = () => {
  const { form } = teamSummaryController.useTeamReportForm();
 
  return (
    <YStack flex={1}>
      <FormProvider {...form}>
        <ScrollView>
          {/* Form components that access the form context */}
          <TeamLeaderSignature />
          <EmailSection />
          <SubmitButton />
        </ScrollView>
      </FormProvider>
    </YStack>
  );
};

6.4 Form Field Components

Individual form fields use the Controller component for proper integration:

// Example email field component
const EmailField = ({ index, readOnly }) => {
  return (
    <Controller
      name={`emailTargets.${index}`}
      render={({ field, fieldState }) => (
        <XStack alignItems="center" gap="$2">
          <Input
            value={field.value.email}
            onChangeText={(text) =>
              field.onChange({ ...field.value, email: text })
            }
            editable={!readOnly && !field.value.setByAdmin}
            placeholder="Enter email address"
            flex={1}
            error={!!fieldState.error}
          />
 
          <Switch
            checked={field.value.enabled}
            onCheckedChange={(checked) =>
              field.onChange({ ...field.value, enabled: checked })
            }
            disabled={readOnly}
          />
 
          {!field.value.setByAdmin && !readOnly && (
            <Button
              icon="trash"
              variant="ghost"
              onPress={() => remove(index)}
            />
          )}
 
          {fieldState.error && (
            <Text color="$error" fontSize="$xs">
              {fieldState.error.message}
            </Text>
          )}
        </XStack>
      )}
    />
  );
};

6.5 Form Validation Strategies

The Team Audit implementation includes several validation strategies:

  1. Schema Validation: Using Zod for structural typing and schema validation
  2. Field-Level Validation: Immediate validation of individual fields
  3. Form-Level Validation: Cross-field validation to ensure consistency
  4. API Validation: Backend validation for security and ensuring business rules
  5. Custom Error Handling: Formatted error display with contextual guidance

7. UI Components and User Experience

7.1 Key UI Components

The Team Audit feature includes several specialized UI components that enhance the user experience.

classDiagram
    class SiteDetailsCard {
        +location: string
        +siteName: string
        +auditName: string
        +startTime: Date
        +render()
    }

    class FlagSummary {
        +green: number
        +yellow: number
        +red: number
        +total: number
        +renderCounts()
    }

    class SectionPreview {
        +sections: Section[]
        +renderSectionList()
        +renderSectionStatus(status)
    }

    class TeamLeaderSignature {
        +signature: SignatureData
        +handleCaptureSignature()
        +renderSignatureDisplay()
    }

    class EmailSection {
        +emails: EmailTarget[]
        +addEmail()
        +removeEmail()
        +toggleEmail()
    }

    class ConfirmSubmit {
        +isSubmitting: boolean
        +handleSubmit()
        +validateBeforeSubmit()
    }

    class UploadProgress {
        +progress: number
        +isUploading: boolean
        +renderProgressBar()
    }

7.2 Site Details Card

Located at packages/app/features/team-report/summary/_components/shared-ui.tsx:

// Site details card implementation
export const SiteDetailsCard = styled(XStack, {
  backgroundColor: "$primary",
  borderRadius: "$4",
  marginHorizontal: "$4",
  padding: "$4",
  shadowColor: "rgba(0, 0, 0, 0.1)",
  shadowOffset: { width: 0, height: 2 },
  shadowOpacity: 1,
  shadowRadius: 4,
  elevation: 2,
});
 
// Usage in components
const SiteInfo = ({ summaryData }) => (
  <SiteDetailsCard>
    <XStack alignItems="center" gap="$3">
      <LocationIconContainer>
        <Octicons name="location" size={18} color="white" />
      </LocationIconContainer>
      <YStack flex={1} gap="$1">
        <Text bold color="white">
          {summaryData?.siteName}
        </Text>
        <Text caption bold color="white">
          {summaryData?.auditName}
        </Text>
        <Text caption color="white">
          {`Started: ${formatDateTime(summaryData?.datetimeIn)}`}
        </Text>
      </YStack>
      <Image source={SiteDetailsIllustration} marginRight={-8} />
    </XStack>
  </SiteDetailsCard>
);

7.3 Flag Summary Component

Located at packages/app/features/team-report/summary/_components/flag-summary.ts`:

// Flag summary visualizing audit health
const FlagSummary = ({
  totalGreenFlag = 0,
  totalYellowFlag = 0,
  totalRedFlag = 0,
}) => {
  const total = totalGreenFlag + totalYellowFlag + totalRedFlag;
 
  return (
    <YStack alignItems="center" gap="$2">
      <XStack gap="$4">
        <FlagCount count={totalGreenFlag} color="green" />
        <FlagCount count={totalYellowFlag} color="yellow" />
        <FlagCount count={totalRedFlag} color="red" />
      </XStack>
      <Text caption>Total: {total} questions</Text>
    </YStack>
  );
};
 
const FlagCount = ({ count, color }) => (
  <YStack alignItems="center">
    <Circle size={40} backgroundColor={`$${color}`}>
      <Text bold color="white">
        {count}
      </Text>
    </Circle>
    <Text caption marginTop="$1">
      {color.charAt(0).toUpperCase() + color.slice(1)}
    </Text>
  </YStack>
);

7.4 Navigation Flow

The Team Audit feature implements a structured navigation flow to guide users through the process.

flowchart LR
    A[Team Lobby] --> B[Audit Overview]
    B --> C[Section Selection]
    C --> D[Section Form]
    D --> C
    C --> E[Summary Screen]
    E --> F[Signature Capture]
    F --> E
    E --> G[Submission]
    G --> H[Completion]

    style A fill:#f9d5e5,stroke:#333,stroke-width:2px
    style E fill:#d5e5f9,stroke:#333,stroke-width:2px
    style G fill:#e5f9d5,stroke:#333,stroke-width:2px
    style H fill:#f9e5d5,stroke:#333,stroke-width:2px

Implementation:

The navigation structure is implemented using Expo Router in apps/expo/app/questionnaire/_layout.tsx:

// Navigation structure definition
export default function QuestionnaireLayout() {
  return (
    <Stack>
      <Stack.Screen
        name="team-lobby"
        options={{
          title: "Team Audit",
          headerShown: true,
        }}
      />
      <Stack.Screen
        name="overview"
        options={{
          title: "Audit Overview",
          headerShown: true,
        }}
      />
      <Stack.Screen
        name="index"
        options={{
          title: "Questionnaire",
          headerShown: true,
        }}
      />
      <Stack.Screen
        name="section"
        options={{
          title: "Section Form",
          headerShown: true,
        }}
      />
      <Stack.Screen
        name="team-audit-summary"
        options={{
          title: "Audit Summary",
          headerShown: true,
        }}
      />
      <Stack.Screen
        name="signature"
        options={{
          title: "Signature",
          headerShown: true,
          presentation: "modal",
        }}
      />
      <Stack.Screen
        name="payoff"
        options={{
          title: "Audit Complete",
          headerShown: false,
        }}
      />
    </Stack>
  );
}

8. Error Handling and Recovery

8.1 Error Handling Strategies

The Team Audit feature implements robust error handling at multiple levels of the application:

flowchart TD
    A[Error Source] --> B{Error Type}
    B -->|Network Error| C[Retry Strategy]
    B -->|Validation Error| D[User Feedback]
    B -->|Server Error| E[Graceful Degradation]
    B -->|Unknown Error| F[Fallback UI]

    C --> G[Exponential Backoff]
    C --> H[Offline Caching]

    D --> I[Field-Level Errors]
    D --> J[Form-Level Errors]
    D --> K[Toast Notifications]

    E --> L[Partial Data Display]
    E --> M[Alternative Workflows]

    F --> N[Error Boundary]
    F --> O[Error Logging]

    style A fill:#f9d5e5,stroke:#333,stroke-width:2px
    style B fill:#d5e5f9,stroke:#333,stroke-width:2px
    style C fill:#e5f9d5,stroke:#333,stroke-width:2px
    style D fill:#f9e5d5,stroke:#333,stroke-width:2px

8.2 Network Error Handling

Located at packages/app/api/api-client.ts:

// API client with retry logic
const client = create({
  baseURL: API_BASE_URL,
  timeout: 30000,
  headers: {
    "Content-Type": "application/json",
  },
});
 
// Configure retry logic for network failures
client.addMonitor(
  axiosRetry(client.axiosInstance, {
    retries: 3,
    retryDelay: (retryCount) => {
      return Math.min(1000 * Math.pow(2, retryCount), 10000); // Exponential backoff
    },
    retryCondition: (error) => {
      // Only retry on network errors or 5xx server errors
      return (
        axiosRetry.isNetworkError(error) ||
        (error.response && error.response.status >= 500)
      );
    },
  })
);

8.3 Form Validation Errors

Form validation errors are presented inline with clear messaging:

// Form error display component
const FormErrors = ({ errors }) => {
  if (Object.keys(errors).length === 0) return null;
 
  return (
    <YStack
      backgroundColor="$errorLight"
      padding="$2"
      borderRadius="$2"
      marginBottom="$2"
    >
      <Text color="$error" bold>
        Please fix the following errors:
      </Text>
      {Object.entries(errors).map(([key, error]) => (
        <Text key={key} color="$error">
          • {error.message}
        </Text>
      ))}
    </YStack>
  );
};

8.4 Recovery Mechanisms

8.4.1 Offline Support

Data can be stored locally and synchronized when connectivity is restored:

// Offline-capable repository method
public updateTeamQuestionnaireSection = async (params) => {
  try {
    // Try online submission first
    const response = await this.api.patch(
      `/v1.0/team-reports/${params.reportID}/sections/${params.sectionIndex}`,
      params
    );
 
    if (response.ok) {
      // Clear any pending offline data
      await this.offlineStorage.removePendingChange(
        `team-report-${params.reportID}-section-${params.sectionIndex}`
      );
      return response.data;
    }
 
    throw new Error('Failed to update section');
  } catch (error) {
    if (isNetworkError(error)) {
      // Store data for offline synchronization
      await this.offlineStorage.storePendingChange(
        `team-report-${params.reportID}-section-${params.sectionIndex}`,
        params
      );
 
      // Return optimistic result
      return {
        success: true,
        offlineMode: true,
        data: {
          sectionIndex: params.sectionIndex,
          status: 'pending_sync',
        },
      };
    }
 
    // Re-throw other errors
    throw error;
  }
};

8.4.2 Autosave

Form data is automatically saved to prevent data loss:

// Autosave implementation
useEffect(() => {
  let autosaveTimeout;
 
  const subscription = form.watch((formValues) => {
    // Debounce to prevent too frequent saves
    if (autosaveTimeout) {
      clearTimeout(autosaveTimeout);
    }
 
    autosaveTimeout = setTimeout(() => {
      // Save to local storage
      saveFormDataToStorage(reportID, sectionIndex, formValues);
    }, 2000);
  });
 
  return () => {
    subscription.unsubscribe();
    if (autosaveTimeout) {
      clearTimeout(autosaveTimeout);
    }
  };
}, [form, reportID, sectionIndex]);

9. Testing Strategy

The Team Audit implementation follows a comprehensive testing approach to ensure reliability and maintainability.

9.1 Testing Architecture

flowchart TD
    UT[Unit Tests] --> CT[Component Tests]
    CT --> IT[Integration Tests]
    IT --> E2E[End-to-End Tests]

    UT --> M[Mocking Strategy]
    CT --> M

    style UT fill:#f9d5e5,stroke:#333,stroke-width:2px
    style CT fill:#d5e5f9,stroke:#333,stroke-width:2px
    style IT fill:#e5f9d5,stroke:#333,stroke-width:2px
    style E2E fill:#f9e5d5,stroke:#333,stroke-width:2px

9.2 Unit Testing Controllers

Each controller is tested with a corresponding test file that verifies its behavior in isolation:

// Example from team-summary-controller.test.ts
describe("TeamSummaryController", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });
 
  it("should fetch team audit summary data", async () => {
    // Mock dependencies
    const mockSummaryData = {
      siteName: "Test Site",
      auditName: "Safety Audit",
      flagCount: { red: 3, yellow: 2, green: 10 },
    };
 
    vi.mocked(
      teamSummaryService.getTeamAuditReportSummaryUIData
    ).mockResolvedValue(mockSummaryData);
 
    // Execute the test
    const { result } = renderHook(() =>
      teamSummaryController.useTeamAuditSummary()
    );
 
    // Wait for the query to complete
    await waitFor(() => expect(result.current.isSuccess).toBe(true));
 
    // Verify the data
    expect(result.current.data).toEqual(mockSummaryData);
  });
 
  it("should handle form submission", async () => {
    // Test implementation
  });
});

9.3 Repository Testing

Repository layers are tested to ensure proper API integration:

// Example from team-summary-repo.test.ts
describe("TeamSummaryRepository", () => {
  const mockResponse = {
    ok: true,
    status: 200,
    data: { success: true },
  };
 
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(apiClient.client.get).mockResolvedValue(mockResponse);
    vi.mocked(apiClient.client.post).mockResolvedValue(mockResponse);
  });
 
  describe("getTeamAuditReportSummaryData", () => {
    it("calls the correct endpoint with the provided reportID", async () => {
      const reportID = "test-report-id";
 
      await teamSummaryRepository.getTeamAuditReportSummaryData({ reportID });
 
      expect(reportEndpoint.GET_TEAM_AUDIT_REPORT_SUMMARY).toHaveBeenCalledWith(
        { mongoReportId: reportID }
      );
      expect(apiClient.client.get).toHaveBeenCalledWith(
        `/v1.0/reports/app/${reportID}`
      );
    });
 
    it("validates the response data using schema", async () => {
      const reportID = "test-report-id";
 
      await teamSummaryRepository.getTeamAuditReportSummaryData({ reportID });
 
      expect(summaryPageDataResponseSchema.parse).toHaveBeenCalledWith(
        mockResponse.data
      );
    });
  });
});

9.4 Component Testing

UI components are tested using React Testing Library:

// Example from team-audit-summary.test.tsx
describe("TeamAuditSummary", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });
 
  it("should render correctly with summary data", () => {
    render(<MockTeamAuditSummary />);
 
    // Verify main containers
    expect(screen.getByTestId("root-container")).toBeTruthy();
    expect(screen.getByTestId("form-provider")).toBeTruthy();
    expect(screen.getByTestId("scroll-view")).toBeTruthy();
 
    // Verify site information is displayed
    expect(screen.getByTestId("site-name")).toBeTruthy();
    expect(screen.getByTestId("audit-name")).toBeTruthy();
    expect(screen.getByText("Test Site")).toBeTruthy();
    expect(screen.getByText("Safety Audit")).toBeTruthy();
 
    // Verify report summary is displayed
    expect(screen.getByText("Report Summary")).toBeTruthy();
    expect(screen.getByTestId("flag-summary")).toBeTruthy();
 
    // Verify subcomponents are rendered
    expect(screen.getByTestId("section-preview")).toBeTruthy();
    expect(screen.getByTestId("team-leader-signature")).toBeTruthy();
    expect(screen.getByTestId("email-section")).toBeTruthy();
  });
});

9.5 Mock Implementation

Vitest mocks are used extensively to isolate components during testing:

// Example mock implementation
vi.mock("../_controllers/team-summary-controller", () => ({
  teamSummaryController: {
    useTeamAuditSummary: vi.fn().mockReturnValue({
      data: {
        siteName: "Test Site",
        auditName: "Safety Audit",
        flagCount: { red: 3, yellow: 2, green: 10 },
        sections: [],
        signatures: [],
        emailTargets: [],
      },
      isLoading: false,
      error: null,
    }),
    useTeamReportForm: vi.fn().mockReturnValue({
      form: {
        control: {},
        handleSubmit: vi.fn(),
        watch: vi.fn(),
      },
    }),
  },
}));

9.6 State Testing

Atom-based state management is tested using renderHook:

// Example from state.test.tsx
describe("Team Audit State", () => {
  it("should initialize teamAuditProgressUploadAtom with false", () => {
    const { result } = renderHook(() => useAtom(teamAuditProgressUploadAtom));
    expect(result.current[0]).toBe(false);
  });
 
  it("should update the teamAuditProgressUploadAtom value when set", () => {
    const { result } = renderHook(() => useAtom(teamAuditProgressUploadAtom));
 
    act(() => {
      result.current[1](true);
    });
 
    expect(result.current[0]).toBe(true);
  });
});

9.7 Test Coverage Goals

The Team Audit codebase aims for high test coverage across different layers:

LayerCoverage TargetTest Focus
Domain Models100%Type validation, property access
Use Cases90%+Business logic, error handling
Controllers85%+Hook behavior, state management
Repositories85%+API integration, data mapping
UI Components75%+Rendering, user interactions

10. Security and Permissions

10.1 Role-Based Access Control

The Team Audit feature implements a comprehensive role-based access control (RBAC) system:

flowchart TD
    R[User Roles] --> P[Permissions]
    P --> A[Actions]

    R --> TL[Team Leader]
    R --> TM[Team Member]
    R --> AD[Admin]

    TL --> TLP[Team Leader Permissions]
    TM --> TMP[Team Member Permissions]
    AD --> ADP[Admin Permissions]

    TLP --> TLPA[Start Audit]
    TLP --> TLPB[Distribute Sections]
    TLP --> TLPC[Review Sections]
    TLP --> TLPC[Submit Report]

    TMP --> TMPA[Complete Sections]
    TMP --> TMPB[Upload Attachments]

    ADP --> ADPA[Configure Recipients]
    ADP --> ADPB[Access All Reports]

    style R fill:#f9d5e5,stroke:#333,stroke-width:2px
    style P fill:#d5e5f9,stroke:#333,stroke-width:2px
    style A fill:#e5f9d5,stroke:#333,stroke-width:2px

Located at packages/app/features/team-report/summary/_controllers/team-summary-controller.ts:

// Role-based permission checks
public usePermissions = () => {
  const { user } = useAuth();
 
  return {
    canSubmitReport: user.role === 'team_leader' ||
                     user.permissions.includes('submit_team_report'),
    canEditSignature: user.role === 'team_leader' ||
                      user.permissions.includes('edit_signature'),
    canConfigureEmails: user.role === 'team_leader' ||
                        user.role === 'admin' ||
                        user.permissions.includes('configure_emails'),
    canResetSection: user.role === 'team_leader' ||
                     user.permissions.includes('reset_section'),
    canAccessAllSections: user.role === 'team_leader' ||
                          user.permissions.includes('access_all_sections'),
  };
};
 
// Permission-controlled component rendering
const TeamLeaderActions = () => {
  const { canSubmitReport } = teamSummaryController.usePermissions();
 
  if (!canSubmitReport) {
    return null; // Don't render submission controls
  }
 
  return (
    <YStack gap="$3">
      <Button onPress={handleSubmit}>Submit Report</Button>
      <Button variant="outlined" onPress={handleSave}>Save Draft</Button>
    </YStack>
  );
};

10.2 Authentication Flow

All API requests require authentication through JWT tokens:

// Auth token interceptor in api-client.ts
api.addAsyncRequestTransform(async (request) => {
  const token = await authStateRepository.getToken();
  if (token) {
    request.headers["Authorization"] = `Bearer ${token}`;
  } else {
    throw new AuthError("No valid authentication token");
  }
});
 
// Automatic token refresh handling
api.addAsyncResponseTransform(async (response) => {
  if (response.status === 401) {
    try {
      const newToken = await authStateRepository.refreshToken();
      if (newToken) {
        // Retry the original request with the new token
        return api.request({
          ...response.config,
          headers: {
            ...response.config.headers,
            Authorization: `Bearer ${newToken}`,
          },
        });
      }
    } catch (error) {
      // Force logout on refresh failure
      authStateRepository.clearTokens();
      navigateToLogin();
    }
  }
  return response;
});

10.3 Data Protection

Sensitive audit data is protected through several mechanisms:

10.3.1 Secure Storage

// Encrypted storage for sensitive data
import * as SecureStore from "expo-secure-store";
 
export class SecureStorageService {
  // Store data securely with encryption
  static async storeSecureData(key: string, data: any): Promise<void> {
    const jsonValue = JSON.stringify(data);
    await SecureStore.setItemAsync(key, jsonValue, {
      keychainAccessible: SecureStore.WHEN_UNLOCKED,
    });
  }
 
  // Retrieve securely stored data
  static async getSecureData(key: string): Promise<any> {
    const jsonValue = await SecureStore.getItemAsync(key);
    return jsonValue ? JSON.parse(jsonValue) : null;
  }
 
  // Remove secure data
  static async removeSecureData(key: string): Promise<void> {
    await SecureStore.deleteItemAsync(key);
  }
}

10.3.2 Data Encryption

Sensitive data is encrypted both in transit and at rest:

// Transport-level encryption
import { encrypt, decrypt } from 'react-native-crypto';
 
export class DataEncryptionService {
  private static readonly ENCRYPTION_KEY = 'SECURE_TEAM_REPORT_KEY';
 
  // Encrypt data before storage
  static encryptData(data: string): string {
    return encrypt(data, this.ENCRYPTION_KEY);
  }
 
  // Decrypt data after retrieval
  static decryptData(encryptedData: string): string {
    return decrypt(encryptedData, this.ENCRYPTION_KEY);
  }
}
 
// Usage in offline storage
private async storeOfflineData(key: string, data: any) {
  try {
    const jsonValue = JSON.stringify(data);
    const encryptedValue = DataEncryptionService.encryptData(jsonValue);
    await SecureStorageService.storeSecureData(key, encryptedValue);
  } catch (error) {
    console.error('Error storing offline data:', error);
    throw new Error('Failed to store offline data securely');
  }
}

10.3.3 Data Access Policies

Team audit data access is controlled by policy-based restrictions:

// Data access policy enforcement
class TeamReportAccessPolicy {
  // Check if user can access a specific report
  static canAccessReport(user: User, reportId: string): boolean {
    if (user.role === "admin") return true;
 
    // Team leaders can access all reports they created
    if (user.role === "team_leader" && user.createdReports.includes(reportId)) {
      return true;
    }
 
    // Team members can only access reports they're part of
    if (
      user.role === "team_member" &&
      user.participatedReports.includes(reportId)
    ) {
      return true;
    }
 
    return false;
  }
 
  // Check if user can access a specific section
  static canAccessSection(
    user: User,
    reportId: string,
    sectionId: string
  ): boolean {
    if (!this.canAccessReport(user, reportId)) return false;
 
    if (user.role === "admin" || user.role === "team_leader") {
      return true;
    }
 
    // Team members can only access assigned sections
    return user.assignedSections.some(
      (s) => s.reportId === reportId && s.sectionId === sectionId
    );
  }
}

10.4 Audit Trails

All significant actions are logged for accountability:

// Audit logging middleware
const auditLogger = async (action: string, params: any, userId: string) => {
  try {
    await api.post('/v1.0/audit-logs', {
      action,
      params,
      userId,
      timestamp: new Date().toISOString(),
      userAgent: getUserAgent(),
      ipAddress: await getIPAddress(),
    });
  } catch (error) {
    // Log failures shouldn't break functionality
    console.error('Failed to log audit trail:', error);
  }
};
 
// Usage in team report submission
public submitTeamReport = async (params: SubmitTeamReportParams): Promise<any> => {
  try {
    // Log the submission attempt
    await auditLogger('TEAM_REPORT_SUBMIT_ATTEMPT', { reportId: params.reportId }, params.userId);
 
    // Perform the submission
    const response = await this.api.post(`/v1.0/team-reports/${params.reportId}/submit`, params);
 
    if (response.ok) {
      // Log successful submission
      await auditLogger('TEAM_REPORT_SUBMIT_SUCCESS', { reportId: params.reportId }, params.userId);
      return response.data;
    }
 
    // Log submission failure
    await auditLogger('TEAM_REPORT_SUBMIT_FAILURE', {
      reportId: params.reportId,
      error: response.status
    }, params.userId);
 
    throw new Error(`Failed to submit report: ${response.status}`);
  } catch (error) {
    // Log unexpected errors
    await auditLogger('TEAM_REPORT_SUBMIT_ERROR', {
      reportId: params.reportId,
      error: error.message
    }, params.userId);
 
    throw error;
  }
};

11. Offline Capabilities

The Team Audit feature implements a robust offline-first architecture to ensure data integrity and continuity of operations in challenging connectivity environments.

11.1 Offline Architecture

flowchart TD
    A[User Action] --> B{Connection?}
    B -->|Yes| C[Direct API]
    B -->|No| D[Offline Queue]
    D --> E[Local Storage]
    C --> F[Server Response]
    F --> G[Update UI]
    E --> H{Online?}
    H -->|Yes| I[Background Sync]
    I --> J[Conflict Resolution]
    J --> G
    H -->|No| K[Retry Later]
    K --> E

    style A fill:#f9d5e5,stroke:#333,stroke-width:2px
    style B fill:#d5e5f9,stroke:#333,stroke-width:2px
    style D fill:#e5f9d5,stroke:#333,stroke-width:2px
    style E fill:#f9e5d5,stroke:#333,stroke-width:2px
    style I fill:#e5d5f9,stroke:#333,stroke-width:2px
    style J fill:#f5f5f5,stroke:#333,stroke-width:2px

11.2 Local Storage Implementation

Located at packages/app/shared/infrastructure/offline-storage.ts:

// Offline storage service for persistent data
class OfflineStorage {
  // Store section data offline
  async storeTeamSectionData(
    reportId: string,
    sectionIndex: number,
    data: any
  ): Promise<void> {
    const key = this.generateSectionKey(reportId, sectionIndex);
    const timestamp = new Date().toISOString();
 
    await AsyncStorage.setItem(
      key,
      JSON.stringify({
        data,
        timestamp,
        synced: false,
        syncAttempts: 0,
      })
    );
  }
 
  // Retrieve section data
  async getTeamSectionData(
    reportId: string,
    sectionIndex: number
  ): Promise<any> {
    const key = this.generateSectionKey(reportId, sectionIndex);
    const storedData = await AsyncStorage.getItem(key);
 
    if (!storedData) return null;
 
    return JSON.parse(storedData);
  }
 
  // Store pending API operation
  async storePendingOperation(operation: {
    endpoint: string;
    method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
    data?: any;
    id: string;
  }): Promise<void> {
    const pendingOps = await this.getPendingOperations();
    pendingOps.push({
      ...operation,
      timestamp: Date.now(),
      attempts: 0,
    });
 
    await AsyncStorage.setItem("pendingOperations", JSON.stringify(pendingOps));
  }
 
  // Get all pending operations
  async getPendingOperations(): Promise<PendingOperation[]> {
    const pending = await AsyncStorage.getItem("pendingOperations");
    return pending ? JSON.parse(pending) : [];
  }
 
  // Helper methods
  private generateSectionKey(reportId: string, sectionIndex: number): string {
    return `team_report_${reportId}_section_${sectionIndex}`;
  }
}

11.3 Synchronization Service

Located at packages/app/shared/services/sync-service.ts:

// Background synchronization service
class SyncService {
  private isRunning = false;
  private offlineStorage: OfflineStorage;
  private api: ApiClient;
 
  constructor(offlineStorage: OfflineStorage, api: ApiClient) {
    this.offlineStorage = offlineStorage;
    this.api = api;
 
    // Initialize network change listeners
    NetInfo.addEventListener(this.handleNetworkChange);
  }
 
  // Handle network status changes
  private handleNetworkChange = (state: NetInfoState) => {
    if (state.isConnected && state.isInternetReachable) {
      this.startSync();
    }
  };
 
  // Start the synchronization process
  async startSync(): Promise<void> {
    if (this.isRunning) return;
 
    this.isRunning = true;
 
    try {
      // Get all pending operations
      const operations = await this.offlineStorage.getPendingOperations();
 
      if (operations.length === 0) {
        this.isRunning = false;
        return;
      }
 
      // Sort by timestamp (oldest first)
      operations.sort((a, b) => a.timestamp - b.timestamp);
 
      // Process each operation
      for (const operation of operations) {
        try {
          await this.processOperation(operation);
 
          // Remove from pending queue on success
          await this.offlineStorage.removePendingOperation(operation.id);
        } catch (error) {
          // Update attempts counter
          await this.offlineStorage.updateOperationAttempts(
            operation.id,
            operation.attempts + 1
          );
 
          // Log the failure
          console.error(
            `Sync failed for operation ${operation.id}: ${error.message}`
          );
        }
      }
    } finally {
      this.isRunning = false;
    }
  }
 
  // Process a single operation
  private async processOperation(operation: PendingOperation): Promise<any> {
    switch (operation.method) {
      case "GET":
        return this.api.get(operation.endpoint);
      case "POST":
        return this.api.post(operation.endpoint, operation.data);
      case "PUT":
        return this.api.put(operation.endpoint, operation.data);
      case "PATCH":
        return this.api.patch(operation.endpoint, operation.data);
      case "DELETE":
        return this.api.delete(operation.endpoint);
      default:
        throw new Error(`Unsupported method: ${operation.method}`);
    }
  }
}

11.4 Conflict Resolution

The Team Audit feature implements a sophisticated conflict resolution system to handle cases where multiple users modify the same data while offline.

11.4.1 Conflict Detection

// Conflict detection in repository methods
public async submitTeamReportSection(params: SubmitSectionParams): Promise<any> {
  try {
    // Get latest version from server if online
    let serverVersion;
    try {
      serverVersion = await this.api.get(
        `/v1.0/team-reports/${params.reportID}/sections/${params.sectionIndex}`
      );
    } catch (error) {
      // If offline, proceed with local changes
      serverVersion = null;
    }
 
    // Check for conflicts if we got server data
    if (serverVersion && serverVersion.lastModified) {
      const localData = await this.offlineStorage.getTeamSectionData(
        params.reportID,
        params.sectionIndex
      );
 
      // Detect conflicts
      if (localData && new Date(localData.lastModified) < new Date(serverVersion.lastModified)) {
        return this.handleConflict({
          local: localData,
          server: serverVersion,
          params
        });
      }
    }
 
    // No conflict, proceed with submission
    // ...implementation continues
  } catch (error) {
    // Handle errors
  }
}

11.4.2 Conflict Resolution UI

// Conflict resolution UI component
const ConflictResolutionModal = ({
  visible,
  localData,
  serverData,
  onResolve,
  onCancel,
}) => {
  const [resolvedData, setResolvedData] = useState(null);
 
  // Prepare merged data with local changes preferred
  useEffect(() => {
    if (visible && localData && serverData) {
      const merged = mergeData(localData, serverData);
      setResolvedData(merged);
    }
  }, [visible, localData, serverData]);
 
  if (!visible) return null;
 
  return (
    <Modal visible={visible} onRequestClose={onCancel}>
      <YStack padding="$4" gap="$4">
        <Text fontWeight="bold">Changes Conflict Detected</Text>
 
        <Text>
          Your offline changes conflict with updates made by other team members.
          Please review the differences and resolve the conflicts.
        </Text>
 
        <ConflictList
          conflicts={findConflicts(localData, serverData)}
          onResolveConflict={(field, value) => {
            setResolvedData((prev) => ({
              ...prev,
              [field]: value,
            }));
          }}
        />
 
        <XStack gap="$4">
          <Button variant="outlined" onPress={onCancel}>
            Cancel
          </Button>
          <Button variant="filled" onPress={() => onResolve(resolvedData)}>
            Resolve Conflicts
          </Button>
        </XStack>
      </YStack>
    </Modal>
  );
};

11.5 Offline Usage Indicators

The UI provides clear indicators to users when they are working offline:

// Offline indicator component
const OfflineIndicator = () => {
  const isConnected = useNetInfo().isConnected;
 
  if (isConnected) return null;
 
  return (
    <XStack
      backgroundColor="$warning"
      padding="$2"
      justifyContent="center"
      alignItems="center"
    >
      <Icon name="wifi-off" color="white" size={16} />
      <Text color="white" marginLeft="$2">
        You are offline. Changes will be saved and synced when connection is
        restored.
      </Text>
    </XStack>
  );
};
 
// Integration in main screens
const TeamAuditScreen = () => {
  return (
    <YStack flex={1}>
      <OfflineIndicator />
      <ScrollView flex={1}>{/* Screen content */}</ScrollView>
    </YStack>
  );
};

11.6 Background Synchronization

The app implements background synchronization to ensure data is updated even when the app is not in active use:

// Background sync configuration
import * as BackgroundFetch from "expo-background-fetch";
import * as TaskManager from "expo-task-manager";
 
const BACKGROUND_SYNC_TASK = "background-sync";
 
// Define the task
TaskManager.defineTask(BACKGROUND_SYNC_TASK, async () => {
  try {
    // Get sync service instance
    const syncService = getSyncService();
 
    // Run synchronization
    await syncService.startSync();
 
    // Return success
    return BackgroundFetch.BackgroundFetchResult.NewData;
  } catch (error) {
    console.error("Background sync failed:", error);
    return BackgroundFetch.BackgroundFetchResult.Failed;
  }
});
 
// Register background sync
async function registerBackgroundSync() {
  try {
    await BackgroundFetch.registerTaskAsync(BACKGROUND_SYNC_TASK, {
      minimumInterval: 15 * 60, // 15 minutes
      stopOnTerminate: false,
      startOnBoot: true,
    });
 
    console.log("Background sync registered");
  } catch (error) {
    console.error("Background sync registration failed:", error);
  }
}

12. Performance Optimization

The Team Audit feature implements various optimization techniques to ensure smooth operation even on lower-end devices and in challenging network environments.

12.1 Memory Management

flowchart TD
    A[Memory Management] --> B[Component Virtualization]
    A --> C[Asset Optimization]
    A --> D[Memory Leak Prevention]

    B --> B1[Virtualized Lists]
    B --> B2[On-demand Loading]

    C --> C1[Image Resizing]
    C --> C2[Asset Preloading]

    D --> D1[Cleanup Effects]
    D --> D2[Component Unmount]

    style A fill:#f9d5e5,stroke:#333,stroke-width:2px
    style B fill:#d5e5f9,stroke:#333,stroke-width:2px
    style C fill:#e5f9d5,stroke:#333,stroke-width:2px
    style D fill:#f9e5d5,stroke:#333,stroke-width:2px

12.1.1 Virtualized Lists

For displaying potentially long lists of sections or questions, we use virtualized components:

// Optimized section list with virtualization
const SectionsList = ({ sections }) => {
  return (
    <FlashList
      data={sections}
      renderItem={({ item }) => <SectionItem section={item} />}
      estimatedItemSize={80}
      keyExtractor={(item) => item.sectionIndex.toString()}
      onEndReachedThreshold={0.5}
      ListEmptyComponent={<EmptyState />}
      contentContainerStyle={{ paddingBottom: 20 }}
    />
  );
};

12.1.2 Memory Leak Prevention

Every component that uses subscriptions implements proper cleanup:

// Proper effect cleanup to prevent memory leaks
useEffect(() => {
  const subscription = NetInfo.addEventListener(handleNetworkChange);
 
  return () => {
    subscription(); // Cleanup subscription when component unmounts
  };
}, []);

12.2 Network Optimization

12.2.1 Data Serialization

We optimize payloads for minimal data transfer:

// Optimized data transformation for network transfers
export const transformForUpload = (formData: FormData): UploadPayload => {
  // Only include necessary fields
  return {
    id: formData.id,
    questions: formData.questions.map((q) => ({
      id: q.id,
      answer: q.answer,
      // Only include references to attachments, not the full binary data
      attachmentRefs: q.attachments?.map((a) => a.id) || [],
    })),
    metadata: {
      version: formData.version,
      completedBy: formData.completedBy,
    },
  };
};

12.2.2 Request Batching

For multiple related operations, requests are batched:

// Batch multiple attachment uploads in a single network request
public async batchUploadAttachments(attachments: Attachment[]): Promise<UploadResult[]> {
  // Group files by type for optimal encoding
  const imageAttachments = attachments.filter(a => a.type.startsWith('image/'));
  const documentAttachments = attachments.filter(a => !a.type.startsWith('image/'));
 
  // Create form data for batch upload
  const formData = new FormData();
 
  // Append all images
  imageAttachments.forEach((attachment, index) => {
    formData.append(`images[${index}]`, {
      uri: attachment.path,
      name: attachment.name,
      type: attachment.type
    });
  });
 
  // Append all documents
  documentAttachments.forEach((attachment, index) => {
    formData.append(`documents[${index}]`, {
      uri: attachment.path,
      name: attachment.name,
      type: attachment.type
    });
  });
 
  // Single API call for multiple files
  const response = await api.post('/v1.0/attachments/batch-upload', formData, {
    headers: {
      'Content-Type': 'multipart/form-data'
    },
    onUploadProgress: this.onProgress
  });
 
  return response.data.results;
}

12.2.3 Payload Compression

Large data payloads are compressed before transmission:

// Compression middleware for large payloads
api.addRequestTransform((request) => {
  // Only compress large payloads
  if (request.data && JSON.stringify(request.data).length > 10000) {
    request.headers["Content-Encoding"] = "gzip";
    request.data = compressData(request.data);
  }
  return request;
});
 
// Compression function
function compressData(data) {
  const jsonString = JSON.stringify(data);
  return gzip(jsonString);
}

12.3 Rendering Optimization

12.3.1 Component Memoization

Heavy components are memoized to prevent unnecessary re-renders:

// Memoized section component
const SectionItem = React.memo(
  ({ section, onSelect }) => {
    return (
      <Pressable onPress={() => onSelect(section.sectionIndex)}>
        <YStack padding="$3" borderRadius="$2" backgroundColor="$background">
          <Text bold>{section.sectionName}</Text>
          <SectionStatusBadge status={section.approvalStatus} />
        </YStack>
      </Pressable>
    );
  },
  (prevProps, nextProps) => {
    // Custom comparison logic
    return (
      prevProps.section.approvalStatus === nextProps.section.approvalStatus
    );
  }
);

12.3.2 Dynamic Imports

Heavy components used infrequently are dynamically imported:

// Dynamic import for signature capture component
const SignatureCapture = React.lazy(() => import("./signature-capture"));
 
// Usage with suspense
const SignatureModal = ({ visible, onClose, onSave }) => {
  if (!visible) return null;
 
  return (
    <Modal visible={visible} onRequestClose={onClose}>
      <React.Suspense fallback={<Spinner />}>
        <SignatureCapture onSave={onSave} onCancel={onClose} />
      </React.Suspense>
    </Modal>
  );
};

12.3.3 Render Optimization

Components use optimized rendering techniques:

// Optimized rendering with useMemo and useCallback
const OptimizedQuestionList = ({ questions, onChange }) => {
  // Memoize expensive transformations
  const processedQuestions = useMemo(() => {
    return questions.map((q) => ({
      ...q,
      hasAttachments: q.attachments && q.attachments.length > 0,
      isAnswered: !!q.answer,
    }));
  }, [questions]);
 
  // Memoize callbacks
  const handleChange = useCallback(
    (id, value) => {
      onChange(id, value);
    },
    [onChange]
  );
 
  return (
    <YStack>
      {processedQuestions.map((question) => (
        <QuestionItem
          key={question.id}
          question={question}
          onChange={handleChange}
        />
      ))}
    </YStack>
  );
};

12.4 Resource Management

12.4.1 Image Optimization

Images are processed for optimal size and quality:

// Image optimization before upload
async function optimizeImage(uri: string, quality: number = 0.8): Promise<string> {
  const result = await manipulateAsync(
    uri,
    [{ resize: { width: 800 } }],
    { compress: quality, format: SaveFormat.JPEG }
  );
  return result.uri;
}
 
// Usage in attachment upload
public async uploadImageAttachment(attachment: ImageAttachment): Promise<UploadResult> {
  // Optimize the image before upload
  const optimizedUri = await optimizeImage(attachment.path);
 
  // Create optimized attachment
  const optimizedAttachment = {
    ...attachment,
    path: optimizedUri
  };
 
  // Upload the optimized image
  return this.uploadFile(optimizedAttachment);
}

12.4.2 Asset Preloading

Critical assets are preloaded to prevent UI jank:

// Asset preloading service
class AssetPreloader {
  static async preloadTeamAuditAssets() {
    // Preload images
    const imageAssets = [
      require("../assets/images/site-details.png"),
      require("../assets/images/flag-green.png"),
      require("../assets/images/flag-yellow.png"),
      require("../assets/images/flag-red.png"),
      require("../assets/images/signature-placeholder.png"),
    ];
 
    // Start preloading
    await Asset.loadAsync(imageAssets);
 
    // Preload fonts
    await Font.loadAsync({
      "Roboto-Bold": require("../assets/fonts/Roboto-Bold.ttf"),
      "Roboto-Regular": require("../assets/fonts/Roboto-Regular.ttf"),
    });
  }
}
 
// Used in app initialization
useEffect(() => {
  AssetPreloader.preloadTeamAuditAssets();
}, []);

12.5 Performance Monitoring

The app includes performance monitoring to identify bottlenecks:

// Performance tracking hooks
export const usePerformanceTracker = (trackingName: string) => {
  const startTime = useRef(Date.now());
  const events = useRef<Array<{ name: string; timestamp: number }>>([]);
 
  // Record a performance event
  const trackEvent = useCallback((eventName: string) => {
    events.current.push({
      name: eventName,
      timestamp: Date.now() - startTime.current,
    });
  }, []);
 
  // Complete tracking and send metrics
  const completeTracking = useCallback(() => {
    const totalTime = Date.now() - startTime.current;
 
    // Send metrics to analytics
    Analytics.logEvent("performance_tracking", {
      tracking_name: trackingName,
      total_time: totalTime,
      events: events.current,
    });
 
    // Log to console in development
    if (__DEV__) {
      console.log(
        `Performance (${trackingName}): ${totalTime}ms`,
        events.current
      );
    }
 
    // Reset for reuse
    events.current = [];
    startTime.current = Date.now();
  }, [trackingName]);
 
  return { trackEvent, completeTracking };
};
 
// Usage in a component
const TeamAuditSummary = () => {
  const { trackEvent, completeTracking } =
    usePerformanceTracker("team_audit_summary");
 
  useEffect(() => {
    trackEvent("component_mount");
 
    return () => {
      trackEvent("component_unmount");
      completeTracking();
    };
  }, []);
 
  // Component implementation
};

14. Conclusion

The Team Audit feature in Nimbly 2.0 represents a sophisticated implementation that enables collaborative inspection workflows. By following clean architecture principles and leveraging modern frontend technologies, the system provides a robust, scalable solution for conducting team-based audits across various facilities and scenarios.

14.1 Key Technical Achievements

The implementation successfully delivers:

  1. Real-time Collaboration: Multiple team members can work concurrently on different sections
  2. Comprehensive Data Management: Structured handling of complex audit data with proper validation
  3. Responsive Offline-Capable UI: Functional experience across network conditions
  4. Robust Attachment Handling: Secure, validated file uploads with progress tracking
  5. Role-Based Permissions: Granular access control based on team roles

14.2 Architectural Strengths

The codebase demonstrates several architectural strengths:

  • Clear separation of concerns following clean architecture principles
  • Strong typing with TypeScript and validation with Zod
  • Reactive state management with Jotai and React Query
  • Consistent use of repository pattern for data access
  • Comprehensive error handling across all levels

14.3 Development Practices

The development process followed several best practices:

  • Component-based design for UI elements
  • Test-driven development with co-located test files
  • Feature flags for incremental deployment
  • Comprehensive documentation
  • Performance optimization for mobile devices

This documentation provides a complete technical reference for developers working with the Team Audit feature, enabling them to understand the architecture, implementation details, and workflows necessary for maintenance and extension of the system.

15. Deployment and Integration

The Team Audit feature is designed with a robust deployment process and integrates with various systems and services.

15.1 Deployment Architecture

flowchart TD
    A[Code Repository] --> B[CI/CD Pipeline]
    B --> C{Environment}
    C -->|Development| D[Dev Environment]
    C -->|Staging| E[Staging Environment]
    C -->|Production| F[Production Environment]

    D --> G[Development Tests]
    E --> H[Integration Tests]
    F --> I[Monitoring]

    style A fill:#f9d5e5,stroke:#333,stroke-width:2px
    style B fill:#d5e5f9,stroke:#333,stroke-width:2px
    style C fill:#e5f9d5,stroke:#333,stroke-width:2px
    style F fill:#f9e5d5,stroke:#333,stroke-width:2px
    style I fill:#e5d5f9,stroke:#333,stroke-width:2px

15.2 Release Process

The Team Audit feature follows a structured release process:

  1. Feature Development: Code is written and unit tested in feature branches
  2. Code Review: All changes undergo peer review through pull requests
  3. Automated Testing: CI pipeline runs unit and integration tests
  4. QA Verification: Manual testing in the staging environment
  5. Feature Flagging: New features are wrapped in feature flags for controlled rollout
  6. Staged Rollout: Features are released to a percentage of users first
  7. Monitoring: Performance and error metrics are monitored closely after release
  8. Documentation: Release notes and documentation are updated

15.3 Feature Flags

The Team Audit feature uses feature flags for controlled deployment:

// Feature flag configuration
const featureFlags = {
  teamAudit: {
    ENABLED: true,
    ATTACHMENT_VALIDATION: true,
    REAL_TIME_COLLABORATION: false,
    OFFLINE_SYNC: true,
    AI_ASSISTANCE: false,
  },
};
 
// Feature flag usage
if (featureFlags.teamAudit.ENABLED) {
  // Register team audit routes
  router.registerRoute("/team-audit", TeamAuditScreen);
 
  // Only show attachment validation if enabled
  if (featureFlags.teamAudit.ATTACHMENT_VALIDATION) {
    attachmentValidationEnabled = true;
  }
}
 
// Dynamic feature flag loading
class FeatureFlagService {
  private flags: Record<string, any> = {};
 
  async loadFlags(userId: string): Promise<void> {
    try {
      const response = await api.get("/v1.0/feature-flags", {
        params: { userId },
      });
 
      if (response.ok) {
        this.flags = response.data;
      }
    } catch (error) {
      console.error("Failed to load feature flags:", error);
      // Fall back to default flags
      this.flags = defaultFeatureFlags;
    }
  }
 
  isEnabled(flagPath: string): boolean {
    const parts = flagPath.split(".");
    let current = this.flags;
 
    for (const part of parts) {
      if (current[part] === undefined) return false;
      current = current[part];
    }
 
    return !!current;
  }
}

15.4 Integration Points

The Team Audit feature integrates with several external systems:

15.4.1 PDF Generation

Reports are converted to PDF for distribution:

// PDF generation service integration
async function generateTeamReportPDF(reportId: string): Promise<string> {
  try {
    // Call the PDF generation service
    const response = await api.post("/v1.0/reports/generate-pdf", {
      reportId,
      template: "team-audit",
    });
 
    if (response.ok) {
      return response.data.pdfUrl;
    }
 
    throw new Error(`Failed to generate PDF: ${response.status}`);
  } catch (error) {
    console.error("PDF generation error:", error);
    throw error;
  }
}

15.4.2 Email Integration

The reporting system integrates with email services:

// Email service integration
async function sendReportEmails(
  reportId: string,
  emailTargets: EmailTarget[]
): Promise<void> {
  try {
    // Filter enabled email targets
    const enabledTargets = emailTargets.filter((target) => target.enabled);
 
    if (enabledTargets.length === 0) {
      console.log("No email targets enabled, skipping email send");
      return;
    }
 
    // Call the email service
    const response = await api.post("/v1.0/notifications/email", {
      reportId,
      recipients: enabledTargets.map((target) => target.email),
      templateId: "team-audit-report",
    });
 
    if (!response.ok) {
      throw new Error(`Failed to send emails: ${response.status}`);
    }
 
    console.log("Report emails sent successfully");
  } catch (error) {
    console.error("Email sending error:", error);
    throw error;
  }
}

15.4.3 Analytics Integration

User actions and report data are fed into analytics systems:

// Analytics integration
class AnalyticsService {
  // Track user actions
  static trackAction(
    action: string,
    properties: Record<string, any> = {}
  ): void {
    try {
      Analytics.logEvent(action, {
        ...properties,
        timestamp: new Date().toISOString(),
      });
    } catch (error) {
      console.error("Failed to track action:", error);
    }
  }
 
  // Track team audit funnel
  static trackTeamAuditStep(
    step: string,
    reportId: string,
    properties: Record<string, any> = {}
  ): void {
    this.trackAction("team_audit_step", {
      step,
      reportId,
      ...properties,
    });
  }
 
  // Track completion of reports
  static trackReportCompletion(reportId: string, stats: ReportStats): void {
    this.trackAction("team_audit_completed", {
      reportId,
      completionTimeMinutes: stats.completionTimeMinutes,
      sectionCount: stats.sectionCount,
      questionCount: stats.questionCount,
      flagCounts: stats.flagCounts,
    });
  }
}

15.5 Versioning and Backward Compatibility

The Team Audit feature implements versioning to ensure backward compatibility:

// API version handling
const API_VERSIONS = {
  TEAM_AUDIT_V1: "2023-04-01",
  TEAM_AUDIT_V2: "2023-10-15",
  CURRENT: "2023-10-15",
};
 
// API client with version header
api.addRequestTransform((request) => {
  request.headers["X-API-Version"] = API_VERSIONS.CURRENT;
  return request;
});
 
// Version-specific data mapping
function mapReportData(data: any, version: string): TeamReportModel {
  if (version === API_VERSIONS.TEAM_AUDIT_V1) {
    // Map using V1 schema
    return {
      id: data.id,
      sections: data.sections.map(mapV1Section),
      // Other V1 mappings...
    };
  } else {
    // Map using current schema
    return {
      id: data.reportId,
      sections: data.sections.map(mapCurrentSection),
      // Other current mappings...
    };
  }
}

15.6 Environment Configuration

The Team Audit feature is configured for different environments:

// Environment configuration
const ENV = {
  development: {
    API_URL: "https://dev-api.nimbly.com/v1.0",
    ATTACHMENT_UPLOAD_LIMIT: 10, // MB
    POLLING_INTERVAL: 5000, // ms
    LOG_LEVEL: "debug",
  },
  staging: {
    API_URL: "https://staging-api.nimbly.com/v1.0",
    ATTACHMENT_UPLOAD_LIMIT: 20, // MB
    POLLING_INTERVAL: 10000, // ms
    LOG_LEVEL: "info",
  },
  production: {
    API_URL: "https://api.nimbly.com/v1.0",
    ATTACHMENT_UPLOAD_LIMIT: 50, // MB
    POLLING_INTERVAL: 30000, // ms
    LOG_LEVEL: "error",
  },
};
 
// Get current environment configuration
const getEnvConfig = () => {
  const environment = process.env.ENVIRONMENT || "development";
  return ENV[environment];
};
 
// Usage example
const config = getEnvConfig();
console.log(`API URL: ${config.API_URL}`);

15.7 Monitoring and Alerts

The Team Audit feature includes comprehensive monitoring:

// Error monitoring integration
class ErrorMonitor {
  static initialize(): void {
    // Set up global error handler
    ErrorUtils.setGlobalHandler((error, isFatal) => {
      this.reportError(error, { isFatal });
 
      // Display user-friendly error
      if (isFatal) {
        showErrorDialog(
          "An unexpected error occurred",
          "Please restart the app and try again"
        );
      }
    });
  }
 
  static reportError(error: Error, context: Record<string, any> = {}): void {
    try {
      // Send to error monitoring service
      Sentry.captureException(error, {
        extra: context,
      });
 
      // Log locally in development
      if (__DEV__) {
        console.error("Error captured:", error, context);
      }
    } catch (reportingError) {
      console.error("Failed to report error:", reportingError);
    }
  }
}
 
// Performance monitoring
class PerformanceMonitor {
  static trackRenderTime(componentName: string, renderTimeMs: number): void {
    if (renderTimeMs > 100) {
      // Track slow renders
      Analytics.logEvent("slow_render", {
        componentName,
        renderTimeMs,
      });
    }
  }
 
  static trackNetworkRequest(
    endpoint: string,
    durationMs: number,
    success: boolean
  ): void {
    Analytics.logEvent("network_request", {
      endpoint,
      durationMs,
      success,
    });
 
    // Alert on slow network requests
    if (durationMs > 5000) {
      Sentry.captureMessage("Slow network request", {
        extra: {
          endpoint,
          durationMs,
        },
        level: "warning",
      });
    }
  }
}

16. References

16.1 API Documentation

16.2 Libraries and Dependencies

16.3 Design Systems

16.4 Internal Resources


Last Updated: 08 May 2025 at 01:02 am IST by samir


Last Updated: 08 May 2025 at 12:06 pm IST by apple