From cc9a4807cbbe4f90f562c1b46597923d30953d27 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 25 May 2026 17:03:28 +0300 Subject: [PATCH 01/65] PM-5015 - ai review only - hide human review when "Ai only" is selected --- .../ReviewersField/ReviewersField.module.scss | 10 ++++++++++ .../components/ReviewersField/ReviewersField.tsx | 13 ++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/ReviewersField.module.scss b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/ReviewersField.module.scss index 2c5177a71..26f1fc301 100644 --- a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/ReviewersField.module.scss +++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/ReviewersField.module.scss @@ -56,6 +56,16 @@ padding: 0; } +.aiOnlyNotice { + background-color: #e8f0fe; + border: 1px solid #c5d8fb; + border-radius: 4px; + color: #1a4a9e; + font-size: 13px; + margin-bottom: 16px; + padding: 10px 14px; +} + .container { display: flex; flex-direction: column; diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/ReviewersField.tsx b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/ReviewersField.tsx index 50148fc8d..81505e64b 100644 --- a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/ReviewersField.tsx +++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/ReviewersField.tsx @@ -14,6 +14,7 @@ import classNames from 'classnames' import { AiReviewConfig, + AiReviewMode, ChallengeEditorFormData, Reviewer, } from '../../../../../lib/models' @@ -51,6 +52,7 @@ function hasReviewerChanges( export const ReviewersField: FC = (props: ReviewersFieldProps) => { const formContext = useFormContext() const [activeTab, setActiveTab] = useState('human') + const [aiReviewMode, setAiReviewMode] = useState(undefined) const humanTabRef = useRef(null) const aiTabRef = useRef(null) @@ -150,6 +152,7 @@ export const ReviewersField: FC = (props: ReviewersFieldPro const handleAiConfigPersisted = useCallback( (config: AiReviewConfig): void => { + setAiReviewMode(config.mode) const currentReviewers = formContext.getValues('reviewers') as Reviewer[] | undefined const nextReviewers = syncAiConfigReviewers({ phases, @@ -169,6 +172,7 @@ export const ReviewersField: FC = (props: ReviewersFieldPro [formContext, phases], ) const handleAiConfigRemoved = useCallback(async (): Promise => { + setAiReviewMode(undefined) const currentReviewers = formContext.getValues('reviewers') as Reviewer[] | undefined const nextReviewers = (currentReviewers || []).filter(reviewer => !isAiReviewer(reviewer)) @@ -285,7 +289,14 @@ export const ReviewersField: FC = (props: ReviewersFieldPro id='reviewers-human-panel' role='tabpanel' > - + {aiReviewMode === 'AI_ONLY' && ( +

+ No manual reviewers are needed in AI Only mode. +

+ )} + {aiReviewMode !== 'AI_ONLY' && ( + + )}
Date: Mon, 25 May 2026 17:04:47 +0300 Subject: [PATCH 02/65] dev deploy --- .circleci/config.yml | 2 ++ package.json | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 36aa6b62a..866cd65eb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -227,6 +227,8 @@ workflows: only: - dev - hide_ba_details + tags: + only: /^dev-.*/ - deployQa: context: org-global diff --git a/package.json b/package.json index 8824556b9..15d23e286 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "report:coverage": "nyc report --reporter=html", "report:coverage:text": "nyc report --reporter=text", "sb": "storybook dev -p 6006", - "sb:build": "storybook build -o build/storybook" + "sb:build": "storybook build -o build/storybook", + "deploy:dev": "BRANCH=$(git rev-parse --abbrev-ref HEAD) && TAG=\"dev-${BRANCH}\" && git tag -d \"$TAG\" 2>/dev/null; git push origin \":refs/tags/$TAG\" 2>/dev/null; git tag \"$TAG\" && git push origin \"$TAG\"" }, "dependencies": { "@codemirror/autocomplete": "^6.20.1", From 0a4b552ed619c64db111000c93c55c53413623a6 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 25 May 2026 17:08:17 +0300 Subject: [PATCH 03/65] build & deploy dev tags --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 866cd65eb..d52fc3db0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -203,6 +203,8 @@ workflows: ignore: - master - qa + tags: + only: /^dev-.*/ - build-qa: context: org-global From 7438506602cc8efd17741928e25fe23ad264af82 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 25 May 2026 17:46:57 +0300 Subject: [PATCH 04/65] Remove manual reviewers in ai-only mode --- .../components/ReviewersField/ReviewersField.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/ReviewersField.tsx b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/ReviewersField.tsx index 81505e64b..8d66375f2 100644 --- a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/ReviewersField.tsx +++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/ReviewersField.tsx @@ -154,12 +154,17 @@ export const ReviewersField: FC = (props: ReviewersFieldPro (config: AiReviewConfig): void => { setAiReviewMode(config.mode) const currentReviewers = formContext.getValues('reviewers') as Reviewer[] | undefined - const nextReviewers = syncAiConfigReviewers({ + let nextReviewers = syncAiConfigReviewers({ phases, reviewers: currentReviewers, workflows: config.workflows, }) + // AI_ONLY mode means no manual reviewers — strip any that remain + if (config.mode === 'AI_ONLY') { + nextReviewers = nextReviewers.filter(isAiReviewer) + } + if (!hasReviewerChanges(currentReviewers, nextReviewers)) { return } From f266fd682d182f787a61ca6b6712005670d11f1a Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 25 May 2026 22:41:44 +0300 Subject: [PATCH 05/65] PM-5015 - disable review timeline --- .../ChallengeDetailsContent.tsx | 7 +++++++ .../ChallengeTimeline.module.scss | 9 ++++++++ .../ChallengeTimeline/ChallengeTimeline.tsx | 21 ++++++++++++++++--- .../ChallengeDetailsPage.tsx | 7 ++++++- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/apps/review/src/lib/components/ChallengeDetailsContent/ChallengeDetailsContent.tsx b/src/apps/review/src/lib/components/ChallengeDetailsContent/ChallengeDetailsContent.tsx index 74824f97f..ab56ec0a5 100644 --- a/src/apps/review/src/lib/components/ChallengeDetailsContent/ChallengeDetailsContent.tsx +++ b/src/apps/review/src/lib/components/ChallengeDetailsContent/ChallengeDetailsContent.tsx @@ -196,6 +196,7 @@ export const ChallengeDetailsContent: FC = (props: Props) => { const { challengeInfo, myResources, + aiReviewConfig, }: ChallengeDetailContextModel = useContext(ChallengeDetailContext) const { actionChallengeRole }: useRoleProps = useRole() const hasIterativeReviewerRole = useMemo( @@ -539,6 +540,12 @@ export const ChallengeDetailsContent: FC = (props: Props) => { ) } + if (selectedTabNormalized === 'review' && aiReviewConfig?.mode === 'AI_ONLY') { + return ( + + ) + } + return ( = (props: Props) => { isSortable: false, label: 'Phase', propertyName: 'name', - renderer: (row: ChallengeTimelineRow) => {row.name}, + renderer: (row: ChallengeTimelineRow) => ( + {row.name} + ), type: 'element', }, { @@ -58,6 +61,14 @@ export const ChallengeTimeline: FC = (props: Props) => { label: 'Status', propertyName: 'status', renderer: (row: ChallengeTimelineRow) => { + if (row.disabled) { + return ( + + N/A + + ) + } + const normalizedStatus = row.status.trim() .toLowerCase() const statusClassKey = STATUS_CLASS_MAP[normalizedStatus] @@ -77,7 +88,9 @@ export const ChallengeTimeline: FC = (props: Props) => { isSortable: false, label: 'Start', propertyName: 'start', - renderer: (row: ChallengeTimelineRow) => {row.start}, + renderer: (row: ChallengeTimelineRow) => ( + {row.start} + ), type: 'element', }, { @@ -86,7 +99,9 @@ export const ChallengeTimeline: FC = (props: Props) => { isSortable: false, label: 'End', propertyName: 'end', - renderer: (row: ChallengeTimelineRow) => {row.end}, + renderer: (row: ChallengeTimelineRow) => ( + {row.end} + ), type: 'element', }, ] diff --git a/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx b/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx index e9c31970c..ed02eb16b 100644 --- a/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx +++ b/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx @@ -283,6 +283,7 @@ export const ChallengeDetailsPage: FC = (props: Props) => { isLoadingChallengeSubmissions, hasChallengeScopedFetchError, retryChallengeScopedFetches, + aiReviewConfig, }: ChallengeDetailContextModel = useContext(ChallengeDetailContext) const { loginUserInfo }: ReviewAppContextModel = useContext(ReviewAppContext) const { actionChallengeRole }: useRoleProps = useRole() @@ -1237,7 +1238,11 @@ export const ChallengeDetailsPage: FC = (props: Props) => { return 'Closed' })() + const isReviewPhaseRow = displayName.trim().toLowerCase() === 'review' + const isAiOnlyReviewRow = isReviewPhaseRow && aiReviewConfig?.mode === 'AI_ONLY' + rows.push({ + disabled: isAiOnlyReviewRow || undefined, duration: typeof phase.duration === 'number' ? phase.duration : undefined, end: formatDate(endSource), id: phase.id || phase.phaseId, @@ -1248,7 +1253,7 @@ export const ChallengeDetailsPage: FC = (props: Props) => { }) return rows - }, [approvalReviews, challengeInfo, phaseOrderingOptions, visibleChallengePhases]) + }, [aiReviewConfig, approvalReviews, challengeInfo, phaseOrderingOptions, visibleChallengePhases]) const setPhaseActionLoading = useCallback((phaseId: string, loading: boolean) => { setPhaseActionLoadingMap(prev => ({ From b3b084615c097d46cc2d4836140bee11cdc060ca Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 25 May 2026 22:56:51 +0300 Subject: [PATCH 06/65] lint --- .../ChallengeDetailsPage/ChallengeDetailsPage.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx b/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx index ed02eb16b..b939fb9bf 100644 --- a/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx +++ b/src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx @@ -1,3 +1,4 @@ +/* eslint-disable complexity */ /** * Challenge Details Page. */ @@ -1238,7 +1239,8 @@ export const ChallengeDetailsPage: FC = (props: Props) => { return 'Closed' })() - const isReviewPhaseRow = displayName.trim().toLowerCase() === 'review' + const isReviewPhaseRow = displayName.trim() + .toLowerCase() === 'review' const isAiOnlyReviewRow = isReviewPhaseRow && aiReviewConfig?.mode === 'AI_ONLY' rows.push({ From aeaf0eb6809e8f9dba3e2d89721b3ad8436c327d Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 28 May 2026 12:44:15 +0300 Subject: [PATCH 07/65] Hide "ai screening" phase for AI-only challenges --- .../ChallengeScheduleSection.spec.ts | 23 +++++++++++++++++++ .../ChallengeScheduleSection.utils.ts | 8 ++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengeScheduleSection/ChallengeScheduleSection.spec.ts b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengeScheduleSection/ChallengeScheduleSection.spec.ts index 69fd77043..5f98a6955 100644 --- a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengeScheduleSection/ChallengeScheduleSection.spec.ts +++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengeScheduleSection/ChallengeScheduleSection.spec.ts @@ -1,6 +1,7 @@ import { ChallengePhase } from '../../../../../lib/models' import { + AI_REVIEW_PHASE_NAME, AI_SCREENING_PHASE_NAME, buildSchedulePhaseRows, canEditPhaseStartDate, @@ -169,5 +170,27 @@ describe('ChallengeScheduleSection helpers', () => { expect(rows.filter(row => row.isVirtual)) .toHaveLength(0) }) + + it('does not inject a virtual AI screening row when an AI Review phase is present (AI_ONLY challenge)', () => { + const rows = buildSchedulePhaseRows([ + buildPhase({ + name: 'Registration', + phaseId: 'registration', + }), + buildPhase({ + name: 'Submission', + phaseId: 'submission', + }), + buildPhase({ + name: AI_REVIEW_PHASE_NAME, + phaseId: 'ai-review', + }), + ], true) + + expect(rows.filter(row => row.isVirtual)) + .toHaveLength(0) + expect(rows.map(row => row.phase.name)) + .toEqual(['Registration', 'Submission', AI_REVIEW_PHASE_NAME]) + }) }) }) diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengeScheduleSection/ChallengeScheduleSection.utils.ts b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengeScheduleSection/ChallengeScheduleSection.utils.ts index a4134206f..44999188e 100644 --- a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengeScheduleSection/ChallengeScheduleSection.utils.ts +++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengeScheduleSection/ChallengeScheduleSection.utils.ts @@ -6,6 +6,7 @@ import { ChallengePhase } from '../../../../../lib/models' import { getPhaseEndDateInDate } from '../../../../../lib/utils/date.utils' export const AI_SCREENING_PHASE_NAME = 'AI Screening' +export const AI_REVIEW_PHASE_NAME = 'AI Review' export interface RecalculatePhasesResult { phases: ChallengePhase[] @@ -133,7 +134,12 @@ export function buildSchedulePhaseRows( const hasRealAiScreeningPhase = phases.some( phase => normalizePhaseName(phase.name) === normalizePhaseName(AI_SCREENING_PHASE_NAME), ) - const shouldInsertVirtualAiScreening = hasAiReviewers && !hasRealAiScreeningPhase + // AI_ONLY challenges use an "AI Review" phase from the timeline template — no virtual + // AI Screening row should be injected for them. + const hasRealAiReviewPhase = phases.some( + phase => normalizePhaseName(phase.name) === normalizePhaseName(AI_REVIEW_PHASE_NAME), + ) + const shouldInsertVirtualAiScreening = hasAiReviewers && !hasRealAiScreeningPhase && !hasRealAiReviewPhase const rows: SchedulePhaseRow[] = [] let insertedVirtualRow = false From f07fad9a1e36013e3abf812405d62bef03f36802 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Thu, 28 May 2026 14:39:28 +0300 Subject: [PATCH 08/65] PM-5102 activate validation --- .../AiReviewTemplatesPage.tsx | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/apps/admin/src/ai/review-templates/AiReviewTemplatesPage.tsx b/src/apps/admin/src/ai/review-templates/AiReviewTemplatesPage.tsx index 101736764..a6e73fc83 100644 --- a/src/apps/admin/src/ai/review-templates/AiReviewTemplatesPage.tsx +++ b/src/apps/admin/src/ai/review-templates/AiReviewTemplatesPage.tsx @@ -194,6 +194,10 @@ export const AiReviewTemplatesPage: FC = () => { open: false, }) const [isToggling, setIsToggling] = useState(false) + const [disabledWorkflowsModal, setDisabledWorkflowsModal] = useState<{ + open: boolean; + workflowNames: string[]; + }>({ open: false, workflowNames: [] }) const trackOptions: InputSelectOption[] = useMemo(() => { const seen = new Set() @@ -309,6 +313,19 @@ export const AiReviewTemplatesPage: FC = () => { }, [deleteModal.template, filter, loadTemplates]) const handleToggleClick = useCallback((template: AiReviewTemplate) => { + // When trying to activate a disabled template, check for disabled workflows + if (template.disabled) { + const disabledWorkflows = (template.workflows || []) + .filter(item => item.workflow.disabled) + .map(item => item.workflow.name) + + if (disabledWorkflows.length > 0) { + setDisabledWorkflowsModal({ open: true, workflowNames: disabledWorkflows }) + + return + } + } + setToggleModal({ open: true, template }) }, []) @@ -316,6 +333,10 @@ export const AiReviewTemplatesPage: FC = () => { setToggleModal({ open: false }) }, []) + const handleCloseDisabledWorkflowsModal = useCallback(() => { + setDisabledWorkflowsModal({ open: false, workflowNames: [] }) + }, []) + const handleConfirmToggle = useCallback(async () => { if (!toggleModal.template) return setIsToggling(true) @@ -465,6 +486,32 @@ export const AiReviewTemplatesPage: FC = () => { ?

+ + + )} + > +

+ This template cannot be activated because the following workflow(s) are disabled: +

+
    + {disabledWorkflowsModal.workflowNames.map(name => ( +
  • {name}
  • + ))} +
+

+ Please enable these workflows first before activating this template. +

+
) } From d883406489cc8ae723ee40178a7026ef92b629b8 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 28 May 2026 22:41:05 +0300 Subject: [PATCH 09/65] PM-5015 - Render winners & final score for AI-only challenges --- .../ChallengeDetailsContent.tsx | 3 +++ .../components/TableWinners/TableWinners.tsx | 25 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/apps/review/src/lib/components/ChallengeDetailsContent/ChallengeDetailsContent.tsx b/src/apps/review/src/lib/components/ChallengeDetailsContent/ChallengeDetailsContent.tsx index ab56ec0a5..3d1f95c08 100644 --- a/src/apps/review/src/lib/components/ChallengeDetailsContent/ChallengeDetailsContent.tsx +++ b/src/apps/review/src/lib/components/ChallengeDetailsContent/ChallengeDetailsContent.tsx @@ -83,6 +83,7 @@ const TabContentPlaceholder = (props: { message: string }): JSX.Element => ( ) +const AI_REVIEW_KEY = normalizeType('ai review') const SUBMISSION_TAB_KEYS = new Set([ normalizeType('submission'), normalizeType('specification submission'), @@ -90,6 +91,7 @@ const SUBMISSION_TAB_KEYS = new Set([ normalizeType('ai screening'), normalizeType('submission / screening'), normalizeType('topgear submission'), + AI_REVIEW_KEY, ]) const CHECKPOINT_REVIEW_KEY = normalizeType('checkpoint review') @@ -156,6 +158,7 @@ const renderSubmissionTab = ({ .startsWith('submission') || isSpecificationSubmissionTab || selectedTabNormalized === AI_SCREENING_KEY + || selectedTabNormalized === AI_REVIEW_KEY || isTopgearSubmissionTab const visibleSubmissions = shouldRestrictToContestSubmissions ? submissions.filter( diff --git a/src/apps/review/src/lib/components/TableWinners/TableWinners.tsx b/src/apps/review/src/lib/components/TableWinners/TableWinners.tsx index 2bfdcf945..0f6dc9b8e 100644 --- a/src/apps/review/src/lib/components/TableWinners/TableWinners.tsx +++ b/src/apps/review/src/lib/components/TableWinners/TableWinners.tsx @@ -45,7 +45,12 @@ export const TableWinners: FC = (props: Props) => { const { width: screenWidth }: WindowSize = useWindowSize() const isTablet = useMemo(() => screenWidth <= 744, [screenWidth]) const location = useLocation() - const { challengeInfo }: ChallengeDetailContextModel = useContext(ChallengeDetailContext) + const { + challengeInfo, + aiReviewConfig, + aiReviewDecisionsBySubmissionId, + }: ChallengeDetailContextModel = useContext(ChallengeDetailContext) + const isAiOnly = aiReviewConfig?.mode === 'AI_ONLY' const { canViewAllSubmissions }: UseRolePermissionsResult = useRolePermissions() const { loginUserInfo }: ReviewAppContextModel = useContext(ReviewAppContext) @@ -229,10 +234,18 @@ export const TableWinners: FC = (props: Props) => { { label: 'Final Review Score', renderer: (data: ProjectResult) => { - const formatted = (typeof data.finalScore === 'number' - && Number.isFinite(data.finalScore)) - ? data.finalScore.toFixed(2) - : `${data.finalScore}` + let formatted: string + if (isAiOnly && data.submissionId) { + const aiDecision = aiReviewDecisionsBySubmissionId[data.submissionId] + formatted = typeof aiDecision?.totalScore === 'number' + ? aiDecision.totalScore.toFixed(2) + : '-' + } else { + formatted = (typeof data.finalScore === 'number' + && Number.isFinite(data.finalScore)) + ? data.finalScore.toFixed(2) + : `${data.finalScore}` + } return ( @@ -268,6 +281,8 @@ export const TableWinners: FC = (props: Props) => { [ downloadSubmission, isDownloading, + isAiOnly, + aiReviewDecisionsBySubmissionId, reviewTabUrl, getRestrictionMessageForMember, isSubmissionDownloadRestrictedForMember, From 425f563b5932913362138e7d9da54f41f4cf1462 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 2 Jun 2026 09:24:56 +0300 Subject: [PATCH 10/65] PM-5203 - allow manager to update ai score --- .../ChallengeDetailsContent.tsx | 11 + .../TabContentAiApproval.module.scss | 111 +++++++++ .../TabContentAiApproval.tsx | 211 ++++++++++++++++++ .../review/src/lib/models/AiReview.model.ts | 3 + .../src/lib/services/aiReview.service.ts | 23 +- 5 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentAiApproval.module.scss create mode 100644 src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentAiApproval.tsx diff --git a/src/apps/review/src/lib/components/ChallengeDetailsContent/ChallengeDetailsContent.tsx b/src/apps/review/src/lib/components/ChallengeDetailsContent/ChallengeDetailsContent.tsx index 3d1f95c08..33773f8a9 100644 --- a/src/apps/review/src/lib/components/ChallengeDetailsContent/ChallengeDetailsContent.tsx +++ b/src/apps/review/src/lib/components/ChallengeDetailsContent/ChallengeDetailsContent.tsx @@ -35,6 +35,7 @@ import { } from '../../utils/reviewPhaseGuards' import TabContentApproval from './TabContentApproval' +import TabContentAiApproval from './TabContentAiApproval' import TabContentCheckpoint from './TabContentCheckpoint' import TabContentIterativeReview from './TabContentIterativeReview' import TabContentRegistration from './TabContentRegistration' @@ -497,6 +498,16 @@ export const ChallengeDetailsContent: FC = (props: Props) => { } if (selectedTabNormalized === 'approval') { + if (aiReviewConfig?.mode === 'AI_ONLY') { + return ( + + ) + } + return ( void +} + +const SubmissionApprovalRow: FC = ({ + submission, + decision, + aiReviewers, + isPrivilegedRole, + isApprovalPhaseOpen, + onSaved, +}) => { + const [managerComment, setManagerComment] = useState(decision?.managerComment ?? '') + const [isSaving, setIsSaving] = useState(false) + const canEdit = isPrivilegedRole && isApprovalPhaseOpen + + const handleSave = useCallback(async () => { + if (!decision?.id) return + setIsSaving(true) + try { + const updated = await patchAiReviewDecision(decision.id, { + managerComment: managerComment.trim() || null, + }) + onSaved(updated) + toast.success('Manager comment saved.') + } catch (err) { + toast.error('Failed to save manager comment.') + } finally { + setIsSaving(false) + } + }, [decision?.id, managerComment, onSaved]) + + const submittedDate = submission.created + ? moment(submission.created).format(TABLE_DATE_FORMAT) + : '-' + + return ( +
+
+ + {submission.id} + + {submittedDate} + {decision && ( + + AI Score:{' '} + {decision.totalScore != null + ? decision.totalScore.toFixed(2) + : '-'} + {decision.status === 'HUMAN_OVERRIDE' && ( + (Override) + )} + + )} +
+ + + + {decision && canEdit && ( +
+ +