142 lines
4.7 KiB
TypeScript
142 lines
4.7 KiB
TypeScript
/**
|
||
* PermissionMatrix
|
||
*
|
||
* User × Role matrix with inline toggles and edit/remove actions.
|
||
*/
|
||
|
||
import React, { useState } from 'react';
|
||
import { FaEdit, FaTrash } from 'react-icons/fa';
|
||
import type { FeatureAccessUser } from '../../hooks/useFeatureAccess';
|
||
import type { FeatureInstanceRole } from '../../hooks/useFeatureAccess';
|
||
import styles from './Admin.module.css';
|
||
import matrixStyles from './PermissionMatrix.module.css';
|
||
|
||
export interface PermissionMatrixProps {
|
||
users: FeatureAccessUser[];
|
||
roles: FeatureInstanceRole[];
|
||
onEditUser: (user: FeatureAccessUser) => void;
|
||
onRemoveUser: (user: FeatureAccessUser) => void;
|
||
onAddUser: () => void;
|
||
disabled?: boolean;
|
||
}
|
||
|
||
export const PermissionMatrix: React.FC<PermissionMatrixProps> = ({
|
||
users,
|
||
roles,
|
||
onEditUser,
|
||
onRemoveUser,
|
||
onAddUser,
|
||
disabled = false,
|
||
}) => {
|
||
const [removingId, setRemovingId] = useState<string | null>(null);
|
||
|
||
const handleRemove = (user: FeatureAccessUser) => {
|
||
if (removingId) return;
|
||
if (window.confirm(`"${user.username}" aus dieser Instanz entfernen?`)) {
|
||
setRemovingId(user.userId);
|
||
onRemoveUser(user);
|
||
setRemovingId(null);
|
||
}
|
||
};
|
||
|
||
if (roles.length === 0) {
|
||
return (
|
||
<div className={matrixStyles.empty}>
|
||
<p>Keine Rollen in dieser Instanz. Bitte zuerst Rollen synchronisieren.</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className={matrixStyles.wrapper}>
|
||
<div className={matrixStyles.tableWrap}>
|
||
<table className={matrixStyles.table}>
|
||
<thead>
|
||
<tr>
|
||
<th className={matrixStyles.cellUser}>Benutzer</th>
|
||
{roles.map((r) => (
|
||
<th key={r.id} className={matrixStyles.cellRole}>
|
||
{r.roleLabel}
|
||
</th>
|
||
))}
|
||
<th className={matrixStyles.cellActive}>Aktiv</th>
|
||
<th className={matrixStyles.cellActions}>Aktionen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{users.length === 0 ? (
|
||
<tr>
|
||
<td colSpan={roles.length + 3} className={matrixStyles.cellEmpty}>
|
||
Keine Benutzer zugewiesen.
|
||
</td>
|
||
</tr>
|
||
) : (
|
||
users.map((user) => (
|
||
<tr key={user.id}>
|
||
<td className={matrixStyles.cellUser}>
|
||
<span className={matrixStyles.userName}>{user.username}</span>
|
||
{user.email && (
|
||
<span className={matrixStyles.userEmail}>{user.email}</span>
|
||
)}
|
||
</td>
|
||
{roles.map((role) => {
|
||
const hasRole = user.roleIds?.includes(role.id) ?? false;
|
||
return (
|
||
<td key={role.id} className={matrixStyles.cellRole}>
|
||
<span
|
||
className={`${matrixStyles.badge} ${hasRole ? matrixStyles.badgeActive : ''}`}
|
||
title={hasRole ? role.roleLabel : ''}
|
||
>
|
||
{hasRole ? '✓' : '—'}
|
||
</span>
|
||
</td>
|
||
);
|
||
})}
|
||
<td className={matrixStyles.cellActive}>
|
||
<span
|
||
className={`${matrixStyles.badge} ${user.enabled ? matrixStyles.badgeActive : ''}`}
|
||
>
|
||
{user.enabled ? '✓' : '—'}
|
||
</span>
|
||
</td>
|
||
<td className={matrixStyles.cellActions}>
|
||
<button
|
||
type="button"
|
||
className={matrixStyles.actionBtn}
|
||
onClick={() => onEditUser(user)}
|
||
disabled={disabled}
|
||
title="Rollen bearbeiten"
|
||
>
|
||
<FaEdit />
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className={`${matrixStyles.actionBtn} ${matrixStyles.actionBtnDanger}`}
|
||
onClick={() => handleRemove(user)}
|
||
disabled={disabled || removingId === user.userId}
|
||
title="Aus Instanz entfernen"
|
||
>
|
||
<FaTrash />
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
))
|
||
)}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div className={matrixStyles.footer}>
|
||
<button
|
||
type="button"
|
||
className={styles.primaryButton}
|
||
onClick={onAddUser}
|
||
disabled={disabled}
|
||
>
|
||
+ Benutzer hinzufügen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default PermissionMatrix;
|