commit
a36fc051ca
6
ToDo.txt
6
ToDo.txt
@ -33,10 +33,12 @@ Last updated: 2026-01-20
|
||||
• [ ] User Status 1 Feld das wir nicht benutzen
|
||||
• [ ] Pool mulit user actions (select 5 -> add to pool)
|
||||
• [x] reset edit templates
|
||||
• [] "Suspended" status should actually do something
|
||||
• [] Matrix shit
|
||||
• [x] "Suspended" status should actually do something
|
||||
• [] Matrix shit (tiefe dynamisch einstellen)
|
||||
• [x] Git
|
||||
• [] Switching status -> confirmation modal
|
||||
• [] mobile scroll bug with double page on top
|
||||
• [] search modal unify -> only return userId(s)
|
||||
|
||||
|
||||
|
||||
|
||||
@ -53,6 +53,12 @@ export default function UserDetailModal({ isOpen, onClose, userId, onUserUpdated
|
||||
const [selectedStatus, setSelectedStatus] = useState<UserStatus>('pending')
|
||||
const token = useAuthStore(state => state.accessToken)
|
||||
|
||||
const [allPermissions, setAllPermissions] = useState<Array<{ id: number; name: string; description?: string; is_active: boolean }>>([])
|
||||
const [permissionsLoading, setPermissionsLoading] = useState(false)
|
||||
const [permissionsError, setPermissionsError] = useState<string | null>(null)
|
||||
const [selectedPermissions, setSelectedPermissions] = useState<string[]>([])
|
||||
const [permissionsSaving, setPermissionsSaving] = useState(false)
|
||||
|
||||
// Contract preview state (lazy-loaded, per contract type)
|
||||
const [activePreviewTab, setActivePreviewTab] = useState<'contract' | 'gdpr'>('contract')
|
||||
const [previewState, setPreviewState] = useState({
|
||||
@ -86,6 +92,7 @@ export default function UserDetailModal({ isOpen, onClose, userId, onUserUpdated
|
||||
useEffect(() => {
|
||||
if (isOpen && userId && token) {
|
||||
fetchUserDetails()
|
||||
fetchAllPermissions()
|
||||
loadContractFiles()
|
||||
}
|
||||
}, [isOpen, userId, token])
|
||||
@ -104,6 +111,7 @@ export default function UserDetailModal({ isOpen, onClose, userId, onUserUpdated
|
||||
})
|
||||
setContractFiles({ contract: [], gdpr: [] })
|
||||
setSelectedFile({})
|
||||
setPermissionsError(null)
|
||||
}, [isOpen, userId])
|
||||
|
||||
useEffect(() => {
|
||||
@ -112,6 +120,12 @@ export default function UserDetailModal({ isOpen, onClose, userId, onUserUpdated
|
||||
}
|
||||
}, [userDetails])
|
||||
|
||||
useEffect(() => {
|
||||
if (userDetails?.permissions) {
|
||||
setSelectedPermissions(userDetails.permissions.map(p => p.name))
|
||||
}
|
||||
}, [userDetails])
|
||||
|
||||
const fetchUserDetails = async () => {
|
||||
if (!userId || !token) return
|
||||
|
||||
@ -134,6 +148,58 @@ export default function UserDetailModal({ isOpen, onClose, userId, onUserUpdated
|
||||
}
|
||||
}
|
||||
|
||||
const fetchAllPermissions = async () => {
|
||||
if (!token) return
|
||||
setPermissionsLoading(true)
|
||||
setPermissionsError(null)
|
||||
try {
|
||||
const response = await AdminAPI.getPermissions(token)
|
||||
if (response.success) {
|
||||
setAllPermissions(response.permissions || [])
|
||||
} else {
|
||||
throw new Error(response.message || 'Failed to fetch permissions')
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch permissions'
|
||||
setPermissionsError(errorMessage)
|
||||
console.error('UserDetailModal.fetchAllPermissions error:', err)
|
||||
} finally {
|
||||
setPermissionsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const togglePermission = (permName: string) => {
|
||||
setSelectedPermissions(prev => {
|
||||
if (prev.includes(permName)) {
|
||||
return prev.filter(p => p !== permName)
|
||||
}
|
||||
return [...prev, permName]
|
||||
})
|
||||
}
|
||||
|
||||
const handleSavePermissions = async () => {
|
||||
if (!userId || !token) return
|
||||
setPermissionsSaving(true)
|
||||
setPermissionsError(null)
|
||||
try {
|
||||
const response = await AdminAPI.updateUserPermissions(token, userId, selectedPermissions)
|
||||
if (response.success) {
|
||||
await fetchUserDetails()
|
||||
if (onUserUpdated) {
|
||||
onUserUpdated()
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.message || 'Failed to update permissions')
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to update permissions'
|
||||
setPermissionsError(errorMessage)
|
||||
console.error('UserDetailModal.handleSavePermissions error:', err)
|
||||
} finally {
|
||||
setPermissionsSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleStatusChange = async (newStatus: UserStatus) => {
|
||||
if (!userId || !token || newStatus === selectedStatus) return
|
||||
|
||||
@ -794,42 +860,69 @@ export default function UserDetailModal({ isOpen, onClose, userId, onUserUpdated
|
||||
)}
|
||||
|
||||
{/* Permissions */}
|
||||
{userDetails.permissions.length > 0 && (
|
||||
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div className="bg-gray-50 px-6 py-4 border-b border-gray-200">
|
||||
<h3 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
|
||||
<ShieldCheckIcon className="h-5 w-5 text-gray-600" />
|
||||
Permissions ({userDetails.permissions.length})
|
||||
</h3>
|
||||
</div>
|
||||
<div className="px-6 py-5">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{userDetails.permissions.map((perm) => (
|
||||
<div
|
||||
key={perm.id}
|
||||
className={`flex items-center gap-3 p-3 rounded-lg border ${
|
||||
perm.is_active
|
||||
? 'bg-green-50 border-green-200'
|
||||
: 'bg-gray-50 border-gray-200'
|
||||
}`}
|
||||
>
|
||||
{perm.is_active ? (
|
||||
<CheckCircleIcon className="h-5 w-5 text-green-600 flex-shrink-0" />
|
||||
) : (
|
||||
<XCircleIcon className="h-5 w-5 text-gray-400 flex-shrink-0" />
|
||||
)}
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-900">{perm.name}</div>
|
||||
{perm.description && (
|
||||
<div className="text-xs text-gray-500 mt-0.5">{perm.description}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div className="bg-gray-50 px-6 py-4 border-b border-gray-200 flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
|
||||
<ShieldCheckIcon className="h-5 w-5 text-gray-600" />
|
||||
Permissions ({selectedPermissions.length})
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSavePermissions}
|
||||
disabled={permissionsSaving || permissionsLoading}
|
||||
className="inline-flex items-center justify-center rounded-md bg-indigo-600 hover:bg-indigo-500 text-white px-3 py-2 text-sm disabled:opacity-60"
|
||||
>
|
||||
{permissionsSaving ? 'Saving…' : 'Save Permissions'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="px-6 py-5">
|
||||
{permissionsError && (
|
||||
<div className="rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 mb-4">
|
||||
{permissionsError}
|
||||
</div>
|
||||
)}
|
||||
{permissionsLoading ? (
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500">
|
||||
<div className="h-4 w-4 border-2 border-gray-400 border-b-transparent rounded-full animate-spin" />
|
||||
Loading permissions…
|
||||
</div>
|
||||
) : allPermissions.length === 0 ? (
|
||||
<div className="text-sm text-gray-500">No permissions available.</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{allPermissions.map((perm) => {
|
||||
const checked = selectedPermissions.includes(perm.name)
|
||||
const disabled = !perm.is_active
|
||||
return (
|
||||
<label
|
||||
key={perm.id}
|
||||
className={`flex items-start gap-3 p-3 rounded-lg border cursor-pointer ${
|
||||
disabled ? 'bg-gray-50 border-gray-200 opacity-70 cursor-not-allowed' : checked ? 'bg-green-50 border-green-200' : 'bg-white border-gray-200'
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
||||
checked={checked}
|
||||
disabled={disabled || permissionsSaving}
|
||||
onChange={() => togglePermission(perm.name)}
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-900">{perm.name}</div>
|
||||
{perm.description && (
|
||||
<div className="text-xs text-gray-500 mt-0.5">{perm.description}</div>
|
||||
)}
|
||||
{!perm.is_active && (
|
||||
<div className="text-xs text-gray-400 mt-0.5">Inactive</div>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex justify-end gap-3 pt-4">
|
||||
|
||||
@ -32,6 +32,9 @@ export const API_ENDPOINTS = {
|
||||
|
||||
// Documents
|
||||
USER_DOCUMENTS: '/api/users/:id/documents',
|
||||
|
||||
// Permissions
|
||||
PERMISSIONS_LIST: '/api/permissions',
|
||||
|
||||
// Admin
|
||||
ADMIN_USERS: '/api/admin/users/:id/full',
|
||||
@ -46,6 +49,7 @@ export const API_ENDPOINTS = {
|
||||
ADMIN_UPDATE_USER_VERIFICATION: '/api/admin/update-verification/:id',
|
||||
ADMIN_UPDATE_USER_PROFILE: '/api/admin/update-user-profile/:id',
|
||||
ADMIN_UPDATE_USER_STATUS: '/api/admin/update-user-status/:id',
|
||||
ADMIN_UPDATE_USER_PERMISSIONS: '/api/admin/users/:id/permissions',
|
||||
// Pools (admin)
|
||||
ADMIN_POOL_MEMBERS: '/api/admin/pools/:id/members',
|
||||
// Coffee products (admin)
|
||||
@ -395,6 +399,25 @@ export class AdminAPI {
|
||||
return response.json()
|
||||
}
|
||||
|
||||
static async getPermissions(token: string) {
|
||||
const response = await ApiClient.get(API_ENDPOINTS.PERMISSIONS_LIST, token)
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Failed to fetch permissions' }))
|
||||
throw new Error(error.message || 'Failed to fetch permissions')
|
||||
}
|
||||
return response.json()
|
||||
}
|
||||
|
||||
static async updateUserPermissions(token: string, userId: string, permissions: string[]) {
|
||||
const endpoint = API_ENDPOINTS.ADMIN_UPDATE_USER_PERMISSIONS.replace(':id', userId)
|
||||
const response = await ApiClient.put(endpoint, { permissions }, token)
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Failed to update user permissions' }))
|
||||
throw new Error(error.message || 'Failed to update user permissions')
|
||||
}
|
||||
return response.json()
|
||||
}
|
||||
|
||||
// Coffee products (admin)
|
||||
static async listCoffee(token: string) {
|
||||
const response = await ApiClient.get(API_ENDPOINTS.ADMIN_COFFEE_LIST, token)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user