feat: observer team tab, admin-controlled applicant feedback visibility
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m13s
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m13s
- Add Team tab to observer project detail (configurable via admin settings) - Move applicant jury feedback visibility from per-round config to admin settings - Add per-round-type controls: evaluation, live final, deliberation - Support anonymous LiveVote and DeliberationVote display for applicants - Add fine-grained toggles: scores, criteria, written feedback, hide from rejected - Backwards compatible: falls back to old per-round config if admin settings not set - New admin settings section under Analytics tab with all visibility controls - Seed new SystemSettings keys for observer/applicant visibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,7 @@ export function ObserverProjectDetail({ projectId }: { projectId: string }) {
|
||||
{ id: projectId },
|
||||
{ refetchInterval: 30_000 },
|
||||
)
|
||||
const { data: flags } = trpc.settings.getFeatureFlags.useQuery()
|
||||
|
||||
const roundId = data?.assignments?.[0]?.roundId as string | undefined
|
||||
const { data: activeForm } = trpc.evaluation.getStageForm.useQuery(
|
||||
@@ -242,6 +243,14 @@ export function ObserverProjectDetail({ projectId }: { projectId: string }) {
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="files">Files</TabsTrigger>
|
||||
{flags?.observerShowTeamTab && project.teamMembers.length > 0 && (
|
||||
<TabsTrigger value="team">
|
||||
Team
|
||||
<Badge variant="secondary" className="ml-1.5 h-4 px-1 text-xs">
|
||||
{project.teamMembers.length}
|
||||
</Badge>
|
||||
</TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
|
||||
{/* ── Overview Tab ── */}
|
||||
@@ -854,6 +863,48 @@ export function ObserverProjectDetail({ projectId }: { projectId: string }) {
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* ── Team Tab ── */}
|
||||
{flags?.observerShowTeamTab && project.teamMembers.length > 0 && (
|
||||
<TabsContent value="team" className="mt-6">
|
||||
<AnimatedCard index={0}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2.5 text-lg">
|
||||
<div className="rounded-lg bg-indigo-500/10 p-1.5">
|
||||
<Users className="h-4 w-4 text-indigo-500" />
|
||||
</div>
|
||||
Team Members
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{project.teamMembers.map((member) => (
|
||||
<div key={member.userId} className="flex items-center gap-3 rounded-lg border p-3">
|
||||
<UserAvatar
|
||||
user={member.user}
|
||||
avatarUrl={(member.user as { avatarUrl?: string | null }).avatarUrl}
|
||||
size="md"
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-semibold truncate">
|
||||
{member.user.name || 'Unnamed'}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{member.user.email}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline" className="shrink-0 text-xs">
|
||||
{member.role === 'LEAD' ? 'Lead' : member.role === 'ADVISOR' ? 'Advisor' : 'Member'}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</AnimatedCard>
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
{/* ── Files Tab ── */}
|
||||
<TabsContent value="files" className="mt-6">
|
||||
<Card>
|
||||
|
||||
Reference in New Issue
Block a user