diff --git a/src/app/(admin)/admin/awards/[id]/page.tsx b/src/app/(admin)/admin/awards/[id]/page.tsx index 592b4d4..450316d 100644 --- a/src/app/(admin)/admin/awards/[id]/page.tsx +++ b/src/app/(admin)/admin/awards/[id]/page.tsx @@ -15,6 +15,7 @@ import { import { Badge } from '@/components/ui/badge' import { Skeleton } from '@/components/ui/skeleton' import { Switch } from '@/components/ui/switch' +import { Checkbox } from '@/components/ui/checkbox' import { Label } from '@/components/ui/label' import { Table, @@ -365,6 +366,8 @@ export default function AwardDetailPage({ const [isPollingJob, setIsPollingJob] = useState(false) const pollingIntervalRef = useRef | null>(null) const [selectedJurorId, setSelectedJurorId] = useState('') + const [selectedGroupId, setSelectedGroupId] = useState('') + const [selectedGroupMembers, setSelectedGroupMembers] = useState>(new Set()) const [includeSubmitted, setIncludeSubmitted] = useState(true) const [addProjectDialogOpen, setAddProjectDialogOpen] = useState(false) const [projectSearchQuery, setProjectSearchQuery] = useState('') @@ -405,9 +408,17 @@ export default function AwardDetailPage({ // Deferred queries - only load when needed const { data: allUsers } = trpc.user.list.useQuery( - { page: 1, perPage: 200 }, + { page: 1, perPage: 100, roles: ['JURY_MEMBER', 'AWARD_MASTER'] }, { enabled: activeTab === 'jurors' } ) + const { data: juryGroups } = trpc.juryGroup.list.useQuery( + { competitionId: award?.competition?.id ?? '' }, + { enabled: activeTab === 'jurors' && !!award?.competition?.id } + ) + const { data: selectedGroupDetail } = trpc.juryGroup.getById.useQuery( + { id: selectedGroupId }, + { enabled: !!selectedGroupId } + ) const { data: allProjects } = trpc.project.list.useQuery( { programId: award?.programId ?? '', perPage: 200 }, { enabled: !!award?.programId && addProjectDialogOpen } @@ -486,6 +497,15 @@ export default function AwardDetailPage({ }, onError: () => toast.error('Failed to update chair status'), }) + const bulkAddJurors = trpc.specialAward.bulkAddJurors.useMutation({ + onSuccess: (data) => { + utils.specialAward.listJurors.invalidate({ awardId }) + toast.success(`${data.added} juror(s) added from jury group`) + setSelectedGroupId('') + setSelectedGroupMembers(new Set()) + }, + onError: (err) => toast.error(err.message), + }) const bulkInvite = trpc.specialAward.bulkInviteJurors.useMutation({ onSuccess: (data) => { utils.specialAward.listJurors.invalidate({ awardId }) @@ -1337,6 +1357,137 @@ export default function AwardDetailPage({ + {/* Import from Jury Group */} + {juryGroups && juryGroups.length > 0 && ( + <> + +
+

+ + Import from Jury Group +

+

+ Select a jury group and pick which members to add as jurors for this award. +

+ + + {selectedGroupDetail && selectedGroupDetail.members.length > 0 && (() => { + const jurorUserIds = new Set(jurors?.map((j) => j.userId) || []) + const addableMembers = selectedGroupDetail.members.filter( + (m) => !jurorUserIds.has(m.user.id) + ) + const alreadyAdded = selectedGroupDetail.members.filter( + (m) => jurorUserIds.has(m.user.id) + ) + + return ( + + +
+ + {addableMembers.length} available to add + {alreadyAdded.length > 0 && ` ยท ${alreadyAdded.length} already assigned`} + + {addableMembers.length > 0 && ( + + )} +
+
+ + {addableMembers.map((m) => ( + + ))} + {alreadyAdded.map((m) => ( +
+ +
+

{m.user.name || 'Unnamed'}

+

{m.user.email}

+
+ + Already added + +
+ ))} + {addableMembers.length > 0 && ( + + )} +
+
+ ) + })()} +
+ + )} +

Invite New Jurors by Email