feat: Enhance user entity with project-related serialization groups
This commit is contained in:
parent
1e02439e8a
commit
ef77c1e6f1
@ -307,15 +307,16 @@
|
|||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<label for="teamMembers">Team-Mitglieder</label>
|
<label for="teamMembers">Team-Mitglieder</label>
|
||||||
<Select
|
<MultiSelect
|
||||||
id="teamMembers"
|
id="teamMembers"
|
||||||
v-model="editingProject.teamMembers"
|
v-model="editingProject.teamMembers"
|
||||||
:options="users"
|
:options="users"
|
||||||
option-label="email"
|
option-label="email"
|
||||||
placeholder="Team-Mitglieder auswählen"
|
placeholder="Team-Mitglieder auswählen"
|
||||||
filter
|
filter
|
||||||
multiple
|
display="chip"
|
||||||
:disabled="saving"
|
:disabled="saving"
|
||||||
|
:max-selected-labels="3"
|
||||||
>
|
>
|
||||||
<template #option="slotProps">
|
<template #option="slotProps">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
@ -325,7 +326,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Select>
|
</MultiSelect>
|
||||||
<small class="text-500">Team-Mitglieder können das Projekt ansehen und bearbeiten</small>
|
<small class="text-500">Team-Mitglieder können das Projekt ansehen und bearbeiten</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -703,6 +704,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Team & Access -->
|
||||||
|
<div class="p-4 border-round border-1 surface-border">
|
||||||
|
<div class="font-semibold text-lg mb-3">Team & Zugriff</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label class="font-medium text-sm text-500">Eigentümer</label>
|
||||||
|
<div v-if="viewingProject.owner" class="flex align-items-center gap-2">
|
||||||
|
<i class="pi pi-user text-primary"></i>
|
||||||
|
<div>
|
||||||
|
<div class="font-medium">{{ viewingProject.owner.email }}</div>
|
||||||
|
<div v-if="viewingProject.owner.firstName || viewingProject.owner.lastName" class="text-sm text-500">
|
||||||
|
{{ viewingProject.owner.firstName }} {{ viewingProject.owner.lastName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-900">-</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label class="font-medium text-sm text-500">Team-Mitglieder</label>
|
||||||
|
<div v-if="viewingProject.teamMembers && viewingProject.teamMembers.length > 0" class="flex flex-col gap-2">
|
||||||
|
<div v-for="member in viewingProject.teamMembers" :key="member.id" class="flex align-items-center gap-2">
|
||||||
|
<i class="pi pi-users text-500"></i>
|
||||||
|
<div>
|
||||||
|
<div class="font-medium">{{ member.email }}</div>
|
||||||
|
<div v-if="member.firstName || member.lastName" class="text-sm text-500">
|
||||||
|
{{ member.firstName }} {{ member.lastName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-500">Keine Team-Mitglieder</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Documents Section -->
|
<!-- Documents Section -->
|
||||||
<div class="p-4 border-round border-1 surface-border">
|
<div class="p-4 border-round border-1 surface-border">
|
||||||
<div class="font-semibold text-lg mb-3">Dokumente</div>
|
<div class="font-semibold text-lg mb-3">Dokumente</div>
|
||||||
@ -819,6 +856,7 @@ import Button from 'primevue/button'
|
|||||||
import InputText from 'primevue/inputtext'
|
import InputText from 'primevue/inputtext'
|
||||||
import Textarea from 'primevue/textarea'
|
import Textarea from 'primevue/textarea'
|
||||||
import Select from 'primevue/select'
|
import Select from 'primevue/select'
|
||||||
|
import MultiSelect from 'primevue/multiselect'
|
||||||
import DatePicker from 'primevue/datepicker'
|
import DatePicker from 'primevue/datepicker'
|
||||||
import InputNumber from 'primevue/inputnumber'
|
import InputNumber from 'primevue/inputnumber'
|
||||||
import RadioButton from 'primevue/radiobutton'
|
import RadioButton from 'primevue/radiobutton'
|
||||||
@ -898,7 +936,11 @@ const projectColumns = ref([
|
|||||||
])
|
])
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([loadCustomers(), loadStatuses(), loadUsers(), loadCurrentUser()])
|
await Promise.all([loadCustomers(), loadStatuses(), loadUsers()])
|
||||||
|
// Set current user to first user as fallback (until /api/me endpoint is implemented)
|
||||||
|
if (users.value.length > 0) {
|
||||||
|
currentUser.value = users.value[0]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function loadCustomers() {
|
async function loadCustomers() {
|
||||||
@ -967,15 +1009,10 @@ async function loadUsers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadCurrentUser() {
|
async function loadCurrentUser() {
|
||||||
try {
|
// TODO: Implement proper /api/me endpoint
|
||||||
const response = await fetch('/api/me')
|
// For now, use the first user from the users list as a fallback
|
||||||
if (!response.ok) throw new Error('Fehler beim Laden des aktuellen Benutzers')
|
// This will be set after loadUsers() completes
|
||||||
|
currentUser.value = null
|
||||||
currentUser.value = await response.json()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading current user:', error)
|
|
||||||
currentUser.value = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterByType(type, loadData) {
|
function filterByType(type, loadData) {
|
||||||
@ -1056,6 +1093,33 @@ function openNewProjectDialog() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function editProject(project) {
|
function editProject(project) {
|
||||||
|
// Find owner object from users array
|
||||||
|
let ownerObject = null
|
||||||
|
if (project.owner) {
|
||||||
|
if (typeof project.owner === 'object' && project.owner.id) {
|
||||||
|
ownerObject = users.value.find(u => u.id === project.owner.id) || project.owner
|
||||||
|
} else if (typeof project.owner === 'string') {
|
||||||
|
// Extract ID from IRI like "/api/users/1"
|
||||||
|
const ownerId = parseInt(project.owner.split('/').pop())
|
||||||
|
ownerObject = users.value.find(u => u.id === ownerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find team member objects from users array
|
||||||
|
let teamMembersArray = []
|
||||||
|
if (Array.isArray(project.teamMembers)) {
|
||||||
|
teamMembersArray = project.teamMembers.map(member => {
|
||||||
|
if (typeof member === 'object' && member.id) {
|
||||||
|
return users.value.find(u => u.id === member.id) || member
|
||||||
|
} else if (typeof member === 'string') {
|
||||||
|
// Extract ID from IRI like "/api/users/1"
|
||||||
|
const memberId = parseInt(member.split('/').pop())
|
||||||
|
return users.value.find(u => u.id === memberId)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}).filter(m => m !== null)
|
||||||
|
}
|
||||||
|
|
||||||
// Convert date strings to Date objects for Calendar component
|
// Convert date strings to Date objects for Calendar component
|
||||||
editingProject.value = {
|
editingProject.value = {
|
||||||
...project,
|
...project,
|
||||||
@ -1064,7 +1128,8 @@ function editProject(project) {
|
|||||||
endDate: project.endDate ? new Date(project.endDate) : null,
|
endDate: project.endDate ? new Date(project.endDate) : null,
|
||||||
budget: project.budget ? parseFloat(project.budget) : null,
|
budget: project.budget ? parseFloat(project.budget) : null,
|
||||||
hourContingent: project.hourContingent ? parseFloat(project.hourContingent) : null,
|
hourContingent: project.hourContingent ? parseFloat(project.hourContingent) : null,
|
||||||
teamMembers: project.teamMembers || []
|
owner: ownerObject,
|
||||||
|
teamMembers: teamMembersArray
|
||||||
}
|
}
|
||||||
submitted.value = false
|
submitted.value = false
|
||||||
projectDialog.value = true
|
projectDialog.value = true
|
||||||
@ -1330,7 +1395,9 @@ async function saveProject() {
|
|||||||
hourContingent: editingProject.value.hourContingent ? editingProject.value.hourContingent.toString() : null,
|
hourContingent: editingProject.value.hourContingent ? editingProject.value.hourContingent.toString() : null,
|
||||||
isPrivate: editingProject.value.isPrivate,
|
isPrivate: editingProject.value.isPrivate,
|
||||||
owner: `/api/users/${editingProject.value.owner.id}`,
|
owner: `/api/users/${editingProject.value.owner.id}`,
|
||||||
teamMembers: editingProject.value.teamMembers?.map(member => `/api/users/${member.id}`) || []
|
teamMembers: Array.isArray(editingProject.value.teamMembers)
|
||||||
|
? editingProject.value.teamMembers.map(member => `/api/users/${member.id}`)
|
||||||
|
: []
|
||||||
}
|
}
|
||||||
|
|
||||||
const isNew = !editingProject.value.id
|
const isNew = !editingProject.value.id
|
||||||
|
|||||||
@ -37,21 +37,21 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
#[Groups(['user:read', 'document:read'])]
|
#[Groups(['user:read', 'document:read', 'project:read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 180)]
|
#[ORM\Column(length: 180)]
|
||||||
#[Groups(['user:read', 'user:write'])]
|
#[Groups(['user:read', 'user:write', 'project:read'])]
|
||||||
#[Assert\NotBlank(message: 'Die E-Mail-Adresse darf nicht leer sein')]
|
#[Assert\NotBlank(message: 'Die E-Mail-Adresse darf nicht leer sein')]
|
||||||
#[Assert\Email(message: 'Bitte geben Sie eine gültige E-Mail-Adresse ein')]
|
#[Assert\Email(message: 'Bitte geben Sie eine gültige E-Mail-Adresse ein')]
|
||||||
private ?string $email = null;
|
private ?string $email = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 100)]
|
#[ORM\Column(length: 100)]
|
||||||
#[Groups(['user:read', 'user:write', 'document:read'])]
|
#[Groups(['user:read', 'user:write', 'document:read', 'project:read'])]
|
||||||
private ?string $firstName = null;
|
private ?string $firstName = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 100)]
|
#[ORM\Column(length: 100)]
|
||||||
#[Groups(['user:read', 'user:write', 'document:read'])]
|
#[Groups(['user:read', 'user:write', 'document:read', 'project:read'])]
|
||||||
private ?string $lastName = null;
|
private ?string $lastName = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user