Views: - Login/Registrierung - Dashboard mit Stats - Aufträge (Liste + Detail) - Mitarbeiterverwaltung - Verfügbarkeitskalender - Stundenzettel - Einstellungen - Module (Dev-Panel) Features: - Vue 3 + Composition API - TailwindCSS mit Dark Mode - Pinia State Management - JWT Auth mit Refresh - Responsive Design - Rollen-basierte Navigation
173 lines
5.6 KiB
Vue
173 lines
5.6 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { api } from '@/api'
|
|
|
|
const authStore = useAuthStore()
|
|
|
|
interface User {
|
|
id: string
|
|
email: string
|
|
role: string
|
|
first_name: string
|
|
last_name: string
|
|
phone?: string
|
|
active: boolean
|
|
}
|
|
|
|
const users = ref<User[]>([])
|
|
const loading = ref(true)
|
|
const showCreateModal = ref(false)
|
|
const newUser = ref({
|
|
email: '',
|
|
password: '',
|
|
first_name: '',
|
|
last_name: '',
|
|
phone: '',
|
|
role: 'mitarbeiter' as const
|
|
})
|
|
|
|
onMounted(async () => {
|
|
await loadUsers()
|
|
})
|
|
|
|
async function loadUsers() {
|
|
loading.value = true
|
|
try {
|
|
const res = await api.get<{ users: User[] }>('/users')
|
|
users.value = res.data.users
|
|
} catch (e) {
|
|
console.error(e)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function createUser() {
|
|
try {
|
|
await api.post('/users', newUser.value)
|
|
showCreateModal.value = false
|
|
newUser.value = { email: '', password: '', first_name: '', last_name: '', phone: '', role: 'mitarbeiter' }
|
|
await loadUsers()
|
|
} catch (e) {
|
|
alert(e instanceof Error ? e.message : 'Fehler beim Erstellen')
|
|
}
|
|
}
|
|
|
|
async function toggleActive(user: User) {
|
|
try {
|
|
if (user.active) {
|
|
await api.delete(`/users/${user.id}`)
|
|
} else {
|
|
await api.put(`/users/${user.id}`, { active: true })
|
|
}
|
|
await loadUsers()
|
|
} catch (e) {
|
|
alert(e instanceof Error ? e.message : 'Fehler')
|
|
}
|
|
}
|
|
|
|
function getRoleBadge(role: string) {
|
|
const badges: Record<string, string> = {
|
|
chef: 'badge-danger',
|
|
disponent: 'badge-primary',
|
|
mitarbeiter: 'badge-success'
|
|
}
|
|
return badges[role] || 'badge-secondary'
|
|
}
|
|
|
|
function getRoleLabel(role: string) {
|
|
const labels: Record<string, string> = {
|
|
chef: 'Chef',
|
|
disponent: 'Disponent',
|
|
mitarbeiter: 'Mitarbeiter'
|
|
}
|
|
return labels[role] || role
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="space-y-6">
|
|
<div class="flex items-center justify-between">
|
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">👥 Mitarbeiter</h1>
|
|
<button class="btn btn-primary" @click="showCreateModal = true">+ Neu</button>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div v-if="loading" class="text-center py-8 text-gray-500">Lädt...</div>
|
|
<div v-else-if="users.length === 0" class="text-center py-8 text-gray-500">Keine Mitarbeiter</div>
|
|
|
|
<table v-else class="w-full">
|
|
<thead>
|
|
<tr class="text-left text-sm text-gray-500 border-b border-gray-200 dark:border-gray-700">
|
|
<th class="pb-3">Name</th>
|
|
<th class="pb-3">E-Mail</th>
|
|
<th class="pb-3">Rolle</th>
|
|
<th class="pb-3">Status</th>
|
|
<th class="pb-3"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="user in users" :key="user.id" class="border-b border-gray-100 dark:border-gray-800">
|
|
<td class="py-3 font-medium">{{ user.first_name }} {{ user.last_name }}</td>
|
|
<td class="py-3 text-gray-500">{{ user.email }}</td>
|
|
<td class="py-3"><span :class="['badge', getRoleBadge(user.role)]">{{ getRoleLabel(user.role) }}</span></td>
|
|
<td class="py-3"><span :class="user.active ? 'text-green-600' : 'text-red-600'">{{ user.active ? 'Aktiv' : 'Inaktiv' }}</span></td>
|
|
<td class="py-3 text-right">
|
|
<button
|
|
v-if="user.id !== authStore.user?.id && user.role !== 'chef'"
|
|
class="text-sm text-gray-500 hover:text-red-600"
|
|
@click="toggleActive(user)"
|
|
>
|
|
{{ user.active ? 'Deaktivieren' : 'Aktivieren' }}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Create Modal -->
|
|
<div v-if="showCreateModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
|
<div class="card w-full max-w-md m-4">
|
|
<h2 class="text-xl font-semibold mb-6">Neuer Mitarbeiter</h2>
|
|
<form @submit.prevent="createUser" class="space-y-4">
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium mb-1">Vorname *</label>
|
|
<input v-model="newUser.first_name" type="text" required class="input" />
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium mb-1">Nachname *</label>
|
|
<input v-model="newUser.last_name" type="text" required class="input" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium mb-1">E-Mail *</label>
|
|
<input v-model="newUser.email" type="email" required class="input" />
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium mb-1">Passwort *</label>
|
|
<input v-model="newUser.password" type="password" required class="input" />
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium mb-1">Telefon</label>
|
|
<input v-model="newUser.phone" type="tel" class="input" />
|
|
</div>
|
|
<div v-if="authStore.isChef">
|
|
<label class="block text-sm font-medium mb-1">Rolle</label>
|
|
<select v-model="newUser.role" class="input">
|
|
<option value="mitarbeiter">Mitarbeiter</option>
|
|
<option value="disponent">Disponent</option>
|
|
</select>
|
|
</div>
|
|
<div class="flex justify-end gap-3 pt-4">
|
|
<button type="button" class="btn btn-secondary" @click="showCreateModal = false">Abbrechen</button>
|
|
<button type="submit" class="btn btn-primary">Erstellen</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|