feat(frontend): scaffold Nuxt 3 CSR SPA with full auth + client/history UI
- Nuxt 3 CSR (ssr:false), @nuxtjs/tailwindcss, dev proxy to backend
- useAuth composable: CSRF cookie, XSRF token, login/logout/fetchUser
- auth middleware: redirects to /login if no session
- Login page, client list with search + create modal
- Client detail with editable name, dynamic notes (JSON), history feed
- History form with type select, inline edit/delete per entry
- Print layout at /clients/:id/print (calls window.print on mount)
- TypeScript interfaces: User, Client, HistoryEntry, PaginatedResponse
- German UI labels throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 10:33:39 +00:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
definePageMeta({ middleware: 'auth' })
|
|
|
|
|
|
|
|
|
|
|
|
const { clients, search, fetchClients } = useClient()
|
|
|
|
|
|
const q = ref('')
|
|
|
|
|
|
let debounceTimer: ReturnType<typeof setTimeout>
|
|
|
|
|
|
|
|
|
|
|
|
await fetchClients()
|
|
|
|
|
|
|
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
|
|
|
|
type SortKey = 'name' | 'number' | 'recent' | 'updates'
|
|
|
|
|
|
|
|
|
|
|
|
const sortOptions: { key: SortKey; label: string }[] = [
|
|
|
|
|
|
{ key: 'name', label: 'Name (A–Z)' },
|
|
|
|
|
|
{ key: 'number', label: 'Mandantennummer' },
|
|
|
|
|
|
{ key: 'recent', label: 'Letzte Änderung' },
|
|
|
|
|
|
{ key: 'updates', label: 'Anzahl Einträge' },
|
|
|
|
|
|
]
|
|
|
|
|
|
const sortKey = ref<SortKey>('name')
|
|
|
|
|
|
|
|
|
|
|
|
// Real timestamps are large positives, so clients without history (0) always
|
|
|
|
|
|
// sort below those with history, and ties stay stable (no NaN comparisons).
|
|
|
|
|
|
const lastChangeTs = (c: (typeof clients.value)[number]): number =>
|
|
|
|
|
|
c.last_history_at ? new Date(c.last_history_at).getTime() : 0
|
|
|
|
|
|
|
|
|
|
|
|
const sortedClients = computed(() => {
|
|
|
|
|
|
const list = [...clients.value]
|
|
|
|
|
|
switch (sortKey.value) {
|
|
|
|
|
|
case 'number':
|
|
|
|
|
|
return list.sort((a, b) =>
|
|
|
|
|
|
a.client_number.localeCompare(b.client_number, 'de', { numeric: true }),
|
|
|
|
|
|
)
|
|
|
|
|
|
case 'recent':
|
|
|
|
|
|
// Most recently changed first; clients with no history sink to the bottom.
|
|
|
|
|
|
return list.sort((a, b) => lastChangeTs(b) - lastChangeTs(a))
|
|
|
|
|
|
case 'updates':
|
|
|
|
|
|
return list.sort((a, b) => (b.history_count ?? 0) - (a.history_count ?? 0))
|
|
|
|
|
|
case 'name':
|
|
|
|
|
|
default:
|
|
|
|
|
|
return list.sort((a, b) => a.name.localeCompare(b.name, 'de'))
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
feat(frontend): scaffold Nuxt 3 CSR SPA with full auth + client/history UI
- Nuxt 3 CSR (ssr:false), @nuxtjs/tailwindcss, dev proxy to backend
- useAuth composable: CSRF cookie, XSRF token, login/logout/fetchUser
- auth middleware: redirects to /login if no session
- Login page, client list with search + create modal
- Client detail with editable name, dynamic notes (JSON), history feed
- History form with type select, inline edit/delete per entry
- Print layout at /clients/:id/print (calls window.print on mount)
- TypeScript interfaces: User, Client, HistoryEntry, PaginatedResponse
- German UI labels throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 10:33:39 +00:00
|
|
|
|
const onSearch = () => {
|
|
|
|
|
|
clearTimeout(debounceTimer)
|
|
|
|
|
|
debounceTimer = setTimeout(() => search(q.value), 250)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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 { user } = useAuth()
|
|
|
|
|
|
const isAdmin = computed(() => user.value?.role === 'admin')
|
|
|
|
|
|
|
feat(frontend): scaffold Nuxt 3 CSR SPA with full auth + client/history UI
- Nuxt 3 CSR (ssr:false), @nuxtjs/tailwindcss, dev proxy to backend
- useAuth composable: CSRF cookie, XSRF token, login/logout/fetchUser
- auth middleware: redirects to /login if no session
- Login page, client list with search + create modal
- Client detail with editable name, dynamic notes (JSON), history feed
- History form with type select, inline edit/delete per entry
- Print layout at /clients/:id/print (calls window.print on mount)
- TypeScript interfaces: User, Client, HistoryEntry, PaginatedResponse
- German UI labels throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 10:33:39 +00:00
|
|
|
|
const showCreate = ref(false)
|
|
|
|
|
|
const newNumber = ref('')
|
|
|
|
|
|
const newName = ref('')
|
|
|
|
|
|
const creating = ref(false)
|
|
|
|
|
|
const createError = ref('')
|
|
|
|
|
|
const { createClient } = useClient()
|
|
|
|
|
|
|
|
|
|
|
|
const submitCreate = async () => {
|
|
|
|
|
|
creating.value = true
|
|
|
|
|
|
createError.value = ''
|
|
|
|
|
|
try {
|
|
|
|
|
|
const client = await createClient({ client_number: newNumber.value, name: newName.value })
|
|
|
|
|
|
showCreate.value = false
|
|
|
|
|
|
newNumber.value = ''
|
|
|
|
|
|
newName.value = ''
|
|
|
|
|
|
await navigateTo(`/clients/${client.id}`)
|
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
createError.value = e?.data?.message ?? 'Fehler beim Erstellen.'
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
creating.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="min-h-screen bg-gray-50">
|
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
|
|
|
|
<header class="grid grid-cols-3 items-center bg-white border-b border-gray-200 px-6 py-3">
|
|
|
|
|
|
<h1 class="text-base font-semibold text-gray-800">Markus Grote CRM</h1>
|
|
|
|
|
|
|
feat(frontend): scaffold Nuxt 3 CSR SPA with full auth + client/history UI
- Nuxt 3 CSR (ssr:false), @nuxtjs/tailwindcss, dev proxy to backend
- useAuth composable: CSRF cookie, XSRF token, login/logout/fetchUser
- auth middleware: redirects to /login if no session
- Login page, client list with search + create modal
- Client detail with editable name, dynamic notes (JSON), history feed
- History form with type select, inline edit/delete per entry
- Print layout at /clients/:id/print (calls window.print on mount)
- TypeScript interfaces: User, Client, HistoryEntry, PaginatedResponse
- German UI labels throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 10:33:39 +00:00
|
|
|
|
<input
|
|
|
|
|
|
v-model="q"
|
|
|
|
|
|
@input="onSearch"
|
|
|
|
|
|
type="search"
|
|
|
|
|
|
placeholder="Mandant suchen (Name oder Nr.)…"
|
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
|
|
|
|
class="w-full max-w-md justify-self-center border border-gray-300 px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
|
feat(frontend): scaffold Nuxt 3 CSR SPA with full auth + client/history UI
- Nuxt 3 CSR (ssr:false), @nuxtjs/tailwindcss, dev proxy to backend
- useAuth composable: CSRF cookie, XSRF token, login/logout/fetchUser
- auth middleware: redirects to /login if no session
- Login page, client list with search + create modal
- Client detail with editable name, dynamic notes (JSON), history feed
- History form with type select, inline edit/delete per entry
- Print layout at /clients/:id/print (calls window.print on mount)
- TypeScript interfaces: User, Client, HistoryEntry, PaginatedResponse
- German UI labels throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 10:33:39 +00:00
|
|
|
|
/>
|
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 class="justify-self-end flex items-center gap-3">
|
|
|
|
|
|
<!-- Settings: hover-/focus-revealed menu -->
|
|
|
|
|
|
<div v-if="isAdmin" class="relative group/settings">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
aria-label="Einstellungen"
|
|
|
|
|
|
title="Einstellungen"
|
|
|
|
|
|
class="p-1.5 text-gray-500 hover:text-gray-700"
|
|
|
|
|
|
>
|
|
|
|
|
|
<IconSettings />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="absolute right-0 top-full pt-1 z-20 hidden group-hover/settings:block group-focus-within/settings:block"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="bg-white border border-gray-200 shadow-md py-1 min-w-[150px]">
|
|
|
|
|
|
<NuxtLink to="/settings/accounts" class="block px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50">
|
|
|
|
|
|
Benutzer
|
|
|
|
|
|
</NuxtLink>
|
|
|
|
|
|
<NuxtLink to="/settings/fields" class="block px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50">
|
|
|
|
|
|
Felder
|
|
|
|
|
|
</NuxtLink>
|
|
|
|
|
|
<NuxtLink to="/settings/history-types" class="block px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50">
|
|
|
|
|
|
Verlaufstypen
|
|
|
|
|
|
</NuxtLink>
|
|
|
|
|
|
<NuxtLink to="/settings/import" class="block px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50">
|
|
|
|
|
|
Mandanten-Import
|
|
|
|
|
|
</NuxtLink>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<LogoutButton />
|
|
|
|
|
|
</div>
|
feat(frontend): scaffold Nuxt 3 CSR SPA with full auth + client/history UI
- Nuxt 3 CSR (ssr:false), @nuxtjs/tailwindcss, dev proxy to backend
- useAuth composable: CSRF cookie, XSRF token, login/logout/fetchUser
- auth middleware: redirects to /login if no session
- Login page, client list with search + create modal
- Client detail with editable name, dynamic notes (JSON), history feed
- History form with type select, inline edit/delete per entry
- Print layout at /clients/:id/print (calls window.print on mount)
- TypeScript interfaces: User, Client, HistoryEntry, PaginatedResponse
- German UI labels throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 10:33:39 +00:00
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<main class="max-w-4xl mx-auto py-4 px-6 space-y-1">
|
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 class="flex items-center justify-between pb-2">
|
|
|
|
|
|
<span class="text-xs text-gray-500">{{ clients.length }} Mandanten</span>
|
|
|
|
|
|
<label class="flex items-center gap-2 text-xs text-gray-500">
|
|
|
|
|
|
Sortieren
|
|
|
|
|
|
<select
|
|
|
|
|
|
v-model="sortKey"
|
|
|
|
|
|
class="border border-gray-300 bg-white px-2 py-1 text-xs text-gray-700 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option v-for="opt in sortOptions" :key="opt.key" :value="opt.key">
|
|
|
|
|
|
{{ opt.label }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
feat(frontend): scaffold Nuxt 3 CSR SPA with full auth + client/history UI
- Nuxt 3 CSR (ssr:false), @nuxtjs/tailwindcss, dev proxy to backend
- useAuth composable: CSRF cookie, XSRF token, login/logout/fetchUser
- auth middleware: redirects to /login if no session
- Login page, client list with search + create modal
- Client detail with editable name, dynamic notes (JSON), history feed
- History form with type select, inline edit/delete per entry
- Print layout at /clients/:id/print (calls window.print on mount)
- TypeScript interfaces: User, Client, HistoryEntry, PaginatedResponse
- German UI labels throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 10:33:39 +00:00
|
|
|
|
<p v-if="clients.length === 0" class="text-sm text-gray-500 py-8 text-center">
|
|
|
|
|
|
Keine Mandanten gefunden.
|
|
|
|
|
|
</p>
|
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
|
|
|
|
<ClientCard v-for="client in sortedClients" :key="client.id" :client="client" />
|
feat(frontend): scaffold Nuxt 3 CSR SPA with full auth + client/history UI
- Nuxt 3 CSR (ssr:false), @nuxtjs/tailwindcss, dev proxy to backend
- useAuth composable: CSRF cookie, XSRF token, login/logout/fetchUser
- auth middleware: redirects to /login if no session
- Login page, client list with search + create modal
- Client detail with editable name, dynamic notes (JSON), history feed
- History form with type select, inline edit/delete per entry
- Print layout at /clients/:id/print (calls window.print on mount)
- TypeScript interfaces: User, Client, HistoryEntry, PaginatedResponse
- German UI labels throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 10:33:39 +00:00
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Create modal -->
|
|
|
|
|
|
<div v-if="showCreate" class="fixed inset-0 bg-black/30 flex items-center justify-center z-50">
|
|
|
|
|
|
<div class="bg-white shadow-lg p-6 w-full max-w-sm">
|
|
|
|
|
|
<h2 class="text-base font-semibold mb-4">Neuer Mandant</h2>
|
|
|
|
|
|
<form @submit.prevent="submitCreate" class="space-y-3">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Mandantennummer</label>
|
|
|
|
|
|
<input v-model="newNumber" required class="mt-1 block w-full border border-gray-300 px-3 py-2 text-sm" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Name</label>
|
|
|
|
|
|
<input v-model="newName" required class="mt-1 block w-full border border-gray-300 px-3 py-2 text-sm" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p v-if="createError" class="text-sm text-red-600">{{ createError }}</p>
|
|
|
|
|
|
<div class="flex gap-2 justify-end">
|
|
|
|
|
|
<button type="button" @click="showCreate = false" class="px-3 py-1.5 text-sm border border-gray-300">
|
|
|
|
|
|
Abbrechen
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button type="submit" :disabled="creating" class="px-3 py-1.5 text-sm bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50">
|
|
|
|
|
|
Anlegen
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|