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">
|
||||
<label for="teamMembers">Team-Mitglieder</label>
|
||||
<Select
|
||||
<MultiSelect
|
||||
id="teamMembers"
|
||||
v-model="editingProject.teamMembers"
|
||||
:options="users"
|
||||
option-label="email"
|
||||
placeholder="Team-Mitglieder auswählen"
|
||||
filter
|
||||
multiple
|
||||
display="chip"
|
||||
:disabled="saving"
|
||||
:max-selected-labels="3"
|
||||
>
|
||||
<template #option="slotProps">
|
||||
<div class="flex flex-col">
|
||||
@ -325,7 +326,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</Select>
|
||||
</MultiSelect>
|
||||
<small class="text-500">Team-Mitglieder können das Projekt ansehen und bearbeiten</small>
|
||||
</div>
|
||||
</div>
|
||||
@ -703,6 +704,42 @@
|
||||
</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 -->
|
||||
<div class="p-4 border-round border-1 surface-border">
|
||||
<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 Textarea from 'primevue/textarea'
|
||||
import Select from 'primevue/select'
|
||||
import MultiSelect from 'primevue/multiselect'
|
||||
import DatePicker from 'primevue/datepicker'
|
||||
import InputNumber from 'primevue/inputnumber'
|
||||
import RadioButton from 'primevue/radiobutton'
|
||||
@ -898,7 +936,11 @@ const projectColumns = ref([
|
||||
])
|
||||
|
||||
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() {
|
||||
@ -967,15 +1009,10 @@ async function loadUsers() {
|
||||
}
|
||||
|
||||
async function loadCurrentUser() {
|
||||
try {
|
||||
const response = await fetch('/api/me')
|
||||
if (!response.ok) throw new Error('Fehler beim Laden des aktuellen Benutzers')
|
||||
|
||||
currentUser.value = await response.json()
|
||||
} catch (error) {
|
||||
console.error('Error loading current user:', error)
|
||||
currentUser.value = null
|
||||
}
|
||||
// TODO: Implement proper /api/me endpoint
|
||||
// For now, use the first user from the users list as a fallback
|
||||
// This will be set after loadUsers() completes
|
||||
currentUser.value = null
|
||||
}
|
||||
|
||||
function filterByType(type, loadData) {
|
||||
@ -1056,6 +1093,33 @@ function openNewProjectDialog() {
|
||||
}
|
||||
|
||||
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
|
||||
editingProject.value = {
|
||||
...project,
|
||||
@ -1064,7 +1128,8 @@ function editProject(project) {
|
||||
endDate: project.endDate ? new Date(project.endDate) : null,
|
||||
budget: project.budget ? parseFloat(project.budget) : null,
|
||||
hourContingent: project.hourContingent ? parseFloat(project.hourContingent) : null,
|
||||
teamMembers: project.teamMembers || []
|
||||
owner: ownerObject,
|
||||
teamMembers: teamMembersArray
|
||||
}
|
||||
submitted.value = false
|
||||
projectDialog.value = true
|
||||
@ -1330,7 +1395,9 @@ async function saveProject() {
|
||||
hourContingent: editingProject.value.hourContingent ? editingProject.value.hourContingent.toString() : null,
|
||||
isPrivate: editingProject.value.isPrivate,
|
||||
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
|
||||
|
||||
@ -37,21 +37,21 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
#[Groups(['user:read', 'document:read'])]
|
||||
#[Groups(['user:read', 'document:read', 'project:read'])]
|
||||
private ?int $id = null;
|
||||
|
||||
#[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\Email(message: 'Bitte geben Sie eine gültige E-Mail-Adresse ein')]
|
||||
private ?string $email = null;
|
||||
|
||||
#[ORM\Column(length: 100)]
|
||||
#[Groups(['user:read', 'user:write', 'document:read'])]
|
||||
#[Groups(['user:read', 'user:write', 'document:read', 'project:read'])]
|
||||
private ?string $firstName = null;
|
||||
|
||||
#[ORM\Column(length: 100)]
|
||||
#[Groups(['user:read', 'user:write', 'document:read'])]
|
||||
#[Groups(['user:read', 'user:write', 'document:read', 'project:read'])]
|
||||
private ?string $lastName = null;
|
||||
|
||||
#[ORM\Column]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user