Add jury assignment transfer, cap redistribution, and learning hub overhaul
All checks were successful
Build and Push Docker Image / build (push) Successful in 12m19s
All checks were successful
Build and Push Docker Image / build (push) Successful in 12m19s
- Add getTransferCandidates/transferAssignments procedures for targeted assignment moves between jurors with TOCTOU guards and audit logging - Add getOverCapPreview/redistributeOverCap for auto-redistributing assignments when a juror's cap is lowered below their current load - Add TransferAssignmentsDialog (2-step: select projects, pick destinations) - Extend InlineMemberCap with over-cap detection and redistribute banner - Extend getReassignmentHistory to show ASSIGNMENT_TRANSFER and CAP_REDISTRIBUTE events - Learning hub: replace ResourceType/CohortLevel enums with accessJson JSONB, add coverImageKey, resource detail pages for jury/mentor, shared renderer - Migration: 20260221200000_learning_hub_overhaul Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useCreateBlockNote } from '@blocknote/react'
|
||||
import { BlockNoteView } from '@blocknote/mantine'
|
||||
import { BlockNoteView } from '@blocknote/shadcn'
|
||||
import '@blocknote/core/fonts/inter.css'
|
||||
import '@blocknote/mantine/style.css'
|
||||
import '@blocknote/shadcn/style.css'
|
||||
|
||||
import type { PartialBlock } from '@blocknote/core'
|
||||
|
||||
|
||||
71
src/components/shared/resource-renderer.tsx
Normal file
71
src/components/shared/resource-renderer.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
'use client'
|
||||
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const BlockViewer = dynamic(
|
||||
() => import('@/components/shared/block-editor').then((mod) => mod.BlockViewer),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="min-h-[200px] rounded-lg border bg-muted/20 animate-pulse" />
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
interface ResourceRendererProps {
|
||||
title: string
|
||||
description?: string | null
|
||||
contentJson: unknown // BlockNote PartialBlock[] stored as JSON
|
||||
coverImageUrl?: string | null
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ResourceRenderer({
|
||||
title,
|
||||
description,
|
||||
contentJson,
|
||||
coverImageUrl,
|
||||
className,
|
||||
}: ResourceRendererProps) {
|
||||
const contentString =
|
||||
typeof contentJson === 'string' ? contentJson : JSON.stringify(contentJson)
|
||||
|
||||
return (
|
||||
<article className={`mx-auto max-w-3xl ${className ?? ''}`}>
|
||||
{/* Cover image */}
|
||||
{coverImageUrl && (
|
||||
<div className="mb-8 overflow-hidden rounded-lg">
|
||||
<img
|
||||
src={coverImageUrl}
|
||||
alt=""
|
||||
className="h-auto w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Title */}
|
||||
<h1 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
{/* Description */}
|
||||
{description && (
|
||||
<p className="mt-3 text-lg text-muted-foreground leading-relaxed">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Divider */}
|
||||
<hr className="my-6 border-border" />
|
||||
|
||||
{/* Content */}
|
||||
{contentJson ? (
|
||||
<div className="prose-renderer">
|
||||
<BlockViewer content={contentString} />
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted-foreground italic">No content</p>
|
||||
)}
|
||||
</article>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user