User management
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Enumerations\UserRole;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserDetailGetController extends CommonController
|
||||
{
|
||||
public function __invoke(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
|
||||
$userData = $user->toArray();
|
||||
|
||||
unset($userData['password'], $userData['remember_token'], $userData['activation_token'], $userData['activation_token_expires_at']);
|
||||
|
||||
$tenantNames = Tenant::pluck('name', 'slug');
|
||||
$userData['nicename'] = $user->getNicename();
|
||||
$userData['fullname'] = $user->getFullName();
|
||||
$userData['local_group_name'] = $tenantNames[$user->local_group] ?? $user->local_group;
|
||||
|
||||
return response()->json([
|
||||
'user' => $userData,
|
||||
'isOwnUser' => auth()->id() === $user->id,
|
||||
'isLvTenant' => $this->tenant->slug === 'lv',
|
||||
'userRoles' => UserRole::all()->map(fn($role) => ['slug' => $role->slug, 'name' => $role->name]),
|
||||
'localGroups' => Tenant::where('is_active_local_group', true)->get()->map(fn($t) => ['slug' => $t->slug, 'name' => $t->name]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserListApiController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$tenantNames = Tenant::pluck('name', 'slug');
|
||||
|
||||
$query = User::query();
|
||||
|
||||
if ($this->tenant->slug === 'lv') {
|
||||
$query->orderBy('lastname')->orderBy('firstname');
|
||||
} else {
|
||||
$query->where('local_group', $this->tenant->slug)
|
||||
->orderBy('lastname')->orderBy('firstname');
|
||||
}
|
||||
|
||||
$users = $query->get()->map(function ($user) use ($tenantNames) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'firstname' => $user->firstname,
|
||||
'lastname' => $user->lastname,
|
||||
'nickname' => $user->nickname,
|
||||
'local_group' => $user->local_group,
|
||||
'local_group_name' => $tenantNames[$user->local_group] ?? $user->local_group,
|
||||
'active' => $user->active,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json(['users' => $users]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
|
||||
class UserListPageController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
$inertiaProvider = new InertiaProvider('Admin/UserList', [
|
||||
'isLvTenant' => $this->tenant->slug === 'lv',
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\UserManagement\Actions\GenerateActivationToken\GenerateActivationTokenCommand;
|
||||
use App\Models\User;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserResetPasswordController extends CommonController
|
||||
{
|
||||
public function __invoke(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
|
||||
if (!$user->email) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Benutzer*in hat keine E-Mail-Adresse hinterlegt.',
|
||||
]);
|
||||
}
|
||||
|
||||
$command = new GenerateActivationTokenCommand($user);
|
||||
$command->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Passwort-Reset-Mail wurde gesendet.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserToggleActiveController extends CommonController
|
||||
{
|
||||
public function __invoke(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
|
||||
if (auth()->id() === $user->id) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Du kannst dich nicht selbst deaktivieren.',
|
||||
]);
|
||||
}
|
||||
|
||||
$user->update(['active' => !$user->active]);
|
||||
|
||||
$status = $user->active ? 'aktiviert' : 'deaktiviert';
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Benutzer*in wurde ' . $status . '.',
|
||||
'active' => $user->active,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserUpdateController extends CommonController
|
||||
{
|
||||
public function __invoke(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
$isOwnUser = auth()->id() === $user->id;
|
||||
$isLvTenant = $this->tenant->slug === 'lv';
|
||||
|
||||
$allowedFields = [
|
||||
'firstname', 'lastname', 'nickname', 'email', 'phone', 'birthday',
|
||||
'membership_id', 'address_1', 'address_2', 'postcode', 'city',
|
||||
'eating_habits', 'swimming_permission', 'first_aid_permission',
|
||||
'bank_account_owner', 'bank_account_iban',
|
||||
'medications', 'allergies', 'intolerances',
|
||||
'user_role_local_group',
|
||||
];
|
||||
|
||||
if ($isLvTenant) {
|
||||
$allowedFields[] = 'local_group';
|
||||
if (!$isOwnUser) {
|
||||
$allowedFields[] = 'user_role_main';
|
||||
}
|
||||
}
|
||||
|
||||
$data = $request->only($allowedFields);
|
||||
$user->update($data);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Benutzerdaten wurden gespeichert.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Admin\Controllers\UserDetailGetController;
|
||||
use App\Domains\Admin\Controllers\UserListApiController;
|
||||
use App\Domains\Admin\Controllers\UserResetPasswordController;
|
||||
use App\Domains\Admin\Controllers\UserToggleActiveController;
|
||||
use App\Domains\Admin\Controllers\UserUpdateController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantContactGetController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantContactUpdateController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantGdprGetController;
|
||||
@@ -37,6 +42,14 @@ Route::middleware([IdentifyTenant::class, 'auth', AdminRoleMiddleware::class])->
|
||||
Route::post('/gdpr', TenantGdprUpdateController::class);
|
||||
});
|
||||
|
||||
Route::prefix('api/v1/admin/users')->group(function () {
|
||||
Route::get('/list', UserListApiController::class);
|
||||
Route::get('/{id}', UserDetailGetController::class);
|
||||
Route::post('/{id}', UserUpdateController::class);
|
||||
Route::post('/{id}/toggle-active', UserToggleActiveController::class);
|
||||
Route::post('/{id}/reset-password', UserResetPasswordController::class);
|
||||
});
|
||||
|
||||
Route::middleware(LvOnlyMiddleware::class)->group(function () {
|
||||
Route::prefix('api/v1/admin/tenants')->group(function () {
|
||||
Route::get('/list', TenantListApiController::class);
|
||||
|
||||
@@ -4,6 +4,7 @@ use App\Domains\Admin\Controllers\AdminDashboardController;
|
||||
use App\Domains\Admin\Controllers\TenantEditPageController;
|
||||
use App\Domains\Admin\Controllers\TenantListPageController;
|
||||
use App\Domains\Admin\Controllers\TenantPageController;
|
||||
use App\Domains\Admin\Controllers\UserListPageController;
|
||||
use App\Middleware\AdminRoleMiddleware;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use App\Middleware\LvOnlyMiddleware;
|
||||
@@ -13,6 +14,7 @@ Route::middleware([IdentifyTenant::class, 'auth', AdminRoleMiddleware::class])->
|
||||
Route::prefix('admin')->group(function () {
|
||||
Route::get('/', AdminDashboardController::class);
|
||||
Route::get('/tenant', TenantPageController::class);
|
||||
Route::get('/users', UserListPageController::class);
|
||||
|
||||
Route::middleware(LvOnlyMiddleware::class)->group(function () {
|
||||
Route::get('/tenants', TenantListPageController::class);
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import { toast } from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
data: Object,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['updated', 'closed'])
|
||||
|
||||
const { request } = useAjax()
|
||||
|
||||
const editing = ref(false)
|
||||
const user = props.data.user
|
||||
const isOwnUser = props.data.isOwnUser
|
||||
const isLvTenant = props.data.isLvTenant
|
||||
const userRoles = props.data.userRoles
|
||||
const localGroups = props.data.localGroups
|
||||
|
||||
const form = ref({
|
||||
firstname: user.firstname ?? '',
|
||||
lastname: user.lastname ?? '',
|
||||
nickname: user.nickname ?? '',
|
||||
email: user.email ?? '',
|
||||
phone: user.phone ?? '',
|
||||
birthday: user.birthday ?? '',
|
||||
membership_id: user.membership_id ?? '',
|
||||
address_1: user.address_1 ?? '',
|
||||
address_2: user.address_2 ?? '',
|
||||
postcode: user.postcode ?? '',
|
||||
city: user.city ?? '',
|
||||
medications: user.medications ?? '',
|
||||
allergies: user.allergies ?? '',
|
||||
intolerances: user.intolerances ?? '',
|
||||
bank_account_owner: user.bank_account_owner ?? '',
|
||||
bank_account_iban: user.bank_account_iban ?? '',
|
||||
user_role_local_group: user.user_role_local_group ?? '',
|
||||
user_role_main: user.user_role_main ?? '',
|
||||
local_group: user.local_group ?? '',
|
||||
})
|
||||
|
||||
async function save() {
|
||||
const response = await request('/api/v1/admin/users/' + user.id, {
|
||||
method: 'POST',
|
||||
body: form.value,
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
editing.value = false
|
||||
emit('updated')
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleActive() {
|
||||
const response = await request('/api/v1/admin/users/' + user.id + '/toggle-active', {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
emit('updated')
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler')
|
||||
}
|
||||
}
|
||||
|
||||
async function resetPassword() {
|
||||
const response = await request('/api/v1/admin/users/' + user.id + '/reset-password', {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler')
|
||||
}
|
||||
}
|
||||
|
||||
function getRoleName(slug) {
|
||||
const role = userRoles.find(r => r.slug === slug)
|
||||
return role ? role.name : slug
|
||||
}
|
||||
|
||||
function getGroupName(slug) {
|
||||
const group = localGroups.find(g => g.slug === slug)
|
||||
return group ? group.name : slug
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="detail-header">
|
||||
<h3>{{ user.firstname }} {{ user.lastname }}</h3>
|
||||
<div class="detail-actions">
|
||||
<button v-if="!editing" class="btn-edit" @click="editing = true">Bearbeiten</button>
|
||||
<button class="btn-reset" @click="resetPassword">Passwort zurücksetzen</button>
|
||||
<button v-if="!isOwnUser" :class="user.active ? 'btn-deactivate' : 'btn-activate'" @click="toggleActive">
|
||||
{{ user.active ? 'Deaktivieren' : 'Aktivieren' }}
|
||||
</button>
|
||||
<button class="btn-close" @click="emit('closed')">Schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!editing">
|
||||
<table class="data-table">
|
||||
<tr><th>Anmeldename:</th><td>{{ user.username }}</td></tr>
|
||||
<tr><th>Vorname:</th><td>{{ form.firstname }}</td></tr>
|
||||
<tr><th>Nachname:</th><td>{{ form.lastname }}</td></tr>
|
||||
<tr><th>Nickname:</th><td>{{ form.nickname }}</td></tr>
|
||||
<tr><th>E-Mail:</th><td>{{ form.email }}</td></tr>
|
||||
<tr><th>Telefon:</th><td>{{ form.phone }}</td></tr>
|
||||
<tr><th>Geburtstag:</th><td>{{ form.birthday }}</td></tr>
|
||||
<tr><th>Mitgliedsnummer:</th><td>{{ form.membership_id }}</td></tr>
|
||||
<tr><th>Adresse 1:</th><td>{{ form.address_1 }}</td></tr>
|
||||
<tr><th>Adresse 2:</th><td>{{ form.address_2 }}</td></tr>
|
||||
<tr><th>PLZ:</th><td>{{ form.postcode }}</td></tr>
|
||||
<tr><th>Ort:</th><td>{{ form.city }}</td></tr>
|
||||
<tr><th>Medikamente:</th><td>{{ form.medications }}</td></tr>
|
||||
<tr><th>Allergien:</th><td>{{ form.allergies }}</td></tr>
|
||||
<tr><th>Unverträglichkeiten:</th><td>{{ form.intolerances }}</td></tr>
|
||||
<tr><th>Kontoinhaber:</th><td>{{ form.bank_account_owner }}</td></tr>
|
||||
<tr><th>IBAN:</th><td>{{ form.bank_account_iban }}</td></tr>
|
||||
<tr><th>Stamm:</th><td>{{ getGroupName(form.local_group) }}</td></tr>
|
||||
<tr><th>Rolle (Stamm):</th><td>{{ getRoleName(form.user_role_local_group) }}</td></tr>
|
||||
<tr><th>Rolle (LV):</th><td>{{ getRoleName(form.user_role_main) }}</td></tr>
|
||||
<tr><th>Status:</th><td>
|
||||
<span :class="user.active ? 'badge-active' : 'badge-inactive'">
|
||||
{{ user.active ? 'Aktiv' : 'Inaktiv' }}
|
||||
</span>
|
||||
</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<table class="data-table">
|
||||
<tr><th>Anmeldename:</th><td>{{ user.username }}</td></tr>
|
||||
<tr><th>Vorname:</th><td><input type="text" v-model="form.firstname" class="form-input" /></td></tr>
|
||||
<tr><th>Nachname:</th><td><input type="text" v-model="form.lastname" class="form-input" /></td></tr>
|
||||
<tr><th>Nickname:</th><td><input type="text" v-model="form.nickname" class="form-input" /></td></tr>
|
||||
<tr><th>E-Mail:</th><td><input type="email" v-model="form.email" class="form-input" /></td></tr>
|
||||
<tr><th>Telefon:</th><td><input type="text" v-model="form.phone" class="form-input" /></td></tr>
|
||||
<tr><th>Geburtstag:</th><td><input type="date" v-model="form.birthday" class="form-input" /></td></tr>
|
||||
<tr><th>Mitgliedsnummer:</th><td><input type="text" v-model="form.membership_id" class="form-input" /></td></tr>
|
||||
<tr><th>Adresse 1:</th><td><input type="text" v-model="form.address_1" class="form-input" /></td></tr>
|
||||
<tr><th>Adresse 2:</th><td><input type="text" v-model="form.address_2" class="form-input" /></td></tr>
|
||||
<tr><th>PLZ:</th><td><input type="text" v-model="form.postcode" class="form-input" /></td></tr>
|
||||
<tr><th>Ort:</th><td><input type="text" v-model="form.city" class="form-input" /></td></tr>
|
||||
<tr><th>Medikamente:</th><td><input type="text" v-model="form.medications" class="form-input" /></td></tr>
|
||||
<tr><th>Allergien:</th><td><input type="text" v-model="form.allergies" class="form-input" /></td></tr>
|
||||
<tr><th>Unverträglichkeiten:</th><td><input type="text" v-model="form.intolerances" class="form-input" /></td></tr>
|
||||
<tr><th>Kontoinhaber:</th><td><input type="text" v-model="form.bank_account_owner" class="form-input" /></td></tr>
|
||||
<tr><th>IBAN:</th><td><input type="text" v-model="form.bank_account_iban" class="form-input" /></td></tr>
|
||||
<tr><th>Stamm:</th><td>
|
||||
<select v-if="isLvTenant" v-model="form.local_group" class="form-input">
|
||||
<option v-for="group in localGroups" :key="group.slug" :value="group.slug">{{ group.name }}</option>
|
||||
</select>
|
||||
<span v-else>{{ getGroupName(form.local_group) }}</span>
|
||||
</td></tr>
|
||||
<tr><th>Rolle (Stamm):</th><td>
|
||||
<select v-model="form.user_role_local_group" class="form-input">
|
||||
<option v-for="role in userRoles" :key="role.slug" :value="role.slug">{{ role.name }}</option>
|
||||
</select>
|
||||
</td></tr>
|
||||
<tr><th>Rolle (LV):</th><td>
|
||||
<select v-if="isLvTenant && !isOwnUser" v-model="form.user_role_main" class="form-input">
|
||||
<option v-for="role in userRoles" :key="role.slug" :value="role.slug">{{ role.name }}</option>
|
||||
</select>
|
||||
<span v-else>{{ getRoleName(form.user_role_main) }}</span>
|
||||
</td></tr>
|
||||
</table>
|
||||
<div class="btn-group">
|
||||
<button class="btn-save" @click="save">Speichern</button>
|
||||
<button class="btn-cancel" @click="editing = false">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.detail-header h3 {
|
||||
margin: 0;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.detail-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
text-align: left;
|
||||
padding: 6px 12px;
|
||||
width: 180px;
|
||||
color: #374151;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.data-table td {
|
||||
padding: 6px 12px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 5px 8px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.badge-active {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #166534;
|
||||
background-color: #dcfce7;
|
||||
border: 1px solid #22c55e;
|
||||
}
|
||||
|
||||
.badge-inactive {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #991b1b;
|
||||
background-color: #fee2e2;
|
||||
border: 1px solid #ef4444;
|
||||
}
|
||||
|
||||
.btn-edit, .btn-save, .btn-cancel, .btn-reset, .btn-deactivate, .btn-activate, .btn-close {
|
||||
padding: 6px 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: #1d4899;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background-color: #163a7a;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background-color: #16a34a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-save:hover {
|
||||
background-color: #15803d;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: #e5e7eb;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background-color: #d1d5db;
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
background-color: #f59e0b;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-reset:hover {
|
||||
background-color: #d97706;
|
||||
}
|
||||
|
||||
.btn-deactivate {
|
||||
background-color: #ef4444;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-deactivate:hover {
|
||||
background-color: #dc2626;
|
||||
}
|
||||
|
||||
.btn-activate {
|
||||
background-color: #16a34a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-activate:hover {
|
||||
background-color: #15803d;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
background-color: #6b7280;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-close:hover {
|
||||
background-color: #4b5563;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,167 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import AdminAppLayout from "../../../../resources/js/layouts/AdminAppLayout.vue";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import FullScreenModal from "../../../Views/Components/FullScreenModal.vue";
|
||||
import UserDetail from "./Partials/UserDetail.vue";
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
|
||||
const { request } = useAjax()
|
||||
|
||||
const props = defineProps({
|
||||
isLvTenant: Boolean,
|
||||
})
|
||||
|
||||
const users = ref([])
|
||||
const selectedUserId = ref(null)
|
||||
const userDetail = ref(null)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadUsers()
|
||||
})
|
||||
|
||||
async function loadUsers() {
|
||||
const response = await fetch('/api/v1/admin/users/list', {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
const data = await response.json()
|
||||
users.value = data.users
|
||||
}
|
||||
|
||||
async function selectUser(userId) {
|
||||
if (selectedUserId.value === userId) {
|
||||
selectedUserId.value = null
|
||||
userDetail.value = null
|
||||
return
|
||||
}
|
||||
|
||||
selectedUserId.value = userId
|
||||
const response = await fetch('/api/v1/admin/users/' + userId, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
userDetail.value = await response.json()
|
||||
}
|
||||
|
||||
function onUserUpdated() {
|
||||
loadUsers()
|
||||
if (selectedUserId.value) {
|
||||
selectUser(selectedUserId.value)
|
||||
}
|
||||
}
|
||||
|
||||
function onDetailClosed() {
|
||||
selectedUserId.value = null
|
||||
userDetail.value = null
|
||||
loadUsers()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminAppLayout title="Benutzer*innen">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<table class="user-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nachname</th>
|
||||
<th>Vorname</th>
|
||||
<th>Pfadiname</th>
|
||||
<th v-if="props.isLvTenant">Stamm</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in users"
|
||||
:key="user.id"
|
||||
:class="['user-row', { 'user-row-selected': selectedUserId === user.id }]"
|
||||
@click="selectUser(user.id)">
|
||||
<td>{{ user.lastname }}</td>
|
||||
<td>{{ user.firstname }}</td>
|
||||
<td>{{ user.nickname }}</td>
|
||||
<td v-if="props.isLvTenant">{{ user.local_group_name }}</td>
|
||||
<td>
|
||||
<span :class="user.active ? 'badge-active' : 'badge-inactive'">
|
||||
{{ user.active ? 'Aktiv' : 'Inaktiv' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</shadowed-box>
|
||||
|
||||
<FullScreenModal :show="userDetail !== null" @close="onDetailClosed">
|
||||
<UserDetail
|
||||
v-if="userDetail"
|
||||
:data="userDetail"
|
||||
@updated="onUserUpdated"
|
||||
@closed="onDetailClosed"
|
||||
/>
|
||||
</FullScreenModal>
|
||||
</AdminAppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.user-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.user-table thead th {
|
||||
text-align: left;
|
||||
padding: 10px 16px;
|
||||
background-color: #f9fafb;
|
||||
color: #374151;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #d1d5db;
|
||||
}
|
||||
|
||||
.user-table tbody td {
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.user-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.user-row:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.user-row-selected {
|
||||
background-color: #eff6ff;
|
||||
}
|
||||
|
||||
.badge-active {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #166534;
|
||||
background-color: #dcfce7;
|
||||
border: 1px solid #22c55e;
|
||||
}
|
||||
|
||||
.badge-inactive {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #991b1b;
|
||||
background-color: #fee2e2;
|
||||
border: 1px solid #ef4444;
|
||||
}
|
||||
</style>
|
||||
@@ -109,6 +109,9 @@ const props = defineProps({
|
||||
<li v-if="globalProps.tenant.slug === 'lv'">
|
||||
<a href="/admin/tenants" @click="closeSidebar">Stämme</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/users" @click="closeSidebar">Benutzer*innen</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user