Added Frontend
This commit is contained in:
318
frontend/pages/staff/profiles/[id].vue
Normal file
318
frontend/pages/staff/profiles/[id].vue
Normal file
@@ -0,0 +1,318 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
const route = useRoute()
|
||||
const toast = useToast()
|
||||
const { $api } = useNuxtApp()
|
||||
|
||||
const id = route.params.id as string
|
||||
const profile = ref<any>(null)
|
||||
const pending = ref(true)
|
||||
const saving = ref(false)
|
||||
|
||||
/** Profil laden **/
|
||||
async function fetchProfile() {
|
||||
pending.value = true
|
||||
try {
|
||||
profile.value = await $api(`/api/profiles/${id}`)
|
||||
ensureWorkingHoursStructure()
|
||||
} catch (err: any) {
|
||||
console.error('[fetchProfile]', err)
|
||||
toast.add({
|
||||
title: 'Fehler beim Laden',
|
||||
description: err?.data?.error || err?.message || 'Unbekannter Fehler',
|
||||
color: 'red'
|
||||
})
|
||||
} finally {
|
||||
pending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** Profil speichern **/
|
||||
async function saveProfile() {
|
||||
if (saving.value) return
|
||||
saving.value = true
|
||||
|
||||
try {
|
||||
await $api(`/api/profiles/${id}`, {
|
||||
method: 'PUT',
|
||||
body: profile.value
|
||||
})
|
||||
toast.add({ title: 'Profil gespeichert', color: 'green' })
|
||||
fetchProfile()
|
||||
} catch (err: any) {
|
||||
console.error('[saveProfile]', err)
|
||||
toast.add({
|
||||
title: 'Fehler beim Speichern',
|
||||
description: err?.data?.error || err?.message || 'Unbekannter Fehler',
|
||||
color: 'red'
|
||||
})
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const weekdays = [
|
||||
{ key: '1', label: 'Montag' },
|
||||
{ key: '2', label: 'Dienstag' },
|
||||
{ key: '3', label: 'Mittwoch' },
|
||||
{ key: '4', label: 'Donnerstag' },
|
||||
{ key: '5', label: 'Freitag' },
|
||||
{ key: '6', label: 'Samstag' },
|
||||
{ key: '7', label: 'Sonntag' }
|
||||
]
|
||||
|
||||
const bundeslaender = [
|
||||
{ code: 'DE-BW', name: 'Baden-Württemberg' },
|
||||
{ code: 'DE-BY', name: 'Bayern' },
|
||||
{ code: 'DE-BE', name: 'Berlin' },
|
||||
{ code: 'DE-BB', name: 'Brandenburg' },
|
||||
{ code: 'DE-HB', name: 'Bremen' },
|
||||
{ code: 'DE-HH', name: 'Hamburg' },
|
||||
{ code: 'DE-HE', name: 'Hessen' },
|
||||
{ code: 'DE-MV', name: 'Mecklenburg-Vorpommern' },
|
||||
{ code: 'DE-NI', name: 'Niedersachsen' },
|
||||
{ code: 'DE-NW', name: 'Nordrhein-Westfalen' },
|
||||
{ code: 'DE-RP', name: 'Rheinland-Pfalz' },
|
||||
{ code: 'DE-SL', name: 'Saarland' },
|
||||
{ code: 'DE-SN', name: 'Sachsen' },
|
||||
{ code: 'DE-ST', name: 'Sachsen-Anhalt' },
|
||||
{ code: 'DE-SH', name: 'Schleswig-Holstein' },
|
||||
{ code: 'DE-TH', name: 'Thüringen' }
|
||||
]
|
||||
|
||||
|
||||
// Sicherstellen, dass das JSON-Feld existiert
|
||||
function ensureWorkingHoursStructure() {
|
||||
if (!profile.value.weekly_regular_working_hours) {
|
||||
profile.value.weekly_regular_working_hours = {}
|
||||
}
|
||||
for (const { key } of weekdays) {
|
||||
if (profile.value.weekly_regular_working_hours[key] == null) {
|
||||
profile.value.weekly_regular_working_hours[key] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function recalculateWeeklyHours() {
|
||||
if (!profile.value?.weekly_regular_working_hours) return
|
||||
|
||||
const total = Object.values(profile.value.weekly_regular_working_hours).reduce(
|
||||
(sum: number, val: any) => {
|
||||
const num = parseFloat(val)
|
||||
return sum + (isNaN(num) ? 0 : num)
|
||||
},
|
||||
0
|
||||
)
|
||||
|
||||
profile.value.weekly_working_hours = Number(total.toFixed(2))
|
||||
}
|
||||
|
||||
const checkZip = async () => {
|
||||
const zipData = await useFunctions().useZipCheck(profile.value.address_zip)
|
||||
profile.value.address_city = zipData.short
|
||||
profile.value.state_code = zipData.state_code
|
||||
}
|
||||
|
||||
onMounted(fetchProfile)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Haupt-Navigation -->
|
||||
<UDashboardNavbar title="Mitarbeiter">
|
||||
<template #left>
|
||||
<UButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
@click="navigateTo(`/staff/profiles`)"
|
||||
icon="i-heroicons-chevron-left"
|
||||
|
||||
>
|
||||
Mitarbeiter
|
||||
</UButton>
|
||||
</template>
|
||||
<template #center>
|
||||
<h1 class="text-xl font-medium truncate">
|
||||
Mitarbeiter bearbeiten: {{ profile?.full_name || '' }}
|
||||
</h1>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<!-- Toolbar -->
|
||||
<UDashboardToolbar>
|
||||
<template #right>
|
||||
<UButton
|
||||
icon="i-mdi-content-save"
|
||||
color="primary"
|
||||
:loading="saving"
|
||||
@click="saveProfile"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
|
||||
<!-- Inhalt -->
|
||||
<UDashboardPanelContent>
|
||||
<UCard v-if="!pending && profile">
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<UAvatar size="xl" :alt="profile.full_name" />
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-900">{{ profile.full_name }}</h2>
|
||||
<p class="text-sm text-gray-500">{{ profile.employee_number || '–' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UDivider label="Persönliche Daten" />
|
||||
|
||||
<UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
|
||||
<UFormGroup label="Vorname">
|
||||
<UInput v-model="profile.first_name" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Nachname">
|
||||
<UInput v-model="profile.last_name" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="E-Mail">
|
||||
<UInput v-model="profile.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Telefon (Mobil)">
|
||||
<UInput v-model="profile.mobile_tel" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Telefon (Festnetz)">
|
||||
<UInput v-model="profile.fixed_tel" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Geburtstag">
|
||||
<UInput type="date" v-model="profile.birthday" />
|
||||
</UFormGroup>
|
||||
</UForm>
|
||||
</UCard>
|
||||
<UCard v-if="!pending && profile" class="mt-3">
|
||||
<UDivider label="Vertragsinformationen" />
|
||||
|
||||
<UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
|
||||
<UFormGroup label="Vertragsart">
|
||||
<UInput v-model="profile.contract_type"/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Status">
|
||||
<UInput v-model="profile.status"/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Position">
|
||||
<UInput v-model="profile.position"/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Qualifikation">
|
||||
<UInput v-model="profile.qualification"/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Eintrittsdatum">
|
||||
<UInput type="date" v-model="profile.entry_date" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Wöchentliche Arbeitszeit (Std)">
|
||||
<UInput type="number" v-model="profile.weekly_working_hours" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Bezahlte Urlaubstage (Jahr)">
|
||||
<UInput type="number" v-model="profile.annual_paid_leave_days" />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="Aktiv">
|
||||
<div class="flex items-center gap-3">
|
||||
<UToggle v-model="profile.active" color="primary" />
|
||||
<span class="text-sm text-gray-600">
|
||||
</span>
|
||||
</div>
|
||||
</UFormGroup>
|
||||
</UForm>
|
||||
|
||||
</UCard>
|
||||
|
||||
<UCard v-if="!pending && profile" class="mt-3">
|
||||
<UDivider label="Adresse & Standort" />
|
||||
|
||||
<UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
|
||||
<UFormGroup label="Straße und Hausnummer">
|
||||
<UInput v-model="profile.address_street"/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="PLZ">
|
||||
<UInput type="text" v-model="profile.address_zip" @focusout="checkZip"/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Ort">
|
||||
<UInput v-model="profile.address_city"/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Bundesland">
|
||||
<USelectMenu
|
||||
v-model="profile.state_code"
|
||||
:options="bundeslaender"
|
||||
value-attribute="code"
|
||||
option-attribute="name"
|
||||
placeholder="Bundesland auswählen"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</UForm>
|
||||
</UCard>
|
||||
|
||||
|
||||
<UCard v-if="!pending && profile" class="mt-3">
|
||||
<UDivider label="Wöchentliche Arbeitsstunden" />
|
||||
|
||||
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div
|
||||
v-for="day in weekdays"
|
||||
:key="day.key"
|
||||
:class="[...profile.weekly_regular_working_hours[day.key] === 0 ? ['bg-gray-100'] : ['bg-gray-100','border-green-400'], 'flex items-center justify-between border rounded-lg p-3 bg-gray-50']"
|
||||
>
|
||||
<span class="font-medium text-gray-700">{{ day.label }}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<UInput
|
||||
type="number"
|
||||
size="sm"
|
||||
min="0"
|
||||
max="24"
|
||||
step="0.25"
|
||||
v-model.number="profile.weekly_regular_working_hours[day.key]"
|
||||
placeholder="0"
|
||||
class="w-24"
|
||||
@change="recalculateWeeklyHours"
|
||||
/>
|
||||
<span class="text-gray-400 text-sm">Std</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard v-if="!pending && profile" class="mt-3">
|
||||
<UDivider label="Sonstiges" />
|
||||
<UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
|
||||
<UFormGroup label="Kleidergröße (Oberteil)">
|
||||
<UInput v-model="profile.clothing_size_top" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Kleidergröße (Hose)">
|
||||
<UInput v-model="profile.clothing_size_bottom" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Schuhgröße">
|
||||
<UInput v-model="profile.clothing_size_shoe" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Token-ID">
|
||||
<UInput v-model="profile.token_id" />
|
||||
</UFormGroup>
|
||||
</UForm>
|
||||
</UCard>
|
||||
|
||||
<USkeleton v-if="pending" height="300px" />
|
||||
</UDashboardPanelContent>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user