151 lines
5.1 KiB
TypeScript
151 lines
5.1 KiB
TypeScript
/**
|
||
* PermissionMatrix
|
||
*
|
||
* User × Role matrix with inline toggles and edit/remove actions.
|
||
*/
|
||
|
||
import React, { useState, useCallback } from 'react';
|
||
import { FaEdit, FaTrash } from 'react-icons/fa';
|
||
import { useConfirm } from '../../hooks/useConfirm';
|
||
import type { FeatureAccessUser } from '../../hooks/useFeatureAccess';
|
||
import type { FeatureInstanceRole } from '../../hooks/useFeatureAccess';
|
||
import styles from './Admin.module.css';
|
||
import matrixStyles from './PermissionMatrix.module.css';
|
||
|
||
import { useLanguage } from '../../providers/language/LanguageContext';
|
||
|
||
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 { t } = useLanguage();
|
||
const [removingId, setRemovingId] = useState<string | null>(null);
|
||
const { confirm, ConfirmDialog } = useConfirm();
|
||
|
||
const handleRemove = useCallback(async (user: FeatureAccessUser) => {
|
||
if (removingId) return;
|
||
const ok = await confirm(t('"{name}" aus dieser Instanz entfernen?', { name: user.username }), {
|
||
title: t('Benutzer entfernen'),
|
||
confirmLabel: t('Entfernen'),
|
||
variant: 'danger',
|
||
});
|
||
if (!ok) return;
|
||
setRemovingId(user.userId);
|
||
onRemoveUser(user);
|
||
setRemovingId(null);
|
||
}, [removingId, confirm, onRemoveUser, t]);
|
||
|
||
if (roles.length === 0) {
|
||
return (
|
||
<div className={matrixStyles.empty}>
|
||
<p>{t('Keine Rollen in dieser Instanz')}</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className={matrixStyles.wrapper}>
|
||
<div className={matrixStyles.tableWrap}>
|
||
<table className={matrixStyles.table}>
|
||
<thead>
|
||
<tr>
|
||
<th className={matrixStyles.cellUser}>{t('Benutzer')}</th>
|
||
{roles.map((r) => (
|
||
<th key={r.id} className={matrixStyles.cellRole}>
|
||
{r.roleLabel}
|
||
</th>
|
||
))}
|
||
<th className={matrixStyles.cellActive}>{t('Aktiv')}</th>
|
||
<th className={matrixStyles.cellActions}>{t('Aktionen')}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{users.length === 0 ? (
|
||
<tr>
|
||
<td colSpan={roles.length + 3} className={matrixStyles.cellEmpty}>
|
||
{t('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={t('Rollen bearbeiten')}
|
||
>
|
||
<FaEdit />
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className={`${matrixStyles.actionBtn} ${matrixStyles.actionBtnDanger}`}
|
||
onClick={() => handleRemove(user)}
|
||
disabled={disabled || removingId === user.userId}
|
||
title={t('Aus Instanz entfernen')}
|
||
>
|
||
<FaTrash />
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
))
|
||
)}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div className={matrixStyles.footer}>
|
||
<button
|
||
type="button"
|
||
className={styles.primaryButton}
|
||
onClick={onAddUser}
|
||
disabled={disabled}
|
||
>
|
||
+ {t('Benutzer hinzufügen')}
|
||
</button>
|
||
</div>
|
||
<ConfirmDialog />
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default PermissionMatrix;
|