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>
2026-02-02 16:58:29 +01:00
|
|
|
'use client'
|
2026-01-30 13:41:32 +01:00
|
|
|
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
import { useState, useEffect, useCallback } from 'react'
|
|
|
|
|
import Link from 'next/link'
|
|
|
|
|
import { useSearchParams, usePathname } from 'next/navigation'
|
|
|
|
|
import { trpc } from '@/lib/trpc/client'
|
2026-02-03 23:19:45 +01:00
|
|
|
import { toast } from 'sonner'
|
2026-01-30 13:41:32 +01:00
|
|
|
import {
|
|
|
|
|
Card,
|
|
|
|
|
CardContent,
|
|
|
|
|
CardDescription,
|
|
|
|
|
CardHeader,
|
|
|
|
|
CardTitle,
|
|
|
|
|
} from '@/components/ui/card'
|
|
|
|
|
import { Badge } from '@/components/ui/badge'
|
|
|
|
|
import { Button } from '@/components/ui/button'
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
import { Input } from '@/components/ui/input'
|
2026-01-30 13:41:32 +01:00
|
|
|
import { Skeleton } from '@/components/ui/skeleton'
|
2026-02-05 10:27:52 +01:00
|
|
|
import { Progress } from '@/components/ui/progress'
|
2026-01-30 13:41:32 +01:00
|
|
|
import {
|
|
|
|
|
Table,
|
|
|
|
|
TableBody,
|
|
|
|
|
TableCell,
|
|
|
|
|
TableHead,
|
|
|
|
|
TableHeader,
|
|
|
|
|
TableRow,
|
|
|
|
|
} from '@/components/ui/table'
|
|
|
|
|
import {
|
|
|
|
|
DropdownMenu,
|
|
|
|
|
DropdownMenuContent,
|
|
|
|
|
DropdownMenuItem,
|
2026-02-03 23:19:45 +01:00
|
|
|
DropdownMenuSeparator,
|
2026-01-30 13:41:32 +01:00
|
|
|
DropdownMenuTrigger,
|
|
|
|
|
} from '@/components/ui/dropdown-menu'
|
2026-02-03 23:19:45 +01:00
|
|
|
import {
|
|
|
|
|
AlertDialog,
|
|
|
|
|
AlertDialogAction,
|
|
|
|
|
AlertDialogCancel,
|
|
|
|
|
AlertDialogContent,
|
|
|
|
|
AlertDialogDescription,
|
|
|
|
|
AlertDialogFooter,
|
|
|
|
|
AlertDialogHeader,
|
|
|
|
|
AlertDialogTitle,
|
|
|
|
|
} from '@/components/ui/alert-dialog'
|
2026-02-05 10:27:52 +01:00
|
|
|
import {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
} from '@/components/ui/dialog'
|
2026-01-30 13:41:32 +01:00
|
|
|
import {
|
|
|
|
|
Plus,
|
|
|
|
|
MoreHorizontal,
|
|
|
|
|
ClipboardList,
|
|
|
|
|
Eye,
|
|
|
|
|
Pencil,
|
|
|
|
|
FileUp,
|
|
|
|
|
Users,
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
Search,
|
2026-02-03 23:19:45 +01:00
|
|
|
Trash2,
|
|
|
|
|
Loader2,
|
2026-02-04 15:08:46 +01:00
|
|
|
Sparkles,
|
2026-02-05 10:27:52 +01:00
|
|
|
Tags,
|
|
|
|
|
Clock,
|
|
|
|
|
CheckCircle2,
|
|
|
|
|
AlertCircle,
|
|
|
|
|
Layers,
|
|
|
|
|
FolderOpen,
|
2026-01-30 13:41:32 +01:00
|
|
|
} from 'lucide-react'
|
2026-02-04 15:08:46 +01:00
|
|
|
import {
|
|
|
|
|
Select,
|
|
|
|
|
SelectContent,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue,
|
|
|
|
|
} from '@/components/ui/select'
|
|
|
|
|
import { Label } from '@/components/ui/label'
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
import { truncate } from '@/lib/utils'
|
2026-01-30 13:41:32 +01:00
|
|
|
import { ProjectLogo } from '@/components/shared/project-logo'
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
import { Pagination } from '@/components/shared/pagination'
|
|
|
|
|
import {
|
|
|
|
|
ProjectFiltersBar,
|
|
|
|
|
type ProjectFilters,
|
|
|
|
|
} from './project-filters'
|
2026-01-30 13:41:32 +01:00
|
|
|
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
const statusColors: Record<
|
|
|
|
|
string,
|
|
|
|
|
'default' | 'success' | 'secondary' | 'destructive' | 'warning'
|
|
|
|
|
> = {
|
|
|
|
|
SUBMITTED: 'secondary',
|
|
|
|
|
ELIGIBLE: 'default',
|
|
|
|
|
ASSIGNED: 'default',
|
|
|
|
|
SEMIFINALIST: 'success',
|
|
|
|
|
FINALIST: 'success',
|
|
|
|
|
WINNER: 'success',
|
|
|
|
|
REJECTED: 'destructive',
|
|
|
|
|
WITHDRAWN: 'secondary',
|
|
|
|
|
}
|
2026-01-30 13:41:32 +01:00
|
|
|
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
function parseFiltersFromParams(
|
|
|
|
|
searchParams: URLSearchParams
|
|
|
|
|
): ProjectFilters & { page: number } {
|
|
|
|
|
return {
|
|
|
|
|
search: searchParams.get('q') || '',
|
|
|
|
|
statuses: searchParams.get('status')
|
|
|
|
|
? searchParams.get('status')!.split(',')
|
|
|
|
|
: [],
|
|
|
|
|
roundId: searchParams.get('round') || '',
|
|
|
|
|
competitionCategory: searchParams.get('category') || '',
|
|
|
|
|
oceanIssue: searchParams.get('issue') || '',
|
|
|
|
|
country: searchParams.get('country') || '',
|
|
|
|
|
wantsMentorship:
|
|
|
|
|
searchParams.get('mentorship') === 'true'
|
|
|
|
|
? true
|
|
|
|
|
: searchParams.get('mentorship') === 'false'
|
|
|
|
|
? false
|
|
|
|
|
: undefined,
|
|
|
|
|
hasFiles:
|
|
|
|
|
searchParams.get('hasFiles') === 'true'
|
|
|
|
|
? true
|
|
|
|
|
: searchParams.get('hasFiles') === 'false'
|
|
|
|
|
? false
|
|
|
|
|
: undefined,
|
|
|
|
|
hasAssignments:
|
|
|
|
|
searchParams.get('hasAssign') === 'true'
|
|
|
|
|
? true
|
|
|
|
|
: searchParams.get('hasAssign') === 'false'
|
|
|
|
|
? false
|
|
|
|
|
: undefined,
|
|
|
|
|
page: parseInt(searchParams.get('page') || '1', 10),
|
2026-01-30 13:41:32 +01:00
|
|
|
}
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
}
|
2026-01-30 13:41:32 +01:00
|
|
|
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
function filtersToParams(
|
|
|
|
|
filters: ProjectFilters & { page: number }
|
|
|
|
|
): URLSearchParams {
|
|
|
|
|
const params = new URLSearchParams()
|
|
|
|
|
if (filters.search) params.set('q', filters.search)
|
|
|
|
|
if (filters.statuses.length > 0)
|
|
|
|
|
params.set('status', filters.statuses.join(','))
|
|
|
|
|
if (filters.roundId) params.set('round', filters.roundId)
|
|
|
|
|
if (filters.competitionCategory)
|
|
|
|
|
params.set('category', filters.competitionCategory)
|
|
|
|
|
if (filters.oceanIssue) params.set('issue', filters.oceanIssue)
|
|
|
|
|
if (filters.country) params.set('country', filters.country)
|
|
|
|
|
if (filters.wantsMentorship !== undefined)
|
|
|
|
|
params.set('mentorship', String(filters.wantsMentorship))
|
|
|
|
|
if (filters.hasFiles !== undefined)
|
|
|
|
|
params.set('hasFiles', String(filters.hasFiles))
|
|
|
|
|
if (filters.hasAssignments !== undefined)
|
|
|
|
|
params.set('hasAssign', String(filters.hasAssignments))
|
|
|
|
|
if (filters.page > 1) params.set('page', String(filters.page))
|
|
|
|
|
return params
|
|
|
|
|
}
|
2026-01-30 13:41:32 +01:00
|
|
|
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
const PER_PAGE = 20
|
2026-01-30 13:41:32 +01:00
|
|
|
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
export default function ProjectsPage() {
|
2026-01-30 13:41:32 +01:00
|
|
|
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
const pathname = usePathname()
|
|
|
|
|
const searchParams = useSearchParams()
|
|
|
|
|
|
|
|
|
|
const parsed = parseFiltersFromParams(searchParams)
|
|
|
|
|
const [filters, setFilters] = useState<ProjectFilters>({
|
|
|
|
|
search: parsed.search,
|
|
|
|
|
statuses: parsed.statuses,
|
|
|
|
|
roundId: parsed.roundId,
|
|
|
|
|
competitionCategory: parsed.competitionCategory,
|
|
|
|
|
oceanIssue: parsed.oceanIssue,
|
|
|
|
|
country: parsed.country,
|
|
|
|
|
wantsMentorship: parsed.wantsMentorship,
|
|
|
|
|
hasFiles: parsed.hasFiles,
|
|
|
|
|
hasAssignments: parsed.hasAssignments,
|
|
|
|
|
})
|
|
|
|
|
const [page, setPage] = useState(parsed.page)
|
|
|
|
|
const [searchInput, setSearchInput] = useState(parsed.search)
|
|
|
|
|
|
|
|
|
|
// Debounced search
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const timer = setTimeout(() => {
|
|
|
|
|
if (searchInput !== filters.search) {
|
|
|
|
|
setFilters((f) => ({ ...f, search: searchInput }))
|
|
|
|
|
setPage(1)
|
|
|
|
|
}
|
|
|
|
|
}, 300)
|
|
|
|
|
return () => clearTimeout(timer)
|
|
|
|
|
}, [searchInput, filters.search])
|
|
|
|
|
|
|
|
|
|
// Sync URL
|
|
|
|
|
const syncUrl = useCallback(
|
|
|
|
|
(f: ProjectFilters, p: number) => {
|
|
|
|
|
const params = filtersToParams({ ...f, page: p })
|
|
|
|
|
const qs = params.toString()
|
|
|
|
|
window.history.replaceState(null, '', qs ? `${pathname}?${qs}` : pathname)
|
|
|
|
|
},
|
|
|
|
|
[pathname]
|
2026-01-30 13:41:32 +01:00
|
|
|
)
|
|
|
|
|
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
useEffect(() => {
|
|
|
|
|
syncUrl(filters, page)
|
|
|
|
|
}, [filters, page, syncUrl])
|
|
|
|
|
|
|
|
|
|
// Reset page when filters change
|
|
|
|
|
const handleFiltersChange = (newFilters: ProjectFilters) => {
|
|
|
|
|
setFilters(newFilters)
|
|
|
|
|
setPage(1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build tRPC query input
|
|
|
|
|
const queryInput = {
|
|
|
|
|
search: filters.search || undefined,
|
|
|
|
|
statuses:
|
|
|
|
|
filters.statuses.length > 0
|
|
|
|
|
? (filters.statuses as Array<
|
|
|
|
|
| 'SUBMITTED'
|
|
|
|
|
| 'ELIGIBLE'
|
|
|
|
|
| 'ASSIGNED'
|
|
|
|
|
| 'SEMIFINALIST'
|
|
|
|
|
| 'FINALIST'
|
|
|
|
|
| 'REJECTED'
|
|
|
|
|
>)
|
|
|
|
|
: undefined,
|
|
|
|
|
roundId: filters.roundId || undefined,
|
|
|
|
|
competitionCategory:
|
|
|
|
|
(filters.competitionCategory as 'STARTUP' | 'BUSINESS_CONCEPT') ||
|
|
|
|
|
undefined,
|
|
|
|
|
oceanIssue: filters.oceanIssue
|
|
|
|
|
? (filters.oceanIssue as
|
|
|
|
|
| 'POLLUTION_REDUCTION'
|
|
|
|
|
| 'CLIMATE_MITIGATION'
|
|
|
|
|
| 'TECHNOLOGY_INNOVATION'
|
|
|
|
|
| 'SUSTAINABLE_SHIPPING'
|
|
|
|
|
| 'BLUE_CARBON'
|
|
|
|
|
| 'HABITAT_RESTORATION'
|
|
|
|
|
| 'COMMUNITY_CAPACITY'
|
|
|
|
|
| 'SUSTAINABLE_FISHING'
|
|
|
|
|
| 'CONSUMER_AWARENESS'
|
|
|
|
|
| 'OCEAN_ACIDIFICATION'
|
|
|
|
|
| 'OTHER')
|
|
|
|
|
: undefined,
|
|
|
|
|
country: filters.country || undefined,
|
|
|
|
|
wantsMentorship: filters.wantsMentorship,
|
|
|
|
|
hasFiles: filters.hasFiles,
|
|
|
|
|
hasAssignments: filters.hasAssignments,
|
|
|
|
|
page,
|
|
|
|
|
perPage: PER_PAGE,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 23:19:45 +01:00
|
|
|
const utils = trpc.useUtils()
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
const { data, isLoading } = trpc.project.list.useQuery(queryInput)
|
|
|
|
|
const { data: filterOptions } = trpc.project.getFilterOptions.useQuery()
|
|
|
|
|
|
2026-02-03 23:19:45 +01:00
|
|
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
|
|
|
|
const [projectToDelete, setProjectToDelete] = useState<{ id: string; title: string } | null>(null)
|
2026-02-04 15:08:46 +01:00
|
|
|
const [aiTagDialogOpen, setAiTagDialogOpen] = useState(false)
|
2026-02-05 10:27:52 +01:00
|
|
|
const [taggingScope, setTaggingScope] = useState<'round' | 'program'>('round')
|
2026-02-04 15:08:46 +01:00
|
|
|
const [selectedRoundForTagging, setSelectedRoundForTagging] = useState<string>('')
|
2026-02-05 10:27:52 +01:00
|
|
|
const [selectedProgramForTagging, setSelectedProgramForTagging] = useState<string>('')
|
2026-02-05 11:48:57 +01:00
|
|
|
const [activeTaggingJobId, setActiveTaggingJobId] = useState<string | null>(null)
|
2026-02-04 15:08:46 +01:00
|
|
|
|
2026-02-05 10:27:52 +01:00
|
|
|
// Fetch programs and rounds for the AI tagging dialog
|
2026-02-04 15:08:46 +01:00
|
|
|
const { data: programs } = trpc.program.list.useQuery()
|
|
|
|
|
|
2026-02-05 11:48:57 +01:00
|
|
|
// Start tagging job mutation
|
|
|
|
|
const startTaggingJob = trpc.tag.startTaggingJob.useMutation({
|
2026-02-04 15:08:46 +01:00
|
|
|
onSuccess: (result) => {
|
2026-02-05 11:48:57 +01:00
|
|
|
setActiveTaggingJobId(result.jobId)
|
|
|
|
|
toast.info('AI tagging job started. Progress will update automatically.')
|
2026-02-05 10:27:52 +01:00
|
|
|
},
|
|
|
|
|
onError: (error) => {
|
2026-02-05 11:48:57 +01:00
|
|
|
toast.error(error.message || 'Failed to start tagging job')
|
2026-02-05 10:27:52 +01:00
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-05 11:48:57 +01:00
|
|
|
// Poll for job status when job is active
|
|
|
|
|
const { data: jobStatus } = trpc.tag.getTaggingJobStatus.useQuery(
|
|
|
|
|
{ jobId: activeTaggingJobId! },
|
|
|
|
|
{
|
|
|
|
|
enabled: !!activeTaggingJobId,
|
|
|
|
|
refetchInterval: (query) => {
|
|
|
|
|
const status = query.state.data?.status
|
|
|
|
|
// Stop polling when job is done
|
|
|
|
|
if (status === 'COMPLETED' || status === 'FAILED') {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return 1500 // Poll every 1.5 seconds
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Handle job completion
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (jobStatus?.status === 'COMPLETED') {
|
|
|
|
|
toast.success(
|
|
|
|
|
`AI Tagging complete: ${jobStatus.taggedCount} tagged, ${jobStatus.skippedCount} already tagged, ${jobStatus.failedCount} failed`
|
|
|
|
|
)
|
2026-02-04 15:08:46 +01:00
|
|
|
utils.project.list.invalidate()
|
2026-02-05 11:48:57 +01:00
|
|
|
} else if (jobStatus?.status === 'FAILED') {
|
|
|
|
|
toast.error(`AI Tagging failed: ${jobStatus.errorMessage || 'Unknown error'}`)
|
|
|
|
|
}
|
|
|
|
|
}, [jobStatus?.status, jobStatus?.taggedCount, jobStatus?.skippedCount, jobStatus?.failedCount, jobStatus?.errorMessage, utils.project.list])
|
|
|
|
|
|
|
|
|
|
const taggingInProgress = startTaggingJob.isPending ||
|
|
|
|
|
(jobStatus?.status === 'PENDING' || jobStatus?.status === 'RUNNING')
|
|
|
|
|
|
|
|
|
|
const taggingResult = jobStatus?.status === 'COMPLETED' || jobStatus?.status === 'FAILED'
|
|
|
|
|
? {
|
|
|
|
|
processed: jobStatus.taggedCount,
|
|
|
|
|
skipped: jobStatus.skippedCount,
|
|
|
|
|
failed: jobStatus.failedCount,
|
|
|
|
|
errors: jobStatus.errors || [],
|
|
|
|
|
status: jobStatus.status,
|
|
|
|
|
}
|
|
|
|
|
: null
|
2026-02-03 23:19:45 +01:00
|
|
|
|
2026-02-05 10:27:52 +01:00
|
|
|
const handleStartTagging = () => {
|
|
|
|
|
if (taggingScope === 'round' && selectedRoundForTagging) {
|
2026-02-05 11:48:57 +01:00
|
|
|
startTaggingJob.mutate({ roundId: selectedRoundForTagging })
|
2026-02-05 10:27:52 +01:00
|
|
|
} else if (taggingScope === 'program' && selectedProgramForTagging) {
|
2026-02-05 11:48:57 +01:00
|
|
|
startTaggingJob.mutate({ programId: selectedProgramForTagging })
|
2026-02-05 10:27:52 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleCloseTaggingDialog = () => {
|
|
|
|
|
if (!taggingInProgress) {
|
|
|
|
|
setAiTagDialogOpen(false)
|
2026-02-05 11:48:57 +01:00
|
|
|
setActiveTaggingJobId(null)
|
2026-02-05 10:27:52 +01:00
|
|
|
setSelectedRoundForTagging('')
|
|
|
|
|
setSelectedProgramForTagging('')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get selected program's rounds
|
|
|
|
|
const selectedProgram = programs?.find(p => p.id === selectedProgramForTagging)
|
|
|
|
|
const programRounds = filterOptions?.rounds?.filter(r => r.program?.id === selectedProgramForTagging) ?? []
|
|
|
|
|
|
|
|
|
|
// Calculate stats for display
|
|
|
|
|
const selectedRound = filterOptions?.rounds?.find(r => r.id === selectedRoundForTagging)
|
|
|
|
|
const displayProgram = taggingScope === 'program'
|
|
|
|
|
? selectedProgram
|
|
|
|
|
: (selectedRound ? programs?.find(p => p.id === selectedRound.program?.id) : null)
|
|
|
|
|
|
2026-02-05 11:48:57 +01:00
|
|
|
// Calculate progress percentage
|
|
|
|
|
const taggingProgressPercent = jobStatus && jobStatus.totalProjects > 0
|
|
|
|
|
? Math.round((jobStatus.processedCount / jobStatus.totalProjects) * 100)
|
|
|
|
|
: 0
|
|
|
|
|
|
2026-02-03 23:19:45 +01:00
|
|
|
const deleteProject = trpc.project.delete.useMutation({
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
toast.success('Project deleted successfully')
|
|
|
|
|
utils.project.list.invalidate()
|
|
|
|
|
setDeleteDialogOpen(false)
|
|
|
|
|
setProjectToDelete(null)
|
|
|
|
|
},
|
|
|
|
|
onError: (error) => {
|
|
|
|
|
toast.error(error.message || 'Failed to delete project')
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const handleDeleteClick = (project: { id: string; title: string }) => {
|
|
|
|
|
setProjectToDelete(project)
|
|
|
|
|
setDeleteDialogOpen(true)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 13:41:32 +01:00
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
{/* Header */}
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<h1 className="text-2xl font-semibold tracking-tight">Projects</h1>
|
|
|
|
|
<p className="text-muted-foreground">
|
|
|
|
|
Manage submitted projects across all rounds
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex gap-2">
|
2026-02-04 15:08:46 +01:00
|
|
|
<Button variant="outline" onClick={() => setAiTagDialogOpen(true)}>
|
|
|
|
|
<Sparkles className="mr-2 h-4 w-4" />
|
|
|
|
|
AI Tags
|
|
|
|
|
</Button>
|
2026-01-30 13:41:32 +01:00
|
|
|
<Button variant="outline" asChild>
|
|
|
|
|
<Link href="/admin/projects/import">
|
|
|
|
|
<FileUp className="mr-2 h-4 w-4" />
|
|
|
|
|
Import
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
<Button asChild>
|
|
|
|
|
<Link href="/admin/projects/new">
|
|
|
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
|
|
|
Add Project
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
{/* Search */}
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
|
|
|
<Input
|
|
|
|
|
value={searchInput}
|
|
|
|
|
onChange={(e) => setSearchInput(e.target.value)}
|
|
|
|
|
placeholder="Search projects by title, team, or description..."
|
|
|
|
|
className="pl-10"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Filters */}
|
|
|
|
|
<ProjectFiltersBar
|
|
|
|
|
filters={filters}
|
|
|
|
|
filterOptions={filterOptions}
|
|
|
|
|
onChange={handleFiltersChange}
|
|
|
|
|
/>
|
|
|
|
|
|
2026-01-30 13:41:32 +01:00
|
|
|
{/* Content */}
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
{isLoading ? (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardContent className="p-6">
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
{[...Array(5)].map((_, i) => (
|
|
|
|
|
<div key={i} className="flex items-center justify-between">
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Skeleton className="h-5 w-64" />
|
|
|
|
|
<Skeleton className="h-4 w-32" />
|
|
|
|
|
</div>
|
|
|
|
|
<Skeleton className="h-9 w-9" />
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
) : data && data.projects.length === 0 ? (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
|
|
|
|
<ClipboardList className="h-12 w-12 text-muted-foreground/50" />
|
|
|
|
|
<p className="mt-2 font-medium">No projects found</p>
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
{filters.search ||
|
|
|
|
|
filters.statuses.length > 0 ||
|
|
|
|
|
filters.roundId ||
|
|
|
|
|
filters.competitionCategory ||
|
|
|
|
|
filters.oceanIssue ||
|
|
|
|
|
filters.country
|
|
|
|
|
? 'Try adjusting your filters'
|
|
|
|
|
: 'Import projects via CSV or create them manually'}
|
|
|
|
|
</p>
|
|
|
|
|
{!filters.search && filters.statuses.length === 0 && (
|
|
|
|
|
<div className="mt-4 flex gap-2">
|
|
|
|
|
<Button asChild>
|
|
|
|
|
<Link href="/admin/projects/import">
|
|
|
|
|
<FileUp className="mr-2 h-4 w-4" />
|
|
|
|
|
Import CSV
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
<Button variant="outline" asChild>
|
|
|
|
|
<Link href="/admin/projects/new">
|
|
|
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
|
|
|
Add Project
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
) : data ? (
|
|
|
|
|
<>
|
|
|
|
|
{/* Desktop table */}
|
|
|
|
|
<Card className="hidden md:block">
|
|
|
|
|
<Table>
|
|
|
|
|
<TableHeader>
|
|
|
|
|
<TableRow>
|
|
|
|
|
<TableHead>Project</TableHead>
|
|
|
|
|
<TableHead>Round</TableHead>
|
|
|
|
|
<TableHead>Files</TableHead>
|
|
|
|
|
<TableHead>Assignments</TableHead>
|
|
|
|
|
<TableHead>Status</TableHead>
|
|
|
|
|
<TableHead className="text-right">Actions</TableHead>
|
|
|
|
|
</TableRow>
|
|
|
|
|
</TableHeader>
|
|
|
|
|
<TableBody>
|
2026-02-04 09:49:31 +01:00
|
|
|
{data.projects.map((project) => {
|
2026-02-04 14:15:06 +01:00
|
|
|
const isEliminated = project.status === 'REJECTED'
|
2026-02-04 09:49:31 +01:00
|
|
|
return (
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
<TableRow
|
|
|
|
|
key={project.id}
|
2026-02-04 09:49:31 +01:00
|
|
|
className={`group relative cursor-pointer hover:bg-muted/50 ${isEliminated ? 'opacity-60 bg-destructive/5' : ''}`}
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
>
|
|
|
|
|
<TableCell>
|
|
|
|
|
<Link
|
|
|
|
|
href={`/admin/projects/${project.id}`}
|
|
|
|
|
className="flex items-center gap-3 after:absolute after:inset-0 after:content-['']"
|
|
|
|
|
>
|
|
|
|
|
<ProjectLogo
|
|
|
|
|
project={project}
|
|
|
|
|
size="sm"
|
|
|
|
|
fallback="initials"
|
|
|
|
|
/>
|
|
|
|
|
<div>
|
|
|
|
|
<p className="font-medium hover:text-primary">
|
|
|
|
|
{truncate(project.title, 40)}
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
{project.teamName}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</Link>
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell>
|
|
|
|
|
<div>
|
2026-02-04 09:49:31 +01:00
|
|
|
<div className="flex items-center gap-2">
|
2026-02-04 14:15:06 +01:00
|
|
|
<p>{project.round?.name ?? '-'}</p>
|
|
|
|
|
{project.status === 'REJECTED' && (
|
2026-02-04 09:49:31 +01:00
|
|
|
<Badge variant="destructive" className="text-xs">
|
|
|
|
|
Eliminated
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
<p className="text-sm text-muted-foreground">
|
2026-02-04 14:15:06 +01:00
|
|
|
{project.round?.program?.name}
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell>{project.files?.length ?? 0}</TableCell>
|
|
|
|
|
<TableCell>
|
|
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
<Users className="h-4 w-4 text-muted-foreground" />
|
|
|
|
|
{project._count.assignments}
|
|
|
|
|
</div>
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell>
|
|
|
|
|
<Badge
|
2026-02-04 14:15:06 +01:00
|
|
|
variant={statusColors[project.status ?? 'SUBMITTED'] || 'secondary'}
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
>
|
2026-02-04 14:15:06 +01:00
|
|
|
{(project.status ?? 'SUBMITTED').replace('_', ' ')}
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
</Badge>
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell className="relative z-10 text-right">
|
|
|
|
|
<DropdownMenu>
|
|
|
|
|
<DropdownMenuTrigger asChild>
|
|
|
|
|
<Button variant="ghost" size="icon">
|
|
|
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
|
|
|
<span className="sr-only">Actions</span>
|
|
|
|
|
</Button>
|
|
|
|
|
</DropdownMenuTrigger>
|
|
|
|
|
<DropdownMenuContent align="end">
|
|
|
|
|
<DropdownMenuItem asChild>
|
|
|
|
|
<Link href={`/admin/projects/${project.id}`}>
|
|
|
|
|
<Eye className="mr-2 h-4 w-4" />
|
|
|
|
|
View Details
|
|
|
|
|
</Link>
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
<DropdownMenuItem asChild>
|
|
|
|
|
<Link href={`/admin/projects/${project.id}/edit`}>
|
|
|
|
|
<Pencil className="mr-2 h-4 w-4" />
|
|
|
|
|
Edit
|
|
|
|
|
</Link>
|
|
|
|
|
</DropdownMenuItem>
|
2026-02-03 23:19:45 +01:00
|
|
|
<DropdownMenuSeparator />
|
|
|
|
|
<DropdownMenuItem
|
|
|
|
|
className="text-destructive focus:text-destructive"
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
handleDeleteClick({ id: project.id, title: project.title })
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
|
|
|
Delete
|
|
|
|
|
</DropdownMenuItem>
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
</DropdownMenuContent>
|
|
|
|
|
</DropdownMenu>
|
|
|
|
|
</TableCell>
|
|
|
|
|
</TableRow>
|
2026-02-04 09:49:31 +01:00
|
|
|
)})}
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
</TableBody>
|
|
|
|
|
</Table>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
{/* Mobile card view */}
|
|
|
|
|
<div className="space-y-4 md:hidden">
|
|
|
|
|
{data.projects.map((project) => (
|
|
|
|
|
<Link
|
|
|
|
|
key={project.id}
|
|
|
|
|
href={`/admin/projects/${project.id}`}
|
|
|
|
|
className="block"
|
|
|
|
|
>
|
|
|
|
|
<Card className="transition-colors hover:bg-muted/50">
|
|
|
|
|
<CardHeader className="pb-3">
|
|
|
|
|
<div className="flex items-start gap-3">
|
|
|
|
|
<ProjectLogo
|
|
|
|
|
project={project}
|
|
|
|
|
size="md"
|
|
|
|
|
fallback="initials"
|
|
|
|
|
/>
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<div className="flex items-start justify-between gap-2">
|
|
|
|
|
<CardTitle className="text-base line-clamp-2">
|
|
|
|
|
{project.title}
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<Badge
|
|
|
|
|
variant={
|
2026-02-04 14:15:06 +01:00
|
|
|
statusColors[project.status ?? 'SUBMITTED'] || 'secondary'
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
}
|
|
|
|
|
className="shrink-0"
|
|
|
|
|
>
|
2026-02-04 14:15:06 +01:00
|
|
|
{(project.status ?? 'SUBMITTED').replace('_', ' ')}
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
<CardDescription>{project.teamName}</CardDescription>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-3">
|
|
|
|
|
<div className="flex items-center justify-between text-sm">
|
|
|
|
|
<span className="text-muted-foreground">Round</span>
|
2026-02-04 09:49:31 +01:00
|
|
|
<div className="flex items-center gap-2">
|
2026-02-04 14:15:06 +01:00
|
|
|
<span>{project.round?.name ?? '-'}</span>
|
|
|
|
|
{project.status === 'REJECTED' && (
|
2026-02-04 09:49:31 +01:00
|
|
|
<Badge variant="destructive" className="text-xs">
|
|
|
|
|
Eliminated
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
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>
2026-02-02 16:58:29 +01:00
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center justify-between text-sm">
|
|
|
|
|
<span className="text-muted-foreground">Assignments</span>
|
|
|
|
|
<span>{project._count.assignments} jurors</span>
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</Link>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Pagination */}
|
|
|
|
|
<Pagination
|
|
|
|
|
page={data.page}
|
|
|
|
|
totalPages={data.totalPages}
|
|
|
|
|
total={data.total}
|
|
|
|
|
perPage={PER_PAGE}
|
|
|
|
|
onPageChange={setPage}
|
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
) : null}
|
2026-02-03 23:19:45 +01:00
|
|
|
|
|
|
|
|
{/* Delete Confirmation Dialog */}
|
|
|
|
|
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
|
|
|
|
<AlertDialogContent>
|
|
|
|
|
<AlertDialogHeader>
|
|
|
|
|
<AlertDialogTitle>Delete Project</AlertDialogTitle>
|
|
|
|
|
<AlertDialogDescription>
|
|
|
|
|
Are you sure you want to delete "{projectToDelete?.title}"? This will
|
|
|
|
|
permanently remove the project, all its files, assignments, and evaluations.
|
|
|
|
|
This action cannot be undone.
|
|
|
|
|
</AlertDialogDescription>
|
|
|
|
|
</AlertDialogHeader>
|
|
|
|
|
<AlertDialogFooter>
|
|
|
|
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
|
|
|
<AlertDialogAction
|
|
|
|
|
onClick={() => projectToDelete && deleteProject.mutate({ id: projectToDelete.id })}
|
|
|
|
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
|
|
|
>
|
|
|
|
|
{deleteProject.isPending ? (
|
|
|
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
|
|
|
) : null}
|
|
|
|
|
Delete
|
|
|
|
|
</AlertDialogAction>
|
|
|
|
|
</AlertDialogFooter>
|
|
|
|
|
</AlertDialogContent>
|
|
|
|
|
</AlertDialog>
|
2026-02-04 15:08:46 +01:00
|
|
|
|
|
|
|
|
{/* AI Tagging Dialog */}
|
2026-02-05 10:27:52 +01:00
|
|
|
<Dialog open={aiTagDialogOpen} onOpenChange={handleCloseTaggingDialog}>
|
|
|
|
|
<DialogContent className="sm:max-w-lg">
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle className="flex items-center gap-2">
|
|
|
|
|
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-gradient-to-br from-amber-400 to-orange-500">
|
|
|
|
|
<Sparkles className="h-5 w-5 text-white" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<span>AI Tag Generator</span>
|
|
|
|
|
<p className="text-sm font-normal text-muted-foreground">
|
|
|
|
|
Automatically categorize projects with expertise tags
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</DialogTitle>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
|
|
|
|
|
<div className="space-y-6 py-4">
|
|
|
|
|
{/* Progress Indicator (when running) */}
|
|
|
|
|
{taggingInProgress && (
|
|
|
|
|
<div className="p-4 rounded-lg bg-blue-50 dark:bg-blue-950/20 border border-blue-200 dark:border-blue-900">
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<Loader2 className="h-5 w-5 animate-spin text-blue-600" />
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
<p className="font-medium text-blue-900 dark:text-blue-100">
|
|
|
|
|
AI Tagging in Progress
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-sm text-blue-700 dark:text-blue-300">
|
2026-02-05 11:48:57 +01:00
|
|
|
{jobStatus?.status === 'PENDING'
|
|
|
|
|
? 'Initializing...'
|
|
|
|
|
: `Processing ${jobStatus?.totalProjects || 0} projects with AI...`}
|
2026-02-05 10:27:52 +01:00
|
|
|
</p>
|
|
|
|
|
</div>
|
2026-02-05 11:48:57 +01:00
|
|
|
{jobStatus && jobStatus.totalProjects > 0 && (
|
|
|
|
|
<Badge variant="outline" className="border-blue-300 text-blue-700">
|
|
|
|
|
<Clock className="mr-1 h-3 w-3" />
|
|
|
|
|
{jobStatus.processedCount} / {jobStatus.totalProjects}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
2026-02-05 10:27:52 +01:00
|
|
|
</div>
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<div className="flex justify-between text-sm">
|
|
|
|
|
<span className="text-blue-700 dark:text-blue-300">
|
2026-02-05 11:48:57 +01:00
|
|
|
{jobStatus?.processedCount || 0} of {jobStatus?.totalProjects || '?'} projects processed
|
|
|
|
|
{jobStatus?.taggedCount ? ` (${jobStatus.taggedCount} tagged)` : ''}
|
2026-02-05 10:27:52 +01:00
|
|
|
</span>
|
2026-02-05 11:48:57 +01:00
|
|
|
{jobStatus && jobStatus.totalProjects > 0 && (
|
|
|
|
|
<span className="font-medium text-blue-900 dark:text-blue-100">
|
|
|
|
|
{taggingProgressPercent}%
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
2026-02-05 10:27:52 +01:00
|
|
|
</div>
|
2026-02-05 11:48:57 +01:00
|
|
|
<Progress
|
|
|
|
|
value={jobStatus?.totalProjects ? taggingProgressPercent : undefined}
|
|
|
|
|
className={`h-2 ${!jobStatus?.totalProjects ? 'animate-pulse' : ''}`}
|
|
|
|
|
/>
|
2026-02-05 10:27:52 +01:00
|
|
|
</div>
|
2026-02-05 11:48:57 +01:00
|
|
|
{jobStatus?.failedCount ? (
|
|
|
|
|
<p className="text-xs text-amber-600">
|
|
|
|
|
{jobStatus.failedCount} projects failed so far
|
|
|
|
|
</p>
|
|
|
|
|
) : null}
|
2026-02-05 10:27:52 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Result Display */}
|
|
|
|
|
{taggingResult && !taggingInProgress && (
|
|
|
|
|
<div className={`p-4 rounded-lg border ${
|
|
|
|
|
taggingResult.failed > 0
|
|
|
|
|
? 'bg-amber-50 dark:bg-amber-950/20 border-amber-200 dark:border-amber-900'
|
|
|
|
|
: taggingResult.processed > 0
|
|
|
|
|
? 'bg-green-50 dark:bg-green-950/20 border-green-200 dark:border-green-900'
|
|
|
|
|
: 'bg-muted border-border'
|
|
|
|
|
}`}>
|
|
|
|
|
<div className="flex items-center gap-3 mb-3">
|
|
|
|
|
{taggingResult.processed > 0 ? (
|
|
|
|
|
<CheckCircle2 className="h-5 w-5 text-green-600" />
|
|
|
|
|
) : taggingResult.errors.length > 0 ? (
|
|
|
|
|
<AlertCircle className="h-5 w-5 text-amber-600" />
|
|
|
|
|
) : (
|
|
|
|
|
<Tags className="h-5 w-5 text-muted-foreground" />
|
|
|
|
|
)}
|
|
|
|
|
<span className="font-medium">
|
|
|
|
|
{taggingResult.processed > 0
|
|
|
|
|
? 'Tagging Complete'
|
|
|
|
|
: taggingResult.errors.length > 0
|
|
|
|
|
? 'Tagging Issue'
|
|
|
|
|
: 'No Projects to Tag'}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="grid grid-cols-3 gap-3 text-center">
|
|
|
|
|
<div className="p-2 rounded bg-background">
|
|
|
|
|
<p className="text-2xl font-bold text-green-600">{taggingResult.processed}</p>
|
|
|
|
|
<p className="text-xs text-muted-foreground">Tagged</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="p-2 rounded bg-background">
|
|
|
|
|
<p className="text-2xl font-bold text-muted-foreground">{taggingResult.skipped}</p>
|
|
|
|
|
<p className="text-xs text-muted-foreground">Already Tagged</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="p-2 rounded bg-background">
|
|
|
|
|
<p className="text-2xl font-bold text-red-600">{taggingResult.failed}</p>
|
|
|
|
|
<p className="text-xs text-muted-foreground">Failed</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{taggingResult.errors.length > 0 && (
|
|
|
|
|
<p className="mt-3 text-sm text-amber-700 dark:text-amber-300">
|
|
|
|
|
{taggingResult.errors[0]}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Scope Selection */}
|
|
|
|
|
{!taggingInProgress && !taggingResult && (
|
|
|
|
|
<>
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<Label className="text-sm font-medium">Tagging Scope</Label>
|
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setTaggingScope('round')}
|
|
|
|
|
className={`flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors ${
|
|
|
|
|
taggingScope === 'round'
|
|
|
|
|
? 'border-primary bg-primary/5'
|
|
|
|
|
: 'border-border hover:border-muted-foreground/30'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<FolderOpen className={`h-6 w-6 ${taggingScope === 'round' ? 'text-primary' : 'text-muted-foreground'}`} />
|
|
|
|
|
<span className={`text-sm font-medium ${taggingScope === 'round' ? 'text-primary' : ''}`}>
|
|
|
|
|
Single Round
|
|
|
|
|
</span>
|
|
|
|
|
<span className="text-xs text-muted-foreground text-center">
|
|
|
|
|
Tag projects in one specific round
|
|
|
|
|
</span>
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setTaggingScope('program')}
|
|
|
|
|
className={`flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors ${
|
|
|
|
|
taggingScope === 'program'
|
|
|
|
|
? 'border-primary bg-primary/5'
|
|
|
|
|
: 'border-border hover:border-muted-foreground/30'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<Layers className={`h-6 w-6 ${taggingScope === 'program' ? 'text-primary' : 'text-muted-foreground'}`} />
|
|
|
|
|
<span className={`text-sm font-medium ${taggingScope === 'program' ? 'text-primary' : ''}`}>
|
|
|
|
|
Entire Edition
|
|
|
|
|
</span>
|
|
|
|
|
<span className="text-xs text-muted-foreground text-center">
|
|
|
|
|
Tag all projects across all rounds
|
|
|
|
|
</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Selection */}
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
{taggingScope === 'round' ? (
|
|
|
|
|
<>
|
|
|
|
|
<Label htmlFor="round-select">Select Round</Label>
|
|
|
|
|
<Select
|
|
|
|
|
value={selectedRoundForTagging}
|
|
|
|
|
onValueChange={setSelectedRoundForTagging}
|
|
|
|
|
>
|
|
|
|
|
<SelectTrigger id="round-select">
|
|
|
|
|
<SelectValue placeholder="Choose a round..." />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
{filterOptions?.rounds?.map((round) => (
|
|
|
|
|
<SelectItem key={round.id} value={round.id}>
|
|
|
|
|
{round.name} ({round.program?.name})
|
|
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<Label htmlFor="program-select">Select Edition</Label>
|
|
|
|
|
<Select
|
|
|
|
|
value={selectedProgramForTagging}
|
|
|
|
|
onValueChange={setSelectedProgramForTagging}
|
|
|
|
|
>
|
|
|
|
|
<SelectTrigger id="program-select">
|
|
|
|
|
<SelectValue placeholder="Choose an edition..." />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
{programs?.map((program) => (
|
|
|
|
|
<SelectItem key={program.id} value={program.id}>
|
|
|
|
|
{program.name} ({program.year})
|
|
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Info Note */}
|
|
|
|
|
<div className="flex items-start gap-2 p-3 rounded-lg bg-muted/50">
|
|
|
|
|
<Tags className="h-4 w-4 mt-0.5 text-muted-foreground shrink-0" />
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
Only projects without existing tags will be processed. AI will analyze project descriptions and assign relevant expertise tags for jury matching.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
2026-02-04 15:08:46 +01:00
|
|
|
</div>
|
2026-02-05 10:27:52 +01:00
|
|
|
|
|
|
|
|
{/* Footer */}
|
|
|
|
|
<div className="flex justify-end gap-3 pt-2 border-t">
|
|
|
|
|
{taggingResult ? (
|
|
|
|
|
<Button onClick={handleCloseTaggingDialog}>
|
|
|
|
|
Done
|
|
|
|
|
</Button>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={handleCloseTaggingDialog}
|
|
|
|
|
disabled={taggingInProgress}
|
|
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
onClick={handleStartTagging}
|
|
|
|
|
disabled={
|
|
|
|
|
taggingInProgress ||
|
|
|
|
|
(taggingScope === 'round' && !selectedRoundForTagging) ||
|
|
|
|
|
(taggingScope === 'program' && !selectedProgramForTagging)
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
{taggingInProgress ? (
|
|
|
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
|
|
|
) : (
|
|
|
|
|
<Sparkles className="mr-2 h-4 w-4" />
|
|
|
|
|
)}
|
|
|
|
|
{taggingInProgress ? 'Processing...' : 'Generate Tags'}
|
|
|
|
|
</Button>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
2026-01-30 13:41:32 +01:00
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|