Observer platform: mobile fixes, data/UX overhaul, animated nav
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m41s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m41s
- Fix dashboard default round selection to target active round instead of R1 - Move edition selector from dashboard header to hamburger menu via shared context - Add observer-friendly status labels (Not Reviewed / Under Review / Reviewed) - Fix pipeline completion: closed rounds show 100%, cap all rates at 100% - Round badge on projects list shows furthest round reached - Hide scores/evals for projects with zero evaluations - Enhance project detail round history with pass/reject indicators from ProjectRoundState - Remove irrelevant fields (Org Type, Budget, Duration) from project detail - Clickable juror workload with expandable project assignments - Humanize activity feed with icons and readable messages - Fix jurors table: responsive card layout on mobile - Fix criteria chart: horizontal bars for readable labels on mobile - Animate hamburger menu open/close with CSS grid transition Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,13 +41,15 @@ type RoleNavProps = {
|
||||
basePath: string
|
||||
/** Optional status badge displayed next to the logo (e.g., remaining evaluations count) */
|
||||
statusBadge?: React.ReactNode
|
||||
/** Optional slot rendered in the mobile hamburger menu (between nav links and sign out) and desktop header */
|
||||
editionSelector?: React.ReactNode
|
||||
}
|
||||
|
||||
function isNavItemActive(pathname: string, href: string, basePath: string): boolean {
|
||||
return pathname === href || (href !== basePath && pathname.startsWith(href))
|
||||
}
|
||||
|
||||
export function RoleNav({ navigation, roleName, user, basePath, statusBadge }: RoleNavProps) {
|
||||
export function RoleNav({ navigation, roleName, user, basePath, statusBadge, editionSelector }: RoleNavProps) {
|
||||
const pathname = usePathname()
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
||||
const { status: sessionStatus } = useSession()
|
||||
@@ -93,6 +95,7 @@ export function RoleNav({ navigation, roleName, user, basePath, statusBadge }: R
|
||||
|
||||
{/* User menu & mobile toggle */}
|
||||
<div className="flex items-center gap-2">
|
||||
{editionSelector && <div className="hidden md:block">{editionSelector}</div>}
|
||||
{mounted && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -161,42 +164,54 @@ export function RoleNav({ navigation, roleName, user, basePath, statusBadge }: R
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile menu */}
|
||||
{isMobileMenuOpen && (
|
||||
<div className="border-t md:hidden">
|
||||
<nav className="container-app py-4 space-y-1">
|
||||
{navigation.map((item) => {
|
||||
const isActive = isNavItemActive(pathname, item.href, basePath)
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href as Route}
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className={cn(
|
||||
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive
|
||||
? 'bg-primary/10 text-primary'
|
||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
||||
)}
|
||||
{/* Mobile menu — animated with CSS grid */}
|
||||
<div
|
||||
className={cn(
|
||||
'grid md:hidden transition-[grid-template-rows] duration-200 ease-out',
|
||||
isMobileMenuOpen ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
|
||||
)}
|
||||
>
|
||||
<div className="overflow-hidden">
|
||||
<div className={cn('border-t', !isMobileMenuOpen && 'border-transparent')}>
|
||||
<nav className="container-app py-4 space-y-1">
|
||||
{navigation.map((item) => {
|
||||
const isActive = isNavItemActive(pathname, item.href, basePath)
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href as Route}
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className={cn(
|
||||
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive
|
||||
? 'bg-primary/10 text-primary'
|
||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
{item.name}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
{editionSelector && (
|
||||
<div className="border-t pt-4 mt-4 px-3">
|
||||
{editionSelector}
|
||||
</div>
|
||||
)}
|
||||
<div className="border-t pt-4 mt-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full justify-start text-destructive hover:text-destructive"
|
||||
onClick={() => signOut({ callbackUrl: '/login' })}
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
{item.name}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
<div className="border-t pt-4 mt-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full justify-start text-destructive hover:text-destructive"
|
||||
onClick={() => signOut({ callbackUrl: '/login' })}
|
||||
>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Sign Out
|
||||
</Button>
|
||||
</div>
|
||||
</nav>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Sign Out
|
||||
</Button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user