🎨 Frontend komplett implementiert
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
This commit is contained in:
162
src/views/OrderDetailView.vue
Normal file
162
src/views/OrderDetailView.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { api } from '@/api'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const order = ref<any>(null)
|
||||
const assignments = ref<any[]>([])
|
||||
const loading = ref(true)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const res = await api.get<{ order: any; assignments: any[] }>(`/orders/${route.params.id}`)
|
||||
order.value = res.data.order
|
||||
assignments.value = res.data.assignments
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
router.push('/orders')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
async function updateStatus(status: string) {
|
||||
try {
|
||||
await api.put(`/orders/${route.params.id}`, { status })
|
||||
order.value.status = status
|
||||
} catch (e) {
|
||||
alert(e instanceof Error ? e.message : 'Fehler')
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmAssignment(confirm: boolean) {
|
||||
try {
|
||||
await api.put(`/orders/${route.params.id}/assignment`, {
|
||||
status: confirm ? 'confirmed' : 'declined'
|
||||
})
|
||||
const myAssignment = assignments.value.find(a => a.user_id === authStore.user?.id)
|
||||
if (myAssignment) {
|
||||
myAssignment.status = confirm ? 'confirmed' : 'declined'
|
||||
}
|
||||
} catch (e) {
|
||||
alert(e instanceof Error ? e.message : 'Fehler')
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusLabel(status: string) {
|
||||
const labels: Record<string, string> = {
|
||||
draft: 'Entwurf', published: 'Veröffentlicht', in_progress: 'In Bearbeitung',
|
||||
completed: 'Abgeschlossen', cancelled: 'Abgesagt',
|
||||
pending: 'Ausstehend', confirmed: 'Bestätigt', declined: 'Abgelehnt'
|
||||
}
|
||||
return labels[status] || status
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="loading" class="text-center py-12 text-gray-500">Lädt...</div>
|
||||
|
||||
<div v-else-if="order" class="space-y-6">
|
||||
<!-- Back button -->
|
||||
<router-link to="/orders" class="text-primary-600 hover:text-primary-700 text-sm">
|
||||
← Zurück zu Aufträge
|
||||
</router-link>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="card">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-gray-500">#{{ order.number }}</span>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{{ order.title }}</h1>
|
||||
</div>
|
||||
<p v-if="order.description" class="mt-2 text-gray-600 dark:text-gray-400">
|
||||
{{ order.description }}
|
||||
</p>
|
||||
</div>
|
||||
<span :class="['badge', order.status === 'completed' ? 'badge-success' : 'badge-primary']">
|
||||
{{ getStatusLabel(order.status) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Info Grid -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mt-6">
|
||||
<div v-if="order.location">
|
||||
<p class="text-sm text-gray-500">Ort</p>
|
||||
<p class="font-medium">📍 {{ order.location }}</p>
|
||||
</div>
|
||||
<div v-if="order.start_time">
|
||||
<p class="text-sm text-gray-500">Start</p>
|
||||
<p class="font-medium">{{ new Date(order.start_time).toLocaleString('de-DE') }}</p>
|
||||
</div>
|
||||
<div v-if="order.client_name">
|
||||
<p class="text-sm text-gray-500">Kunde</p>
|
||||
<p class="font-medium">{{ order.client_name }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Benötigte MA</p>
|
||||
<p class="font-medium">{{ assignments.length }}/{{ order.required_staff }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status actions for management -->
|
||||
<div v-if="authStore.canManageOrders" class="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<p class="text-sm text-gray-500 mb-2">Status ändern:</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button class="btn btn-secondary text-sm" @click="updateStatus('draft')">Entwurf</button>
|
||||
<button class="btn btn-primary text-sm" @click="updateStatus('published')">Veröffentlichen</button>
|
||||
<button class="btn btn-warning text-sm" @click="updateStatus('in_progress')">In Bearbeitung</button>
|
||||
<button class="btn btn-success text-sm" @click="updateStatus('completed')">Abschließen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Assignments -->
|
||||
<div class="card">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
👥 Zugewiesene Mitarbeiter
|
||||
</h2>
|
||||
|
||||
<div v-if="assignments.length === 0" class="text-center py-4 text-gray-500">
|
||||
Noch keine Mitarbeiter zugewiesen
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-3">
|
||||
<div
|
||||
v-for="assignment in assignments"
|
||||
:key="assignment.id"
|
||||
class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg"
|
||||
>
|
||||
<div>
|
||||
<p class="font-medium text-gray-900 dark:text-white">{{ assignment.user_name }}</p>
|
||||
<p class="text-sm text-gray-500">{{ assignment.user_phone }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span :class="['badge', assignment.status === 'confirmed' ? 'badge-success' : assignment.status === 'declined' ? 'badge-danger' : 'badge-warning']">
|
||||
{{ getStatusLabel(assignment.status) }}
|
||||
</span>
|
||||
|
||||
<!-- Confirm/Decline buttons for assigned user -->
|
||||
<template v-if="assignment.user_id === authStore.user?.id && assignment.status === 'pending'">
|
||||
<button class="btn btn-success text-sm" @click="confirmAssignment(true)">✓</button>
|
||||
<button class="btn btn-danger text-sm" @click="confirmAssignment(false)">✗</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Special Instructions -->
|
||||
<div v-if="order.special_instructions" class="card">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
||||
📝 Besondere Hinweise
|
||||
</h2>
|
||||
<p class="text-gray-600 dark:text-gray-400 whitespace-pre-wrap">{{ order.special_instructions }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user