v6.1.0
Recruitment admin UX parity with the certificate module. Closes #90. The recruitment admin (page=ffc-recruitment) goes from inline <table widefat> placeholders to full WP_List_Table-backed listings with pagination, search, sort, bulk actions, and dedicated edit screens — modeled on the patterns the certificate module uses for ffc_form (FormEditor + SubmissionsList) but adapted to the recruitment custom-table backing. Every existing REST endpoint is preserved; the work is purely in the wp-admin UI layer.
This release also folds in the previously-unreleased recruitment fixes that landed on main between 6.0.4 and this tag: the notice↔adjutancy attach UI + REST routes (the missing piece that was blocking CSV imports on a fresh install) and the semicolon CSV delimiter auto-detection (BR/EU spreadsheet compatibility).
Added
RecruitmentNoticesListTable extends WP_List_Table(sprint A1). Sortable columns (code, name, status, created_at), search by code/name, bulk delete, row actions (Edit, Delete with nonce-protected confirm). Status badge column reuses the.ffc-status-*CSS scheme.RecruitmentAdjutanciesListTable extends WP_List_Table(sprint A2). Sortable, paginated, search by slug/name, bulk delete (routed throughDeleteServiceso the §14 referential guards apply), row actions (Edit, Delete). New "Notices using" column shows the attachment count via the junction repository.RecruitmentCandidatesListTable extends WP_List_Table(sprint A2). Repository-driven pagination + LIKE search at SQL level so the table scales past the in-memory limit.extra_tablenavadds CPF / RF filter inputs that hash + single-row-lookup. WP-user column links towp-admin/user-edit.php?user_id=…when the candidate is promoted, em-dash otherwise.RecruitmentAdminAssetsManager+assets/css/ffc-recruitment-admin.css+assets/js/ffc-recruitment-admin.js(sprint A1).admin_enqueue_scriptshook gated by hook suffix containing the page slug. Centralized status-badge + attached-adjutancy CSS. JS bundle exposes a thin fetch helper onwindow.ffcRecruitmentAdmin(consumed in later sprints; inline<script>blocks left in place during the sprint walk and consolidated as the surface stabilizes).RecruitmentNoticeEditPage(sprint B). Dedicated edit screen at?action=edit-notice¬ice_id=Nwith four metabox-style sections: General (code read-only per §3.2 stable identifier rule, name + public_columns_config editable; UI-level guard rejectsrank=false/name=false), Status (current state badge + transition buttons gated byNoticeStateMachine::transition_to;closed → activerequires a reason), Adjutancies (attach/detach UI relocated from the row inline), Classifications (preview + definitive list tables with rank/candidate/adjutancy/score/status, candidate name + adjutancy slug resolved via in-memory lookup map to avoid N+1).RecruitmentCandidateEditPage(sprint C). Dedicated edit screen at?action=edit-candidate&candidate_id=N. Four sections: General (the §12 always-editable fields — name, email, phone, notes; email change re-encrypts + re-hashes), Sensitive data (CPF / RF / linked WP user shown decrypted to cap-holders per §10-bis admin surface), Classifications + call history (every classification this candidate has across notices + inline call-history tables driven byCallRepository::get_history_for_classifications()), Hard-delete (gated per §7-bis with reason input; renders a "blocked" message with the reference count when classifications still reference the candidate).RecruitmentNoticeAdjutancyRepositoryREST exposure (PUT/DELETE /notices/{id}/adjutancies/{adjutancy_id}). N:N junction now has the REST surface that the admin UI needs. Both routes are gated byffc_manage_recruitmentand idempotent: re-attaching an already-attached pair returns 200 withcreated=false; detaching a missing pair also returns 200. Without this, admins could create notices and adjutancies but had no path to attach them, so every CSV import on a fresh install hitrecruitment_notice_has_no_adjutancies(400).- CSV importer — semicolon delimiter auto-detection. Brazilian / EU spreadsheet exports default to
;because,is the locale decimal separator. The importer now sniffs the header line for delimiter (counts,vs;outside quoted segments; semicolons win iff strictly more;than,) and forwards the detected delimiter to every body line. Backwards-compatible: comma-only CSVs still parse as before. Two unit tests pin behavior. RecruitmentCandidateRepository::get_paginated()andcount_paginated(). Added to drive the candidates list table at SQL level instead of loading every row into memory. LIKE search onnamecolumn (CPF/RF/email are encrypted so single-row hash lookup is the only path for those).- Action dispatcher in
RecruitmentAdminPage::render_page— handles row-action GET links (delete-notice,delete-adjutancy,delete-candidate) and routes edit-screen requests (edit-notice,edit-candidate) to dedicated full-page renderers. Each dispatch validates a per-entity nonce.
Changed
- Notices / Adjutancies / Candidates tabs swapped from inline
<table widefat>blocks to the newWP_List_Tablesubclasses. WP screen options ("Items per page") work; pagination + sort + search + bulk actions all use WP's standard query-arg conventions so URL state is shareable. - Notice row inline attach/detach UI removed in sprint A1 and reimplemented on the dedicated Notice edit screen in sprint B. Net effect for users: same controls, less cluttered list view.
RecruitmentLoader::initwires the assets manager + the two edit-screenadmin_post_*handlers.