feat: use select dropdowns for target role/group in claim mapping editor

Populate target field from existing roles (assign role) or groups
(add to group) instead of free-text input, preventing typos.
Switching action resets the target selection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-14 17:02:09 +02:00
parent c02fd77c30
commit 0e87161426

View File

@@ -9,6 +9,7 @@ import {
useTestClaimMappingRules,
} from '../../api/queries/admin/claim-mappings';
import type { ClaimMappingRule, TestResponse } from '../../api/queries/admin/claim-mappings';
import { useRoles, useGroups } from '../../api/queries/admin/rbac';
import styles from './ClaimMappingRulesModal.module.css';
const MATCH_OPTIONS = [
@@ -60,6 +61,17 @@ export default function ClaimMappingRulesModal({ open, onClose }: Props) {
const [testResult, setTestResult] = useState<TestResponse | null>(null);
const [testError, setTestError] = useState('');
// Roles and groups for target dropdown
const { data: roles = [] } = useRoles(open);
const { data: groups = [] } = useGroups(open);
const roleOptions = roles.map((r) => ({ value: r.name, label: r.name }));
const groupOptions = groups.map((g) => ({ value: g.name, label: g.name }));
function targetOptions(action: string) {
return action === 'assignRole' ? roleOptions : groupOptions;
}
if (!open) return null;
const sorted = [...rules].sort((a, b) => a.priority - b.priority);
@@ -230,8 +242,8 @@ export default function ClaimMappingRulesModal({ open, onClose }: Props) {
<td><Input value={editForm.claim} onChange={(e) => setEditForm({ ...editForm, claim: e.target.value })} className={styles.claimInput} /></td>
<td><Select options={MATCH_OPTIONS} value={editForm.matchType} onChange={(e) => setEditForm({ ...editForm, matchType: e.target.value })} /></td>
<td><Input value={editForm.matchValue} onChange={(e) => setEditForm({ ...editForm, matchValue: e.target.value })} /></td>
<td><Select options={ACTION_OPTIONS} value={editForm.action} onChange={(e) => setEditForm({ ...editForm, action: e.target.value })} /></td>
<td><Input value={editForm.target} onChange={(e) => setEditForm({ ...editForm, target: e.target.value })} /></td>
<td><Select options={ACTION_OPTIONS} value={editForm.action} onChange={(e) => setEditForm({ ...editForm, action: e.target.value, target: '' })} /></td>
<td><Select options={targetOptions(editForm.action)} value={editForm.target} onChange={(e) => setEditForm({ ...editForm, target: e.target.value })} /></td>
<td>
<div className={styles.actions}>
<button className={styles.iconBtn} onClick={() => saveEdit(rule)} title="Save"><Check size={14} /></button>
@@ -272,13 +284,8 @@ export default function ClaimMappingRulesModal({ open, onClose }: Props) {
<Input placeholder="Claim name" value={addForm.claim} onChange={(e) => setAddForm({ ...addForm, claim: e.target.value })} className={styles.claimInput} />
<Select options={MATCH_OPTIONS} value={addForm.matchType} onChange={(e) => setAddForm({ ...addForm, matchType: e.target.value })} className={styles.matchSelect} />
<Input placeholder="Match value" value={addForm.matchValue} onChange={(e) => setAddForm({ ...addForm, matchValue: e.target.value })} className={styles.valueInput} />
<Select options={ACTION_OPTIONS} value={addForm.action} onChange={(e) => setAddForm({ ...addForm, action: e.target.value })} className={styles.actionSelect} />
<Input
placeholder={addForm.action === 'assignRole' ? 'Role name' : 'Group name'}
value={addForm.target}
onChange={(e) => setAddForm({ ...addForm, target: e.target.value })}
className={styles.targetInput}
/>
<Select options={ACTION_OPTIONS} value={addForm.action} onChange={(e) => setAddForm({ ...addForm, action: e.target.value, target: '' })} className={styles.actionSelect} />
<Select options={targetOptions(addForm.action)} value={addForm.target} onChange={(e) => setAddForm({ ...addForm, target: e.target.value })} className={styles.targetInput} />
<Button size="sm" variant="primary" onClick={handleAdd} disabled={addDisabled} loading={createRule.isPending}>+ Add</Button>
</div>