Add hall key feature to member management, including UI updates for displaying and editing hall key status. Update API to handle hall key data in member records.
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 1m6s
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 1m6s
This commit is contained in:
@@ -85,6 +85,9 @@
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Mannschaft
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
🔑
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Status
|
||||
</th>
|
||||
@@ -177,6 +180,15 @@
|
||||
{{ member.isMannschaftsspieler ? 'Ja' : 'Nein' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<span
|
||||
:class="member.hasHallKey ? 'text-amber-600' : 'text-gray-300'"
|
||||
:title="member.hasHallKey ? 'Hat Hallenschlüssel' : 'Hat keinen Hallenschlüssel'"
|
||||
class="text-lg"
|
||||
>
|
||||
🔑
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span
|
||||
@@ -250,6 +262,12 @@
|
||||
<h3 class="text-xl font-semibold text-gray-900">
|
||||
{{ member.name }}
|
||||
</h3>
|
||||
<span
|
||||
:class="member.hasHallKey ? 'ml-2 text-amber-600' : 'ml-2 text-gray-300'"
|
||||
:title="member.hasHallKey ? 'Hat Hallenschlüssel' : 'Hat keinen Hallenschlüssel'"
|
||||
>
|
||||
🔑
|
||||
</span>
|
||||
<span
|
||||
v-if="member.hasLogin"
|
||||
class="ml-3 px-2 py-1 bg-green-100 text-green-800 text-xs font-medium rounded-full"
|
||||
@@ -504,6 +522,22 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="hasHallKey"
|
||||
v-model="formData.hasHallKey"
|
||||
type="checkbox"
|
||||
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
|
||||
:disabled="isSaving"
|
||||
>
|
||||
<label
|
||||
for="hasHallKey"
|
||||
class="ml-2 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Hat Hallenschlüssel
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="errorMessage"
|
||||
class="flex items-center p-3 rounded-md bg-red-50 text-red-700 text-sm"
|
||||
@@ -752,7 +786,8 @@ const formData = ref({
|
||||
phone: '',
|
||||
address: '',
|
||||
notes: '',
|
||||
isMannschaftsspieler: false
|
||||
isMannschaftsspieler: false,
|
||||
hasHallKey: false
|
||||
})
|
||||
|
||||
const canEdit = computed(() => {
|
||||
@@ -777,7 +812,7 @@ const loadMembers = async () => {
|
||||
|
||||
const openAddModal = () => {
|
||||
editingMember.value = null
|
||||
formData.value = { firstName: '', lastName: '', geburtsdatum: '', email: '', phone: '', address: '', notes: '', isMannschaftsspieler: false }
|
||||
formData.value = { firstName: '', lastName: '', geburtsdatum: '', email: '', phone: '', address: '', notes: '', isMannschaftsspieler: false, hasHallKey: false }
|
||||
showModal.value = true
|
||||
errorMessage.value = ''
|
||||
}
|
||||
@@ -792,7 +827,8 @@ const openEditModal = (member) => {
|
||||
phone: member.phone || '',
|
||||
address: member.address || '',
|
||||
notes: member.notes || '',
|
||||
isMannschaftsspieler: member.isMannschaftsspieler === true
|
||||
isMannschaftsspieler: member.isMannschaftsspieler === true,
|
||||
hasHallKey: member.hasHallKey === true
|
||||
}
|
||||
showModal.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
@@ -96,6 +96,9 @@
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Mannschaft
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
🔑
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Status
|
||||
</th>
|
||||
@@ -184,6 +187,15 @@
|
||||
{{ member.isMannschaftsspieler ? 'Ja' : 'Nein' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<span
|
||||
:class="member.hasHallKey ? 'text-amber-600' : 'text-gray-300'"
|
||||
:title="member.hasHallKey ? 'Hat Hallenschlüssel' : 'Hat keinen Hallenschlüssel'"
|
||||
class="text-lg"
|
||||
>
|
||||
🔑
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span
|
||||
@@ -265,6 +277,12 @@
|
||||
🎂 {{ formatBirthday(member.birthday) }}
|
||||
</span>
|
||||
</h3>
|
||||
<span
|
||||
:class="member.hasHallKey ? 'ml-2 text-amber-600' : 'ml-2 text-gray-300'"
|
||||
:title="member.hasHallKey ? 'Hat Hallenschlüssel' : 'Hat keinen Hallenschlüssel'"
|
||||
>
|
||||
🔑
|
||||
</span>
|
||||
<span
|
||||
v-if="member.hasLogin"
|
||||
class="ml-3 px-2 py-1 bg-green-100 text-green-800 text-xs font-medium rounded-full"
|
||||
@@ -508,6 +526,22 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="hasHallKey"
|
||||
v-model="formData.hasHallKey"
|
||||
type="checkbox"
|
||||
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
|
||||
:disabled="isSaving"
|
||||
>
|
||||
<label
|
||||
for="hasHallKey"
|
||||
class="ml-2 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Hat Hallenschlüssel
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="errorMessage"
|
||||
class="flex items-center p-3 rounded-md bg-red-50 text-red-700 text-sm"
|
||||
@@ -876,7 +910,8 @@ const formData = ref({
|
||||
phone: '',
|
||||
address: '',
|
||||
notes: '',
|
||||
isMannschaftsspieler: false
|
||||
isMannschaftsspieler: false,
|
||||
hasHallKey: false
|
||||
})
|
||||
|
||||
const canEdit = computed(() => {
|
||||
@@ -910,7 +945,8 @@ const openAddModal = () => {
|
||||
phone: '',
|
||||
address: '',
|
||||
notes: '',
|
||||
isMannschaftsspieler: false
|
||||
isMannschaftsspieler: false,
|
||||
hasHallKey: false
|
||||
}
|
||||
showModal.value = true
|
||||
errorMessage.value = ''
|
||||
@@ -926,7 +962,8 @@ const openEditModal = (member) => {
|
||||
phone: member.phone || '',
|
||||
address: member.address || '',
|
||||
notes: member.notes || '',
|
||||
isMannschaftsspieler: member.isMannschaftsspieler === true
|
||||
isMannschaftsspieler: member.isMannschaftsspieler === true,
|
||||
hasHallKey: member.hasHallKey === true
|
||||
}
|
||||
showModal.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
@@ -242,6 +242,7 @@ export default defineEventHandler(async (event) => {
|
||||
loginRole: member.loginRole,
|
||||
lastLogin: member.lastLogin,
|
||||
isMannschaftsspieler: member.isMannschaftsspieler,
|
||||
hasHallKey: member.hasHallKey === true || member.hasHallenschluessel === true,
|
||||
notes: member.notes || '',
|
||||
// Sichtbarkeits-Flags explizit mitgeben
|
||||
showEmail: visibility.showEmail === undefined ? true : Boolean(visibility.showEmail),
|
||||
|
||||
@@ -48,7 +48,7 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
|
||||
const body = await readBody(event)
|
||||
const { id, firstName, lastName, geburtsdatum, email, phone, address, notes, isMannschaftsspieler, active } = body
|
||||
const { id, firstName, lastName, geburtsdatum, email, phone, address, notes, isMannschaftsspieler, hasHallKey, hasHallenschluessel, active } = body
|
||||
|
||||
if (!firstName || !lastName) {
|
||||
throw createError({
|
||||
@@ -75,6 +75,7 @@ export default defineEventHandler(async (event) => {
|
||||
address: address || '',
|
||||
notes: notes || '',
|
||||
isMannschaftsspieler: isMannschaftsspieler === true || isMannschaftsspieler === 'true',
|
||||
hasHallKey: hasHallKey === true || hasHallKey === 'true' || hasHallenschluessel === true || hasHallenschluessel === 'true',
|
||||
active: typeof active === 'boolean' ? active : true
|
||||
})
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createEvent, mockSuccessReadBody } from './setup'
|
||||
vi.mock('../server/utils/auth.js', () => ({
|
||||
verifyToken: vi.fn(),
|
||||
getUserById: vi.fn(),
|
||||
getUserFromToken: vi.fn(),
|
||||
readUsers: vi.fn(),
|
||||
readMembers: vi.fn(),
|
||||
writeUsers: vi.fn(),
|
||||
@@ -24,6 +25,11 @@ vi.mock('../server/utils/auth.js', () => ({
|
||||
if (!user) return false
|
||||
const userRoles = Array.isArray(user.roles) ? user.roles : (user.role ? [user.role] : [])
|
||||
return roles.some(r => userRoles.includes(r))
|
||||
}),
|
||||
hasRole: vi.fn((user, role) => {
|
||||
if (!user) return false
|
||||
const userRoles = Array.isArray(user.roles) ? user.roles : (user.role ? [user.role] : [])
|
||||
return userRoles.includes(role)
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -58,16 +64,18 @@ describe('Members API Endpoints', () => {
|
||||
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||||
authUtils.verifyToken.mockReturnValue({ id: '1' })
|
||||
memberUtils.readMembers.mockResolvedValue([
|
||||
{ id: 'm1', firstName: 'Anna', lastName: 'Muster', email: 'anna@club.de' }
|
||||
{ id: 'm1', firstName: 'Anna', lastName: 'Muster', email: 'anna@club.de', hasHallKey: true, active: true }
|
||||
])
|
||||
authUtils.readUsers.mockResolvedValue([
|
||||
{ id: 'u1', name: 'Ben Nutzer', email: 'ben@club.de', role: 'mitglied', active: true }
|
||||
])
|
||||
authUtils.getUserFromToken.mockResolvedValue({ id: '1', role: 'mitglied' })
|
||||
|
||||
const response = await membersGetHandler(event)
|
||||
|
||||
expect(response.success).toBe(true)
|
||||
expect(response.members).toHaveLength(2)
|
||||
expect(response.members[0]).toHaveProperty('hasHallKey', true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -76,7 +84,8 @@ describe('Members API Endpoints', () => {
|
||||
firstName: 'Lisa',
|
||||
lastName: 'Beispiel',
|
||||
geburtsdatum: '2000-01-01',
|
||||
email: 'lisa@example.com'
|
||||
email: 'lisa@example.com',
|
||||
hasHallKey: true
|
||||
}
|
||||
|
||||
it('verweigert Zugriff ohne Token', async () => {
|
||||
@@ -113,7 +122,9 @@ describe('Members API Endpoints', () => {
|
||||
|
||||
const response = await membersPostHandler(event)
|
||||
expect(response.success).toBe(true)
|
||||
expect(memberUtils.saveMember).toHaveBeenCalled()
|
||||
expect(memberUtils.saveMember).toHaveBeenCalledWith(expect.objectContaining({
|
||||
hasHallKey: true
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user