feat: remove Filament, add user account management
- Remove Filament /admin panel completely (AdminPanelProvider, UserResource, routes)
- Update composer.json to remove filament/filament dependency
- Add User model soft deletes with SoftDeletes trait + migration
- Strengthen UserPolicy::delete to prevent deleting last remaining admin
- Build out User module following pattern: CreateUserAction, UpdateUserAction, GetUsersAction,
UserResource, UserController, Create/UpdateUserRequest, UserData DTO
- Add /api/users routes (index, store, update, destroy), all admin-gated via policy
- Create frontend Settings > Accounts page (settings/accounts.vue) for admin user management
- Add useUsers composable mirroring useClient pattern
- Add "Benutzer" link to settings dropdown in clients/index.vue
- All tests pass; User soft-delete and last-admin protection verified
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-13 06:05:40 +00:00
|
|
|
<script setup lang="ts">
|
|
|
|
|
import type { User } from '~/types'
|
|
|
|
|
|
|
|
|
|
definePageMeta({ middleware: ['auth', 'admin'] })
|
|
|
|
|
|
|
|
|
|
const { users, fetchUsers, createUser, updateUser, deleteUser } = useUsers()
|
|
|
|
|
|
|
|
|
|
await fetchUsers()
|
|
|
|
|
|
|
|
|
|
const editingId = ref<number | null>(null)
|
|
|
|
|
const form = ref<{
|
|
|
|
|
name: string
|
|
|
|
|
email: string
|
|
|
|
|
password: string
|
|
|
|
|
password_confirmation: string
|
|
|
|
|
role: 'admin' | 'staff'
|
|
|
|
|
}>({
|
|
|
|
|
name: '',
|
|
|
|
|
email: '',
|
|
|
|
|
password: '',
|
|
|
|
|
password_confirmation: '',
|
|
|
|
|
role: 'staff',
|
|
|
|
|
})
|
|
|
|
|
const error = ref('')
|
feat(frontend): commit uncommitted settings, custom fields, and import work
This local working tree had accumulated a substantial amount of
finished-but-never-committed frontend work: custom client fields
(replacing the old static ClientFields with a dynamic
ClientCustomFieldCard + useCustomFields), CSV import
(useImport + settings/import page), history type management
(useHistoryTypes + settings/history-types page), an admin-only route
middleware, several new icon components, a shared useApiFetch
composable, and a client detail page restructure ([id].vue ->
[id]/index.vue). None of it had ever been pushed, so production was
running a stale build that was already linking to routes/components
that didn't exist server-side (missing IconSettings, 404s on
/settings/import, etc). This commit brings the repo in line with
local state.
2026-07-18 09:27:19 +00:00
|
|
|
const showPassword = ref(false)
|
feat: remove Filament, add user account management
- Remove Filament /admin panel completely (AdminPanelProvider, UserResource, routes)
- Update composer.json to remove filament/filament dependency
- Add User model soft deletes with SoftDeletes trait + migration
- Strengthen UserPolicy::delete to prevent deleting last remaining admin
- Build out User module following pattern: CreateUserAction, UpdateUserAction, GetUsersAction,
UserResource, UserController, Create/UpdateUserRequest, UserData DTO
- Add /api/users routes (index, store, update, destroy), all admin-gated via policy
- Create frontend Settings > Accounts page (settings/accounts.vue) for admin user management
- Add useUsers composable mirroring useClient pattern
- Add "Benutzer" link to settings dropdown in clients/index.vue
- All tests pass; User soft-delete and last-admin protection verified
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-13 06:05:40 +00:00
|
|
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
editingId.value = null
|
|
|
|
|
form.value = {
|
|
|
|
|
name: '',
|
|
|
|
|
email: '',
|
|
|
|
|
password: '',
|
|
|
|
|
password_confirmation: '',
|
|
|
|
|
role: 'staff',
|
|
|
|
|
}
|
|
|
|
|
error.value = ''
|
feat(frontend): commit uncommitted settings, custom fields, and import work
This local working tree had accumulated a substantial amount of
finished-but-never-committed frontend work: custom client fields
(replacing the old static ClientFields with a dynamic
ClientCustomFieldCard + useCustomFields), CSV import
(useImport + settings/import page), history type management
(useHistoryTypes + settings/history-types page), an admin-only route
middleware, several new icon components, a shared useApiFetch
composable, and a client detail page restructure ([id].vue ->
[id]/index.vue). None of it had ever been pushed, so production was
running a stale build that was already linking to routes/components
that didn't exist server-side (missing IconSettings, 404s on
/settings/import, etc). This commit brings the repo in line with
local state.
2026-07-18 09:27:19 +00:00
|
|
|
showPassword.value = false
|
feat: remove Filament, add user account management
- Remove Filament /admin panel completely (AdminPanelProvider, UserResource, routes)
- Update composer.json to remove filament/filament dependency
- Add User model soft deletes with SoftDeletes trait + migration
- Strengthen UserPolicy::delete to prevent deleting last remaining admin
- Build out User module following pattern: CreateUserAction, UpdateUserAction, GetUsersAction,
UserResource, UserController, Create/UpdateUserRequest, UserData DTO
- Add /api/users routes (index, store, update, destroy), all admin-gated via policy
- Create frontend Settings > Accounts page (settings/accounts.vue) for admin user management
- Add useUsers composable mirroring useClient pattern
- Add "Benutzer" link to settings dropdown in clients/index.vue
- All tests pass; User soft-delete and last-admin protection verified
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-13 06:05:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const startEdit = (user: User) => {
|
|
|
|
|
editingId.value = user.id
|
|
|
|
|
error.value = ''
|
feat(frontend): commit uncommitted settings, custom fields, and import work
This local working tree had accumulated a substantial amount of
finished-but-never-committed frontend work: custom client fields
(replacing the old static ClientFields with a dynamic
ClientCustomFieldCard + useCustomFields), CSV import
(useImport + settings/import page), history type management
(useHistoryTypes + settings/history-types page), an admin-only route
middleware, several new icon components, a shared useApiFetch
composable, and a client detail page restructure ([id].vue ->
[id]/index.vue). None of it had ever been pushed, so production was
running a stale build that was already linking to routes/components
that didn't exist server-side (missing IconSettings, 404s on
/settings/import, etc). This commit brings the repo in line with
local state.
2026-07-18 09:27:19 +00:00
|
|
|
showPassword.value = false
|
feat: remove Filament, add user account management
- Remove Filament /admin panel completely (AdminPanelProvider, UserResource, routes)
- Update composer.json to remove filament/filament dependency
- Add User model soft deletes with SoftDeletes trait + migration
- Strengthen UserPolicy::delete to prevent deleting last remaining admin
- Build out User module following pattern: CreateUserAction, UpdateUserAction, GetUsersAction,
UserResource, UserController, Create/UpdateUserRequest, UserData DTO
- Add /api/users routes (index, store, update, destroy), all admin-gated via policy
- Create frontend Settings > Accounts page (settings/accounts.vue) for admin user management
- Add useUsers composable mirroring useClient pattern
- Add "Benutzer" link to settings dropdown in clients/index.vue
- All tests pass; User soft-delete and last-admin protection verified
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-13 06:05:40 +00:00
|
|
|
form.value = {
|
|
|
|
|
name: user.name,
|
|
|
|
|
email: user.email,
|
|
|
|
|
password: '',
|
|
|
|
|
password_confirmation: '',
|
|
|
|
|
role: user.role,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const submit = async () => {
|
|
|
|
|
error.value = ''
|
|
|
|
|
|
|
|
|
|
if (!form.value.name.trim()) {
|
|
|
|
|
error.value = 'Bitte einen Namen angeben.'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!form.value.email.trim()) {
|
|
|
|
|
error.value = 'Bitte eine E-Mail angeben.'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if (editingId.value) {
|
|
|
|
|
const payload: any = {
|
|
|
|
|
name: form.value.name.trim(),
|
|
|
|
|
email: form.value.email.trim(),
|
|
|
|
|
role: form.value.role,
|
|
|
|
|
}
|
|
|
|
|
if (form.value.password) {
|
|
|
|
|
payload.password = form.value.password
|
|
|
|
|
payload.password_confirmation = form.value.password_confirmation
|
|
|
|
|
}
|
|
|
|
|
await updateUser(editingId.value, payload)
|
|
|
|
|
} else {
|
|
|
|
|
if (!form.value.password) {
|
|
|
|
|
error.value = 'Bitte ein Passwort angeben.'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
await createUser({
|
|
|
|
|
name: form.value.name.trim(),
|
|
|
|
|
email: form.value.email.trim(),
|
|
|
|
|
password: form.value.password,
|
|
|
|
|
password_confirmation: form.value.password_confirmation,
|
|
|
|
|
role: form.value.role,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
resetForm()
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
error.value = e.data?.message || 'Fehler beim Speichern.'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const confirmDelete = (user: User) => {
|
|
|
|
|
if (confirm(`Benutzer "${user.name}" wirklich löschen?`)) {
|
|
|
|
|
deleteUser(user.id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const roleLabels = {
|
|
|
|
|
admin: 'Admin',
|
|
|
|
|
staff: 'Mitarbeiter',
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div class="min-h-screen bg-gray-50">
|
|
|
|
|
<header class="bg-white border-b border-gray-200 px-6 py-3 flex items-center gap-4">
|
|
|
|
|
<NuxtLink
|
|
|
|
|
to="/clients"
|
|
|
|
|
title="Alle Mandanten"
|
|
|
|
|
aria-label="Alle Mandanten"
|
|
|
|
|
class="text-gray-500 hover:text-gray-700"
|
|
|
|
|
>
|
|
|
|
|
<IconArrowLeft />
|
|
|
|
|
</NuxtLink>
|
|
|
|
|
<h1 class="text-base font-semibold text-gray-800">Benutzer verwalten</h1>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<div class="max-w-3xl mx-auto py-6 px-6 space-y-6">
|
|
|
|
|
<!-- Existing users -->
|
|
|
|
|
<section class="bg-white border border-gray-200">
|
|
|
|
|
<table class="w-full text-sm">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr class="border-b border-gray-200 text-left text-xs text-gray-500">
|
|
|
|
|
<th class="px-4 py-2 font-medium">Name</th>
|
|
|
|
|
<th class="px-4 py-2 font-medium">E-Mail</th>
|
|
|
|
|
<th class="px-4 py-2 font-medium">Rolle</th>
|
|
|
|
|
<th class="px-4 py-2 font-medium text-right">Aktionen</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<tr v-for="user in users" :key="user.id" class="border-b border-gray-100">
|
|
|
|
|
<td class="px-4 py-2 text-gray-800">{{ user.name }}</td>
|
|
|
|
|
<td class="px-4 py-2 text-gray-600">{{ user.email }}</td>
|
|
|
|
|
<td class="px-4 py-2 text-gray-600">{{ roleLabels[user.role] }}</td>
|
|
|
|
|
<td class="px-4 py-2 text-right space-x-2">
|
|
|
|
|
<button
|
|
|
|
|
@click="startEdit(user)"
|
|
|
|
|
title="Bearbeiten"
|
|
|
|
|
class="inline-flex items-center text-gray-400 hover:text-blue-600"
|
|
|
|
|
>
|
|
|
|
|
<IconEdit />
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
@click="confirmDelete(user)"
|
|
|
|
|
title="Löschen"
|
|
|
|
|
class="inline-flex items-center text-gray-400 hover:text-red-600"
|
|
|
|
|
>
|
|
|
|
|
<IconTrash />
|
|
|
|
|
</button>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- Create/Edit form -->
|
|
|
|
|
<section class="bg-white border border-gray-200 p-6 space-y-4">
|
|
|
|
|
<h2 class="text-sm font-semibold text-gray-800">
|
|
|
|
|
{{ editingId ? 'Benutzer bearbeiten' : 'Neuer Benutzer' }}
|
|
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">Name</label>
|
|
|
|
|
<input
|
|
|
|
|
v-model="form.name"
|
|
|
|
|
type="text"
|
|
|
|
|
class="w-full border border-gray-300 px-3 py-2 text-sm"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">E-Mail</label>
|
|
|
|
|
<input
|
|
|
|
|
v-model="form.email"
|
|
|
|
|
type="email"
|
feat(frontend): commit uncommitted settings, custom fields, and import work
This local working tree had accumulated a substantial amount of
finished-but-never-committed frontend work: custom client fields
(replacing the old static ClientFields with a dynamic
ClientCustomFieldCard + useCustomFields), CSV import
(useImport + settings/import page), history type management
(useHistoryTypes + settings/history-types page), an admin-only route
middleware, several new icon components, a shared useApiFetch
composable, and a client detail page restructure ([id].vue ->
[id]/index.vue). None of it had ever been pushed, so production was
running a stale build that was already linking to routes/components
that didn't exist server-side (missing IconSettings, 404s on
/settings/import, etc). This commit brings the repo in line with
local state.
2026-07-18 09:27:19 +00:00
|
|
|
class="w-full border border-gray-300 px-3 py-2 text-sm lowercase"
|
|
|
|
|
@input="form.email = form.email.toLowerCase()"
|
feat: remove Filament, add user account management
- Remove Filament /admin panel completely (AdminPanelProvider, UserResource, routes)
- Update composer.json to remove filament/filament dependency
- Add User model soft deletes with SoftDeletes trait + migration
- Strengthen UserPolicy::delete to prevent deleting last remaining admin
- Build out User module following pattern: CreateUserAction, UpdateUserAction, GetUsersAction,
UserResource, UserController, Create/UpdateUserRequest, UserData DTO
- Add /api/users routes (index, store, update, destroy), all admin-gated via policy
- Create frontend Settings > Accounts page (settings/accounts.vue) for admin user management
- Add useUsers composable mirroring useClient pattern
- Add "Benutzer" link to settings dropdown in clients/index.vue
- All tests pass; User soft-delete and last-admin protection verified
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-13 06:05:40 +00:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">
|
|
|
|
|
{{ editingId ? 'Passwort (leer = nicht ändern)' : 'Passwort' }}
|
|
|
|
|
</label>
|
feat(frontend): commit uncommitted settings, custom fields, and import work
This local working tree had accumulated a substantial amount of
finished-but-never-committed frontend work: custom client fields
(replacing the old static ClientFields with a dynamic
ClientCustomFieldCard + useCustomFields), CSV import
(useImport + settings/import page), history type management
(useHistoryTypes + settings/history-types page), an admin-only route
middleware, several new icon components, a shared useApiFetch
composable, and a client detail page restructure ([id].vue ->
[id]/index.vue). None of it had ever been pushed, so production was
running a stale build that was already linking to routes/components
that didn't exist server-side (missing IconSettings, 404s on
/settings/import, etc). This commit brings the repo in line with
local state.
2026-07-18 09:27:19 +00:00
|
|
|
<div class="relative">
|
|
|
|
|
<input
|
|
|
|
|
v-model="form.password"
|
|
|
|
|
:type="showPassword ? 'text' : 'password'"
|
|
|
|
|
class="w-full border border-gray-300 px-3 py-2 pr-10 text-sm"
|
|
|
|
|
/>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
@click="showPassword = !showPassword"
|
|
|
|
|
:title="showPassword ? 'Passwort verbergen' : 'Passwort anzeigen'"
|
|
|
|
|
:aria-label="showPassword ? 'Passwort verbergen' : 'Passwort anzeigen'"
|
|
|
|
|
class="absolute inset-y-0 right-0 flex items-center px-2.5 text-gray-400 hover:text-gray-600"
|
|
|
|
|
>
|
|
|
|
|
<IconEyeOff v-if="showPassword" class="w-4 h-4" />
|
|
|
|
|
<IconEye v-else class="w-4 h-4" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
feat: remove Filament, add user account management
- Remove Filament /admin panel completely (AdminPanelProvider, UserResource, routes)
- Update composer.json to remove filament/filament dependency
- Add User model soft deletes with SoftDeletes trait + migration
- Strengthen UserPolicy::delete to prevent deleting last remaining admin
- Build out User module following pattern: CreateUserAction, UpdateUserAction, GetUsersAction,
UserResource, UserController, Create/UpdateUserRequest, UserData DTO
- Add /api/users routes (index, store, update, destroy), all admin-gated via policy
- Create frontend Settings > Accounts page (settings/accounts.vue) for admin user management
- Add useUsers composable mirroring useClient pattern
- Add "Benutzer" link to settings dropdown in clients/index.vue
- All tests pass; User soft-delete and last-admin protection verified
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-13 06:05:40 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-if="form.password">
|
|
|
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">Passwort wiederholen</label>
|
feat(frontend): commit uncommitted settings, custom fields, and import work
This local working tree had accumulated a substantial amount of
finished-but-never-committed frontend work: custom client fields
(replacing the old static ClientFields with a dynamic
ClientCustomFieldCard + useCustomFields), CSV import
(useImport + settings/import page), history type management
(useHistoryTypes + settings/history-types page), an admin-only route
middleware, several new icon components, a shared useApiFetch
composable, and a client detail page restructure ([id].vue ->
[id]/index.vue). None of it had ever been pushed, so production was
running a stale build that was already linking to routes/components
that didn't exist server-side (missing IconSettings, 404s on
/settings/import, etc). This commit brings the repo in line with
local state.
2026-07-18 09:27:19 +00:00
|
|
|
<div class="relative">
|
|
|
|
|
<input
|
|
|
|
|
v-model="form.password_confirmation"
|
|
|
|
|
:type="showPassword ? 'text' : 'password'"
|
|
|
|
|
class="w-full border border-gray-300 px-3 py-2 pr-10 text-sm"
|
|
|
|
|
/>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
@click="showPassword = !showPassword"
|
|
|
|
|
:title="showPassword ? 'Passwort verbergen' : 'Passwort anzeigen'"
|
|
|
|
|
:aria-label="showPassword ? 'Passwort verbergen' : 'Passwort anzeigen'"
|
|
|
|
|
class="absolute inset-y-0 right-0 flex items-center px-2.5 text-gray-400 hover:text-gray-600"
|
|
|
|
|
>
|
|
|
|
|
<IconEyeOff v-if="showPassword" class="w-4 h-4" />
|
|
|
|
|
<IconEye v-else class="w-4 h-4" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
feat: remove Filament, add user account management
- Remove Filament /admin panel completely (AdminPanelProvider, UserResource, routes)
- Update composer.json to remove filament/filament dependency
- Add User model soft deletes with SoftDeletes trait + migration
- Strengthen UserPolicy::delete to prevent deleting last remaining admin
- Build out User module following pattern: CreateUserAction, UpdateUserAction, GetUsersAction,
UserResource, UserController, Create/UpdateUserRequest, UserData DTO
- Add /api/users routes (index, store, update, destroy), all admin-gated via policy
- Create frontend Settings > Accounts page (settings/accounts.vue) for admin user management
- Add useUsers composable mirroring useClient pattern
- Add "Benutzer" link to settings dropdown in clients/index.vue
- All tests pass; User soft-delete and last-admin protection verified
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-13 06:05:40 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">Rolle</label>
|
|
|
|
|
<select v-model="form.role" class="w-full border border-gray-300 px-3 py-2 text-sm">
|
|
|
|
|
<option value="staff">Mitarbeiter</option>
|
|
|
|
|
<option value="admin">Admin</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<p v-if="error" class="text-sm text-red-600">{{ error }}</p>
|
|
|
|
|
|
|
|
|
|
<div class="flex gap-2">
|
|
|
|
|
<button
|
|
|
|
|
@click="submit"
|
|
|
|
|
class="bg-blue-600 text-white px-4 py-1.5 text-sm hover:bg-blue-700"
|
|
|
|
|
>
|
|
|
|
|
{{ editingId ? 'Änderungen speichern' : 'Benutzer anlegen' }}
|
|
|
|
|
</button>
|
|
|
|
|
<button v-if="editingId" @click="resetForm" class="text-sm text-gray-500 hover:underline px-2">
|
|
|
|
|
Abbrechen
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|