# Offers

This directory contains everything related to job offer management: creating offers, managing their hiring pipeline, publishing them to job boards, and tracking applicants through the pipeline stages.

## Directory structure

```
Offers/
├── index.tsx                         # Offers list page
├── constants.ts                      # Form field definitions and validation config
├── utils.ts                          # Form resolvers, validators, initial values, answer helpers
│
├── shared/
│   └── summaryTypes.ts               # Section / Subsection / SummaryItem types for DetailSectionSummary
│
├── components/                       # Shared UI components for the Offers sub-module
│   ├── OffersFiltersDrawerContent/   # Drawer with department + brand filter accordions
│   │   ├── index.tsx
│   │   └── components/FilterList/
│   └── OffersSearchBar/              # Search input for the offers list
│
├── JobOfferForm/                     # CREATE wizard (multi-step form)
│   ├── index.tsx
│   └── components/
│       ├── GeneralConfigStep.tsx     # Step 1 — isolated, accepts form prop
│       ├── ProcessStages/            # Step 2 — isolated, accepts form + jobId props
│       │   ├── index.tsx
│       │   └── components/
│       │       ├── ProcessStage.tsx
│       │       └── ProcessStagesFieldArray.tsx
│       ├── PublishStep/              # Step 3 — isolated, accepts publishForm prop
│       │   ├── index.tsx
│       │   ├── constants.ts
│       │   └── components/
│       │       └── ApplicationFormTab/   # Field-visibility editor for the application form
│       │           ├── index.tsx
│       │           ├── FieldRow.tsx
│       │           ├── constants.ts
│       │           └── utils.ts
│       ├── ReviewStep/               # Step 4 — summary before launch
│       │   └── index.tsx
│       ├── StepMultipleSummary/      # Multi-row summary widget used in ReviewStep
│       │   ├── index.tsx
│       │   └── ItemsList.tsx
│       └── StepSummary.tsx           # Single-row summary widget used in ReviewStep
│
├── JobDetail/                        # Read-only job detail view (sections summary)
│   ├── index.tsx
│   └── components/
│       └── DetailSectionSummary/     # Reusable card for displaying key/value section data
│           ├── index.tsx
│           ├── ItemsList.tsx
│           └── SummaryItemRow.tsx
│
├── JobOfferEdit/                     # Edit page — reuses isolated step components (General Config)
│   └── index.tsx
│
├── OfferDetail/                      # Candidates view for a job offer
│   ├── index.tsx
│   ├── components/
│   │   ├── DraftJobCard.tsx
│   │   ├── JobOfferCandidatesTable/
│   │   ├── JobOfferStatusFilter.tsx
│   │   └── JobOfferStagesFilter.tsx
│   └── hooks/
│       ├── useApplicantDrawerContent.tsx
│       ├── useChangeJobStatus.ts
│       ├── useCloseJobOfferErrorModal.tsx
│       ├── useCloseJobOfferModal.tsx
│       ├── useDeleteJobOfferModal.tsx
│       ├── useJobDetailApplicants.ts
│       ├── useOfferDetailActions.ts
│       ├── useOpenJobOfferModal.tsx
│       ├── usePauseJobPosting.ts
│       └── useShareJobModal.tsx
│
├── JobApplicationDetail/             # Applicant screening view
│   ├── index.tsx
│   ├── utils.ts
│   ├── constants/
│   │   └── profileDisplayRules.ts   # SECTION_LAYOUT_RULES, HIDDEN_SECTION_CODES, FIELD_DISPLAY_RULES, SOCIAL_NETWORK_CONFIG
│   ├── components/
│   │   ├── AdditionalDocumentation.tsx
│   │   ├── ApplicationStage.tsx
│   │   ├── ApplicationStageFiles.tsx
│   │   ├── ApplicationStageFooter.tsx
│   │   ├── ApplicationStageNotes.tsx
│   │   ├── ContactInfoAndActions.tsx
│   │   ├── DeleteFileModal.tsx
│   │   ├── DeleteNoteModal.tsx
│   │   ├── FilesAndNotesEmptyState.tsx
│   │   ├── HiredOrRejectedAlert.tsx
│   │   ├── MoveApplicantForwardModal.tsx
│   │   ├── OfferFile.tsx
│   │   ├── OfferSwitch.tsx
│   │   ├── RejectApplicantModal.tsx
│   │   ├── SideContentTabs.tsx
│   │   └── profile/                 # Candidate profile tab components
│   │       ├── ProfileTab.tsx        # Orchestrates all profile sections
│   │       ├── ApplicationDataSection.tsx
│   │       ├── ApplyFormSection.tsx
│   │       ├── LinkListItem.tsx
│   │       ├── ProfileFieldValue.tsx
│   │       └── SocialNetworksSection.tsx
│   └── hooks/
│       ├── useActionDrawers.tsx
│       ├── useApplicantUpdateDrawerContent.tsx
│       ├── useApplicationFiles.ts
│       ├── useFilesDrawerContent.tsx
│       ├── useHireModal.tsx
│       ├── useMoveApplicantStageDrawerContent.tsx
│       └── useNotesDrawerContent.tsx
│
├── JobOfferPublish/                  # Manage job board postings
│   ├── index.tsx
│   └── hooks/
│       ├── useJobOpenFromPublishModal.tsx
│       └── usePublicDescriptionWarningModal.tsx
│
└── hooks/                            # All offer-level hooks
    ├── useGeneralConfigSection.ts    # Isolated General Config form + mutation
    ├── useProcessStagesSection.ts    # Isolated Process Stages form + mutation
    ├── useJobPublicDescriptionSection.ts # Isolated Publish form + mutation
    ├── useJobApplicationFormSection.ts   # Isolated Application Form visibility mutation
    ├── useJobForm.tsx                # CREATE wizard orchestrator (composes section hooks)
    ├── useJob.ts                     # Fetch a single job offer
    ├── useInfiniteJobs.ts            # Paginated job offers list
    ├── useHiringPipeline.ts          # Fetch + mutate hiring pipeline stages
    ├── useHiringReasons.ts           # Fetch hiring reason options
    ├── useJobBoards.tsx              # Check if job boards exist
    ├── useJobPosting.tsx             # Fetch active job posting + construct public URL
    ├── useJobPublicDescription.ts    # Fetch public job description
    ├── usePublishForm.tsx            # Publish step form factory
    ├── useApplicationDetail.ts       # Fetch a single applicant application
    ├── useApplicationFormAnswers.ts  # Fetch applicant form answers
    ├── useApplicationFormChanges.ts  # Track in-progress field-visibility changes (delta map)
    ├── useApplicationSources.ts      # Fetch application source options
    ├── useJobApplicationForm.ts      # Fetch job application form structure
    ├── useJobApplicationCounts.ts    # Fetch per-stage & per-status application counts for a job
    ├── useJobFilterOptions.ts        # Fetch department + brand options for filters
    ├── useOffersFilters.ts           # URL-param-backed filter state (search, departments, brands)
    ├── useOffersFiltersDrawer.tsx    # Opens OffersFiltersDrawerContent via DrawerLayer
    ├── useRejectionReasons.ts        # Fetch rejection reason options
    └── useRouteId.ts                 # Parse numeric :id from the current route
```

---

## Form architecture

Each form section is **fully isolated** — it owns its own `useForm` instance, its own `FormProvider`, its own validation schema and resolver. Sections do not share form state.

### Section pattern

Every section follows the same contract:

```
useXxxSection(args)
  → { form, onSave, isLoading, ...sectionSpecificData }

<XxxStep form={form} ... />
  → wraps render in <FormProvider {...form}>
  → uses form.watch / form.trigger directly (never useFormContext at root level)
```

### The four form types

| Type | Fields | Validator |
|---|---|---|
| `GeneralConfigForm` | `id`, `name`, `externalId`, `isTestJob`, `department`, `positionName`, `hireReasonId`, `targetCloseDate`, `targetEmployeeStartDate`, `jobOpenings` | `generalConfigFormResolver` |
| `ProcessStagesForm` | `processStages: ProcessStageForm[]` | `processStagesFormResolver` |
| `JobOfferPublishForm` | `title`, `description` | `JobOfferPublishFormResolver` |
| Application Form (visibility) | Field-by-field `JobApplicationFormFieldVisibilityValue` — not an RHF form; managed via `useApplicationFormChanges` delta map | — |

### Field definitions

Form field metadata (name, label, placeholder) is defined in `constants.ts`:

| Constant / Utility | Purpose |
|---|---|
| `generalConfigFormFields(t)` | Field metadata typed to `GeneralConfigForm` |
| `processStagesFormFields(t)` | Field metadata typed to `ProcessStagesForm` |
| `jobOfferPublishFormFields(t)` | Field metadata typed to `JobOfferPublishForm` |
| `requiredLabel(label)` / `optionalLabel(label, text)` | Label formatters in `../utils/form.ts` — append `*` or ` (text)` |

---

## Hooks

### Section hooks (reusable across pages)

#### `useGeneralConfigSection(job?)`
Manages the General Config form and the create/edit job mutation.

```ts
const { form, onSave, isLoading, hiringReasons, jobId } =
  useGeneralConfigSection(job?);
```

- `form` — `UseFormReturn<GeneralConfigForm>`
- `onSave(cb?)` — submits the form; calls `createJob` or `editJob` depending on whether `jobId` exists; calls `cb(showAlert)` on success
- `jobId` — starts as `job?.id`, updated after first successful create

#### `useProcessStagesSection(jobId)`
Manages the Process Stages form and the hiring pipeline mutation.

```ts
const { form, onSave, isLoading, backendHiringPipeline, isLoadingBackendHiringPipeline } =
  useProcessStagesSection(jobId);
```

- `form` — `UseFormReturn<ProcessStagesForm>`
- `onSave(cb?)` — submits the form; calls `updateHiringPipeline`; resets form with returned stage IDs on success
- `backendHiringPipeline` — current pipeline from the API (used to render locked stages)

#### `useJobPublicDescriptionSection({ jobId, publishForm, publicDescription? })`
Manages the save mutation for the publish form.

```ts
const { onSave, isLoading } = useJobPublicDescriptionSection({ jobId, publishForm, publicDescription });
```

- `onSave(cb?)` — calls `createJobOfferPublicDescription` or `editJobOfferPublicDescription` depending on whether `publicDescription` exists; resets form on success

#### `useJobApplicationFormSection({ jobId, changes, onSuccess })`
Manages the field-visibility update mutation for the Application Form tab.

```ts
const { onSave, isLoading, isApplicationFormDirty } =
  useJobApplicationFormSection({ jobId, changes, onSuccess });
```

- `changes` — delta map from `useApplicationFormChanges` (only fields that differ from the backend value)
- `onSave(cb?)` — no-ops if `changes` is empty; otherwise calls `updateJobApplicationFormVisibility`; invalidates `jobKeys.applicationForm(jobId)` on success

### Wizard hook

#### `useJobForm()` — CREATE only
Orchestrates the four-step CREATE wizard by composing the three section hooks. Manages step navigation, footer button state, and the open/publish job mutations triggered from the Review step.

```ts
const {
  generalConfigForm,               // UseFormReturn<GeneralConfigForm>
  processStagesForm,               // UseFormReturn<ProcessStagesForm>
  publishForm,                     // UseFormReturn<JobOfferPublishForm>
  steps,                           // [{ id, label, disabled }] — used by SideStepper
  footer,                          // FooterProps — next/back/draft buttons
  activeStep,
  setActiveStep,
  onSave,                          // dispatches to the correct section's onSave based on activeStep
  jobId,
  hiringReasons,
  backendHiringPipeline,
  isLoadingBackendHiringPipeline,
  hasJobBoards,
  isJobPublicDescriptionSaved,
  jobName,
  isLoadingSave,
} = useJobForm();
```

### Filter hooks

#### `useOffersFilters()`
URL search-param-backed filter state. All filter values live in the URL — no useState.

```ts
const { search, departments, brands, activeFiltersCount, setSearch, setFilters, clearFilters } =
  useOffersFilters();
```

- Lists are encoded as `|`-separated strings in the URL (`FILTERS_LIST_SEPARATOR`)
- All setters use `replace: true` to avoid polluting browser history

#### `useOffersFiltersDrawer()`
Opens `OffersFiltersDrawerContent` in the DrawerLayer with the current filter state.

---

## Pages

### Offers list (`index.tsx`)
- Uses `useInfiniteJobs` for paginated, infinite-scroll data
- Search and filter state managed by `useOffersFilters`
- Filter drawer opened via `useOffersFiltersDrawer`
- Sortable by name, date, applicant count
- Navigates to `recruitingRoutes.jobOffer(id)` (OfferDetail) on row click

### JobOfferForm — CREATE wizard
Four steps rendered by `renderActiveStep` in `index.tsx`. The outer `<FormProvider>` holds `generalConfigForm` so `ReviewStep` can read general config values via `useFormContext`. Each step component brings its own `FormProvider`.

```
Step 0: GeneralConfigStep       → useGeneralConfigSection form
Step 1: ProcessStages           → useProcessStagesSection form
Step 2: PublishStep             → usePublishForm form + ApplicationFormTab (field visibility)
Step 3: ReviewStep              → reads GeneralConfigForm from outer context + receives publishForm as prop
```

After the Review step the user can:
- **Open without publish** — calls `openJobOffer`, navigates to `jobDetail`
- **Publish** — calls `openJobOffer` then `createJobPosting`, navigates to `jobDetail`

### JobDetail (`JobDetail/index.tsx`)
Read-only summary view of an existing job offer. Renders each section (General Config, Process Stages, Publish) as a `DetailSectionSummary` card. Navigates to `JobOfferEdit` for editing. Shows a publish empty state if no public description exists and job boards are available.

### JobOfferEdit (`JobOfferEdit/index.tsx`)
Edit page for an existing job offer's General Config. Uses `useGeneralConfigSection` to manage form state and the save mutation. On success navigates back to `JobDetail`.

### OfferDetail (`OfferDetail/index.tsx`)
The candidates view for a job. Displays:
- Job offer header (name, status pill, context-sensitive actions)
- Pipeline stage filters
- Status filters (Active / Hired / Rejected)
- `JobOfferCandidatesTable` with inline candidate actions

#### Header actions per status

| Status | `mainActions` | `extraActions` |
|---|---|---|
| All | Job Details | — |
| **Draft** | Job Details | Open · Publish (if no posting & job boards) · Delete |
| **Open** (no posting) | Job Details | Publish (if job boards) · Close |
| **Open** (published) | Job Details | View Posting · Share · Pause · Close |
| **Closed** | Job Details | Open (if no hired) · Publish (if job boards) · Delete |

Both `mainActions` and `extraActions` return `[]` while any data is loading.

### JobApplicationDetail (`JobApplicationDetail/index.tsx`)
Full-screen applicant screening view. Two-column layout:
- **Left** — ordered list of pipeline stages, each showing dates, files, and notes
- **Right** — tabs: Profile (candidate application answers), Overview (contact info + quick actions), Activity, Files

Key actions available: Reject, Move Forward, Hire, Pause.

The **Profile tab** renders `ProfileTab`, which reads sections from `jobApplicationFormAnswers`. `application.resume` overrides the form-answer value for the `resume` field so edits via the update drawer are always reflected without a re-fetch.

### JobOfferPublish (`JobOfferPublish/index.tsx`)
Manage job board postings for an offer. Allows creating, editing, and deleting postings on connected career sites. The publish button is only gated on `isValid` when creating a new posting — `isDirty` is additionally required when editing an existing one. Shows a confirmation modal (`usePublicDescriptionWarningModal`) when updating a live posting.

---

## Data flow

```
services.ts  (Axios calls)
    ↓
hooks/       (useQuery / useMutation via React Query)
    ↓
Section hooks (form + mutation composed together)
    ↓
Step components (FormProvider + form fields)
    ↓
Page components (orchestrate sections + navigation)
```

## API base URL

All requests go through `/ats/admin`. The endpoint structure follows:

```
/jobs                                              → list / create
/jobs/:id                                          → get / edit / delete
/jobs/:id/state                                    → update status (open, pause, close)
/jobs/:id/hiring-pipeline                          → get / create / update pipeline
/jobs/:id/hiring-pipeline/stages/:stageId          → delete stage
/jobs/:id/applications                             → list candidates
/jobs/:id/applications/:appId                      → get applicant detail
/jobs/:id/applications/:appId/update               → update applicant info
/jobs/:id/public-description                       → get / create / edit public description
/jobs/:id/postings                                 → list / create / delete job board posting
/jobs/:id/application-form                         → get application form structure
/jobs/:id/application-form/fields/visibility       → update field visibility (bulk)
/application-sources                               → list application sources
/filter-options                                    → list department + brand filter values
/rejection-reasons                                 → list rejection reasons
```
