cms.c2sgmbh/src/app/(frontend)/api/team/route.ts
Martin Porwoll 9016d3c06c fix: resolve all TypeScript errors in production code
- Add Where type imports and proper type assertions in API routes
- Add Locale type definitions for locale validation
- Fix email-logs/stats route with proper EmailLog typing
- Fix newsletter-service interests type and null checks
- Remove invalid contact field from OpenAPI metadata
- Fix formSubmissionOverrides type casting in payload.config
- Fix vcard route Team type casting

All 24 TypeScript errors in src/ are now resolved.
Test files have separate type issues that don't affect production.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-15 09:08:16 +00:00

218 lines
6.2 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
import { getPayload } from 'payload'
import type { Where } from 'payload'
import config from '@payload-config'
type Locale = 'de' | 'en' | 'all'
/**
* Team API
*
* GET /api/team - Liste aller Team-Mitglieder mit Filter und Suche
*
* Query-Parameter:
* - tenant (required): Tenant ID
* - slug: Einzelnes Mitglied nach Slug
* - search: Volltextsuche in Name, Rolle, Abteilung, Bio
* - department: Nach Abteilung filtern
* - level: Nach Hierarchie-Ebene filtern
* - specialization: Nach Fachgebiet filtern
* - language: Nach Sprache filtern
* - featured: Nur hervorgehobene (true/false)
* - limit: Maximale Anzahl (default: 50)
* - page: Seite für Pagination
* - sort: Sortierung (order, name, department, startDate)
* - locale: Sprache (de/en)
*/
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
// Required: Tenant
const tenantId = searchParams.get('tenant')
if (!tenantId) {
return NextResponse.json({ error: 'tenant parameter is required' }, { status: 400 })
}
const payload = await getPayload({ config })
// Optional parameters
const slug = searchParams.get('slug')
const search = searchParams.get('search')
const department = searchParams.get('department')
const level = searchParams.get('level')
const specialization = searchParams.get('specialization')
const language = searchParams.get('language')
const featured = searchParams.get('featured')
const limit = Math.min(parseInt(searchParams.get('limit') || '50'), 100)
const page = parseInt(searchParams.get('page') || '1')
const sort = searchParams.get('sort') || 'order'
const localeParam = searchParams.get('locale')
const locale: Locale = (localeParam === 'de' || localeParam === 'en') ? localeParam : 'de'
// Build where clause
const where: Where = {
tenant: { equals: parseInt(tenantId) },
isActive: { equals: true },
}
// Single member by slug
if (slug) {
where.slug = { equals: slug }
}
// Department filter
if (department) {
where.department = { contains: department }
}
// Hierarchy level filter
if (level) {
where.hierarchyLevel = { equals: level }
}
// Featured filter
if (featured === 'true') {
where.isFeatured = { equals: true }
}
// Build sort string
let sortField = 'order'
switch (sort) {
case 'name':
sortField = 'name'
break
case 'department':
sortField = 'department'
break
case 'startDate':
sortField = '-startDate'
break
case '-order':
sortField = '-order'
break
default:
sortField = 'order'
}
// Query team members
const result = await payload.find({
collection: 'team',
where: where as Where,
sort: sortField,
limit,
page,
locale,
depth: 2, // Include image and reportsTo
})
let members = result.docs
// Post-query filters (for array fields)
// Search filter (case-insensitive)
if (search) {
const searchLower = search.toLowerCase()
members = members.filter((member) => {
const nameMatch = member.name?.toLowerCase().includes(searchLower)
const roleMatch =
typeof member.role === 'string' && member.role.toLowerCase().includes(searchLower)
const deptMatch =
typeof member.department === 'string' &&
member.department.toLowerCase().includes(searchLower)
const bioMatch =
typeof member.bioShort === 'string' &&
member.bioShort.toLowerCase().includes(searchLower)
// Search in specializations
const specMatch =
Array.isArray(member.specializations) &&
member.specializations.some(
(s) => typeof s.title === 'string' && s.title.toLowerCase().includes(searchLower)
)
return nameMatch || roleMatch || deptMatch || bioMatch || specMatch
})
}
// Specialization filter
if (specialization) {
const specLower = specialization.toLowerCase()
members = members.filter(
(member) =>
Array.isArray(member.specializations) &&
member.specializations.some(
(s) => typeof s.title === 'string' && s.title.toLowerCase().includes(specLower)
)
)
}
// Language filter
if (language) {
const langLower = language.toLowerCase()
members = members.filter(
(member) =>
Array.isArray(member.languages) &&
member.languages.some(
(l) => typeof l.language === 'string' && l.language.toLowerCase().includes(langLower)
)
)
}
// Get unique departments for filter dropdown
const allMembers = await payload.find({
collection: 'team',
where: {
tenant: { equals: parseInt(tenantId) },
isActive: { equals: true },
},
limit: 1000,
locale,
})
const departments = [
...new Set(allMembers.docs.map((m) => m.department).filter(Boolean)),
].sort() as string[]
const specializations = [
...new Set(
allMembers.docs.flatMap((m) =>
Array.isArray(m.specializations) ? m.specializations.map((s) => s.title) : []
)
),
]
.filter(Boolean)
.sort() as string[]
const languages = [
...new Set(
allMembers.docs.flatMap((m) =>
Array.isArray(m.languages) ? m.languages.map((l) => l.language) : []
)
),
]
.filter(Boolean)
.sort() as string[]
// Single member response
if (slug && members.length === 1) {
return NextResponse.json({
member: members[0],
filters: { departments, specializations, languages },
})
}
return NextResponse.json({
members,
totalDocs: result.totalDocs,
totalPages: result.totalPages,
page: result.page,
hasNextPage: result.hasNextPage,
hasPrevPage: result.hasPrevPage,
filters: { departments, specializations, languages },
})
} catch (error) {
console.error('Team API error:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}