diff --git a/backend/migrations/manual/027_consolidate_manager_role.sql b/backend/migrations/manual/027_consolidate_manager_role.sql
new file mode 100644
index 0000000..f674007
--- /dev/null
+++ b/backend/migrations/manual/027_consolidate_manager_role.sql
@@ -0,0 +1,29 @@
+-- 027: The hand-made Access Control role "Manager" duplicates the seeded
+-- hiring_manager, which has carried the manager_candidates bundle since 024.
+-- Consolidate: move its live members onto hiring_manager, then soft-delete
+-- it. Both steps are idempotent and guarded; the seeded hiring_manager row
+-- is never matched (name check excludes it, and it is is_system).
+UPDATE app.users u
+SET role_id = hm.id,
+ updated_at = NOW()
+FROM app.roles hm
+WHERE hm.role_name = 'hiring_manager' AND hm.is_deleted = FALSE
+ AND u.role_id IN (
+ SELECT r.id FROM app.roles r
+ WHERE lower(r.role_name) = 'manager'
+ AND r.role_name <> 'hiring_manager'
+ AND r.is_deleted = FALSE
+ )
+ AND COALESCE(u.is_deleted, FALSE) = FALSE;
+
+UPDATE app.roles r
+SET is_deleted = TRUE,
+ is_active = FALSE,
+ updated_at = NOW()
+WHERE lower(r.role_name) = 'manager'
+ AND r.role_name <> 'hiring_manager'
+ AND r.is_deleted = FALSE
+ AND NOT EXISTS (
+ SELECT 1 FROM app.users u
+ WHERE u.role_id = r.id AND COALESCE(u.is_deleted, FALSE) = FALSE
+ );
diff --git a/frontend/src/app/Topbar.jsx b/frontend/src/app/Topbar.jsx
index 1256fd2..4d36506 100644
--- a/frontend/src/app/Topbar.jsx
+++ b/frontend/src/app/Topbar.jsx
@@ -10,6 +10,7 @@ import { useTheme } from '../theme/ThemeProvider'
import { useAuth } from '../auth/AuthContext'
import { qk } from '../lib/queryKeys'
import { friendlyAuthError } from '../lib/errors'
+import { formatRole } from '../lib/format'
import * as notificationsApi from '../api/notifications'
function initialsFromName(name) {
@@ -55,7 +56,7 @@ export default function Topbar({ onOpenNav, searchRef }) {
const name = user?.name || 'Guest'
const email = user?.email || ''
- const role = user?.role_name || user?.role || 'Member'
+ const role = formatRole(user?.role_name || user?.role) || 'Member'
function openNotif(n) {
if (n.unread) markOne.mutate(n.id)
diff --git a/frontend/src/lib/format.js b/frontend/src/lib/format.js
new file mode 100644
index 0000000..97ec6a7
--- /dev/null
+++ b/frontend/src/lib/format.js
@@ -0,0 +1,11 @@
+/** Display formatting for identifiers stored snake_case in the DB. */
+
+/** 'system_administrator' → 'System Administrator', 'INTERVIEW' → 'Interview'. */
+export function formatRole(name) {
+ const s = String(name || '').trim()
+ if (!s) return s
+ return s
+ .split(/[_\s]+/)
+ .map((w) => (w ? w[0].toUpperCase() + w.slice(1).toLowerCase() : w))
+ .join(' ')
+}
diff --git a/frontend/src/screens/Candidates.jsx b/frontend/src/screens/Candidates.jsx
index 4bd1c43..4855899 100644
--- a/frontend/src/screens/Candidates.jsx
+++ b/frontend/src/screens/Candidates.jsx
@@ -24,6 +24,7 @@ import CandidateProfile from './CandidateProfile'
import { useJobTitles } from './ScoredCandidateProfile'
import { qk } from '../lib/queryKeys'
import { exportStyledXlsx } from '../lib/exportXlsx'
+import { formatRole } from '../lib/format'
import { friendlyAuthError } from '../lib/errors'
import * as candidatesApi from '../api/candidates'
import * as jobPostsApi from '../api/jobPosts'
@@ -506,7 +507,7 @@ function RecruiterCandidates() {
{ header: 'Account', key: 'account', width: 12 },
],
rows: rows.map((c) => ({
- name: c.name, email: c.email, role: c.roleName,
+ name: c.name, email: c.email, role: formatRole(c.roleName),
applied: c.applied ? c.applied.toLocaleDateString() : '',
source: c.source,
account: c.isActive ? 'Active' : 'Unconfirmed',
@@ -636,7 +637,7 @@ function RecruiterCandidates() {
- {confirmDelete.role_name} will be soft-deleted. Anyone currently holding it keeps the + {formatRole(confirmDelete.role_name)} will be soft-deleted. Anyone currently holding it keeps the account but loses every permission the role granted, so reassign them first.
diff --git a/frontend/src/screens/Settings.jsx b/frontend/src/screens/Settings.jsx index 0a8161e..2a4fa2f 100644 --- a/frontend/src/screens/Settings.jsx +++ b/frontend/src/screens/Settings.jsx @@ -18,6 +18,7 @@ import { useFormState } from '../components/AuthLayout' import { usePermission } from '../auth/AuthContext' import { qk } from '../lib/queryKeys' import { friendlyAuthError } from '../lib/errors' +import { formatRole } from '../lib/format' import * as rolesApi from '../api/roles' import * as usersApi from '../api/users' import * as orgSettingsApi from '../api/orgSettings' @@ -324,7 +325,7 @@ function Users() {