···157157 // If both are completed, no action, but still editable
158158 return { action: null, hint: 'Review complete' }
159159}
160160+161161+/**
162162+ * Get the display score for a review, handling all cases
163163+ * Returns the calculated weighted score whether it's stored or needs to be computed
164164+ */
165165+export function getReviewDisplayScore(review: any): number {
166166+ if (review.weightedScore) {
167167+ return decodeWeightedScore(review.weightedScore)
168168+ }
169169+ // Fallback: calculate from available ratings
170170+ return calculateWeightedScore(review)
171171+}
+110
src/utils/reviewValidation.ts
···11+import { calculateElapsedHours } from './reviewUtils'
22+33+export interface EditValidationResult {
44+ canEditStage1: boolean
55+ canEditStage2: boolean
66+ canEditStage3: boolean
77+ canAddStage2: boolean
88+ canAddStage3: boolean
99+ error?: string
1010+}
1111+1212+/**
1313+ * Determines what stages can be edited based on review state and elapsed time
1414+ */
1515+export function validateEditPermissions(review: any): EditValidationResult {
1616+ const elapsed = calculateElapsedHours(review.createdAt)
1717+1818+ // After 24 hours, nothing can be edited
1919+ if (elapsed >= 24) {
2020+ return {
2121+ canEditStage1: false,
2222+ canEditStage2: false,
2323+ canEditStage3: false,
2424+ canAddStage2: false,
2525+ canAddStage3: false,
2626+ error: 'Reviews cannot be edited after 24 hours'
2727+ }
2828+ }
2929+3030+ const hasStage1 = !!(review.openingRating && review.openingProjection)
3131+ const hasStage2 = !!(review.drydownRating && review.midProjection && review.sillage)
3232+ const hasStage3 = !!(review.endRating && review.complexity && review.longevity && review.overallRating)
3333+3434+ // Stage 1 can always be edited within 24 hours
3535+ const canEditStage1 = hasStage1
3636+3737+ // Stage 2 can be edited if it exists, or added after 1.5 hours
3838+ const canEditStage2 = hasStage2
3939+ const canAddStage2 = !hasStage2 && elapsed >= 1.5
4040+4141+ // Stage 3 can be edited if it exists, or added after 4 hours
4242+ const canEditStage3 = hasStage3
4343+ const canAddStage3 = !hasStage3 && elapsed >= 4
4444+4545+ return {
4646+ canEditStage1,
4747+ canEditStage2,
4848+ canEditStage3,
4949+ canAddStage2,
5050+ canAddStage3
5151+ }
5252+}
5353+5454+/**
5555+ * Validates that a stage update is allowed based on timing and current state
5656+ */
5757+export function validateStageUpdate(
5858+ review: any,
5959+ stage: 'stage1' | 'stage2' | 'stage3'
6060+): { valid: boolean; error?: string } {
6161+ const permissions = validateEditPermissions(review)
6262+6363+ if (permissions.error) {
6464+ return { valid: false, error: permissions.error }
6565+ }
6666+6767+ if (stage === 'stage1') {
6868+ // Stage 1 can always be edited within 24 hours
6969+ if (!permissions.canEditStage1) {
7070+ return { valid: false, error: 'Stage 1 cannot be edited after 24 hours' }
7171+ }
7272+ }
7373+7474+ if (stage === 'stage2') {
7575+ if (!permissions.canEditStage2 && !permissions.canAddStage2) {
7676+ const elapsed = calculateElapsedHours(review.createdAt)
7777+ if (elapsed < 1.5) {
7878+ const minsRemaining = Math.ceil((1.5 - elapsed) * 60)
7979+ return {
8080+ valid: false,
8181+ error: `Heart notes are available in ${minsRemaining} minutes`
8282+ }
8383+ }
8484+ return { valid: false, error: 'Stage 2 cannot be updated at this time' }
8585+ }
8686+ }
8787+8888+ if (stage === 'stage3') {
8989+ if (!permissions.canEditStage3 && !permissions.canAddStage3) {
9090+ const elapsed = calculateElapsedHours(review.createdAt)
9191+ if (elapsed < 4) {
9292+ const minsRemaining = Math.ceil((4 - elapsed) * 60)
9393+ return {
9494+ valid: false,
9595+ error: `Final notes are available in ${minsRemaining} minutes`
9696+ }
9797+ }
9898+ const hasStage2 = !!(review.drydownRating && review.midProjection && review.sillage)
9999+ if (!hasStage2) {
100100+ return {
101101+ valid: false,
102102+ error: 'Stage 2 must be completed before adding Stage 3'
103103+ }
104104+ }
105105+ return { valid: false, error: 'Stage 3 cannot be updated at this time' }
106106+ }
107107+ }
108108+109109+ return { valid: true }
110110+}