Implement Prototype 1 improvements: unified members, project filters, audit expansion, filtering rounds, special awards
- Unified Member Management: merge /admin/users and /admin/mentors into /admin/members with role tabs, search, pagination - Project List Filters: add search, multi-status filter, round/category/country selects, boolean toggles, URL persistence - Audit Log Expansion: track logins, round state changes, evaluation submissions, file access, role changes via shared logAudit utility - Founding Date Field: add foundedAt to Project model with CSV import support - Filtering Round System: configurable rules (field-based, document check, AI screening), execution engine, results review with override/reinstate - Special Awards System: named awards with eligibility criteria, dedicated jury, PICK_WINNER/RANKED/SCORED voting modes, AI eligibility - Dashboard resilience: wrap heavy queries in try-catch to prevent error boundary on transient DB failures - Reusable pagination component extracted to src/components/shared/pagination.tsx - Old /admin/users and /admin/mentors routes redirect to /admin/members - Prisma migration for all schema additions (additive, no data loss) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -170,6 +170,7 @@ export const userRouter = router({
|
||||
.input(
|
||||
z.object({
|
||||
role: z.enum(['SUPER_ADMIN', 'PROGRAM_ADMIN', 'JURY_MEMBER', 'MENTOR', 'OBSERVER']).optional(),
|
||||
roles: z.array(z.enum(['SUPER_ADMIN', 'PROGRAM_ADMIN', 'JURY_MEMBER', 'MENTOR', 'OBSERVER'])).optional(),
|
||||
status: z.enum(['INVITED', 'ACTIVE', 'SUSPENDED']).optional(),
|
||||
search: z.string().optional(),
|
||||
page: z.number().int().min(1).default(1),
|
||||
@@ -177,12 +178,16 @@ export const userRouter = router({
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { role, status, search, page, perPage } = input
|
||||
const { role, roles, status, search, page, perPage } = input
|
||||
const skip = (page - 1) * perPage
|
||||
|
||||
const where: Record<string, unknown> = {}
|
||||
|
||||
if (role) where.role = role
|
||||
if (roles && roles.length > 0) {
|
||||
where.role = { in: roles }
|
||||
} else if (role) {
|
||||
where.role = role
|
||||
}
|
||||
if (status) where.status = status
|
||||
if (search) {
|
||||
where.OR = [
|
||||
@@ -210,7 +215,7 @@ export const userRouter = router({
|
||||
createdAt: true,
|
||||
lastLoginAt: true,
|
||||
_count: {
|
||||
select: { assignments: true },
|
||||
select: { assignments: true, mentorAssignments: true },
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -238,7 +243,7 @@ export const userRouter = router({
|
||||
where: { id: input.id },
|
||||
include: {
|
||||
_count: {
|
||||
select: { assignments: true },
|
||||
select: { assignments: true, mentorAssignments: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -356,6 +361,21 @@ export const userRouter = router({
|
||||
},
|
||||
})
|
||||
|
||||
// Track role change specifically
|
||||
if (data.role && data.role !== targetUser.role) {
|
||||
await ctx.prisma.auditLog.create({
|
||||
data: {
|
||||
userId: ctx.user.id,
|
||||
action: 'ROLE_CHANGED',
|
||||
entityType: 'User',
|
||||
entityId: id,
|
||||
detailsJson: { previousRole: targetUser.role, newRole: data.role },
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
},
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
return user
|
||||
}),
|
||||
|
||||
@@ -816,7 +836,7 @@ export const userRouter = router({
|
||||
await ctx.prisma.auditLog.create({
|
||||
data: {
|
||||
userId: ctx.user.id,
|
||||
action: 'SET_PASSWORD',
|
||||
action: 'PASSWORD_SET',
|
||||
entityType: 'User',
|
||||
entityId: ctx.user.id,
|
||||
detailsJson: { timestamp: new Date().toISOString() },
|
||||
@@ -896,7 +916,7 @@ export const userRouter = router({
|
||||
await ctx.prisma.auditLog.create({
|
||||
data: {
|
||||
userId: ctx.user.id,
|
||||
action: 'CHANGE_PASSWORD',
|
||||
action: 'PASSWORD_CHANGED',
|
||||
entityType: 'User',
|
||||
entityId: ctx.user.id,
|
||||
detailsJson: { timestamp: new Date().toISOString() },
|
||||
|
||||
Reference in New Issue
Block a user