@@ -834,6 +834,9 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
834834 const [ pendingPaletteSelection , setPendingPaletteSelection ] = useState < {
835835 palette : string
836836 action : ( ) => void
837+ seriesIndex ?: number
838+ stageIndex ?: number
839+ type ?: 'general' | 'twoColor' | 'forecast'
837840 } | null > ( null )
838841
839842 const setLollipopShape = shape => {
@@ -1137,7 +1140,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
11371140 }
11381141
11391142 if ( isV1PaletteConfig ) {
1140- setPendingPaletteSelection ( { palette, action : executeSelection } )
1143+ setPendingPaletteSelection ( { palette, action : executeSelection , type : 'general' } )
11411144 setShowConversionModal ( true )
11421145 } else {
11431146 executeSelection ( )
@@ -1208,7 +1211,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
12081211 }
12091212
12101213 if ( isV1PaletteConfig ) {
1211- setPendingPaletteSelection ( { palette, action : executeSelection } )
1214+ setPendingPaletteSelection ( { palette, action : executeSelection , type : 'twoColor' } )
12121215 setShowConversionModal ( true )
12131216 } else {
12141217 executeSelection ( )
@@ -1218,6 +1221,111 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
12181221 }
12191222 }
12201223
1224+ // Forecast palette selection - includes v1/v2 migration modal logic
1225+ const handleForecastPaletteSelection = ( palette : string , seriesIndex : number , stageIndex : number ) => {
1226+ try {
1227+ if ( ! config ) {
1228+ console . error ( 'COVE: Config is undefined in handleForecastPaletteSelection' )
1229+ return
1230+ }
1231+
1232+ // Check if it's a v1 palette configuration
1233+ const isV1PaletteConfig = isV1Palette ( config )
1234+
1235+ const executeSelection = ( ) => {
1236+ const copyOfSeries = [ ...config . series ]
1237+ const copyOfStages = [ ...( copyOfSeries [ seriesIndex ] . stages || [ ] ) ]
1238+ copyOfStages [ stageIndex ] = { ...copyOfStages [ stageIndex ] , color : palette }
1239+ copyOfSeries [ seriesIndex ] = { ...copyOfSeries [ seriesIndex ] , stages : copyOfStages }
1240+
1241+ const _newConfig = cloneConfig ( config )
1242+ _newConfig . series = copyOfSeries
1243+
1244+ // If this is the first v2 palette selection, upgrade to v2
1245+ if ( isV1PaletteConfig && USE_V2_MIGRATION ) {
1246+ if ( ! _newConfig . general ) {
1247+ _newConfig . general = { }
1248+ }
1249+ if ( ! _newConfig . general . palette ) {
1250+ _newConfig . general . palette = { }
1251+ }
1252+ _newConfig . general . palette . version = '2.0'
1253+
1254+ // Forecast-specific migration map for v1 → v2 palette names (all lowercase-hyphen format)
1255+ const forecastPaletteMigrationMap : Record < string , string > = {
1256+ // Sequential Blue variants → sequential-blue
1257+ 'sequential-blue' : 'sequential-blue' ,
1258+ 'sequential-blue-two' : 'sequential-blue' ,
1259+ 'sequential-blue-three' : 'sequential-blue' ,
1260+ 'sequential-blue-2-(mpx)' : 'sequential-blue' ,
1261+ 'sequential-blue-2-mpx' : 'sequential-blue' ,
1262+ // Sequential Orange variants → sequential-orange
1263+ 'sequential-orange' : 'sequential-orange' ,
1264+ 'sequential-orange-two' : 'sequential-orange' ,
1265+ 'sequential-orange-(mpx)' : 'sequential-orange' ,
1266+ 'sequential-orange-mpx' : 'sequential-orange' ,
1267+ // Other sequential palettes (no variants, just normalize)
1268+ 'sequential-green' : 'sequential-green' ,
1269+ 'sequential-purple' : 'sequential-purple' ,
1270+ 'sequential-teal' : 'sequential-teal' ,
1271+ // Reverse variants - Sequential Blue
1272+ 'sequential-bluereverse' : 'sequential-bluereverse' ,
1273+ 'sequential-blue-reverse' : 'sequential-bluereverse' ,
1274+ 'sequential-blue-tworeverse' : 'sequential-bluereverse' ,
1275+ 'sequential-blue-two-reverse' : 'sequential-bluereverse' ,
1276+ 'sequential-blue-threereverse' : 'sequential-bluereverse' ,
1277+ 'sequential-blue-three-reverse' : 'sequential-bluereverse' ,
1278+ 'sequential-blue-2-(mpx)reverse' : 'sequential-bluereverse' ,
1279+ 'sequential-blue-2-(mpx)-reverse' : 'sequential-bluereverse' ,
1280+ 'sequential-blue-2-mpxreverse' : 'sequential-bluereverse' ,
1281+ 'sequential-blue-2-mpx-reverse' : 'sequential-bluereverse' ,
1282+ // Reverse variants - Sequential Orange
1283+ 'sequential-orangereverse' : 'sequential-orangereverse' ,
1284+ 'sequential-orange-reverse' : 'sequential-orangereverse' ,
1285+ 'sequential-orange-tworeverse' : 'sequential-orangereverse' ,
1286+ 'sequential-orange-two-reverse' : 'sequential-orangereverse' ,
1287+ 'sequential-orange-(mpx)reverse' : 'sequential-orangereverse' ,
1288+ 'sequential-orange-(mpx)-reverse' : 'sequential-orangereverse' ,
1289+ 'sequential-orange-mpxreverse' : 'sequential-orangereverse' ,
1290+ 'sequential-orange-mpx-reverse' : 'sequential-orangereverse' ,
1291+ // Reverse variants - Other sequential palettes
1292+ 'sequential-greenreverse' : 'sequential-greenreverse' ,
1293+ 'sequential-green-reverse' : 'sequential-greenreverse' ,
1294+ 'sequential-purplereverse' : 'sequential-purplereverse' ,
1295+ 'sequential-purple-reverse' : 'sequential-purplereverse' ,
1296+ 'sequential-tealreverse' : 'sequential-tealreverse' ,
1297+ 'sequential-teal-reverse' : 'sequential-tealreverse'
1298+ }
1299+
1300+ // Migrate and normalize all forecast stage colors to v2 format
1301+ _newConfig . series . forEach ( ( series : any ) => {
1302+ if ( series . type === 'Forecasting' && series . stages ) {
1303+ series . stages . forEach ( ( stage : any ) => {
1304+ if ( stage . color ) {
1305+ // First, try to migrate using the map
1306+ const migrated = forecastPaletteMigrationMap [ stage . color ] || stage . color
1307+ // Then normalize to lowercase with hyphens
1308+ stage . color = migrated . toLowerCase ( ) . replace ( / / g, '-' ) . replace ( / _ / g, '-' )
1309+ }
1310+ } )
1311+ }
1312+ } )
1313+ }
1314+
1315+ updateConfig ( _newConfig )
1316+ }
1317+
1318+ if ( isV1PaletteConfig ) {
1319+ setPendingPaletteSelection ( { palette, action : executeSelection , type : 'forecast' , seriesIndex, stageIndex } )
1320+ setShowConversionModal ( true )
1321+ } else {
1322+ executeSelection ( )
1323+ }
1324+ } catch ( error ) {
1325+ console . error ( 'COVE: Error in handleForecastPaletteSelection:' , error )
1326+ }
1327+ }
1328+
12211329 // Modal handlers
12221330 const handleConversionConfirm = ( ) => {
12231331 if ( pendingPaletteSelection ) {
@@ -1228,18 +1336,53 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
12281336 }
12291337
12301338 const handleConversionCancel = ( ) => {
1339+ // Don't update config - just close modal and discard pending selection
12311340 setShowConversionModal ( false )
12321341 setPendingPaletteSelection ( null )
12331342 }
12341343
12351344 const handleReturnToV1 = ( ) => {
12361345 if ( pendingPaletteSelection ) {
12371346 const _newConfig = cloneConfig ( config )
1347+ const { palette, type } = pendingPaletteSelection
1348+
1349+ // Handle based on palette type
1350+ if ( type === 'forecast' ) {
1351+ // Forecast palette selection
1352+ const { seriesIndex, stageIndex } = pendingPaletteSelection
1353+ if ( seriesIndex !== undefined && stageIndex !== undefined ) {
1354+ const copyOfSeries = [ ..._newConfig . series ]
1355+ const copyOfStages = [ ...copyOfSeries [ seriesIndex ] . stages ]
1356+ copyOfStages [ stageIndex ] = { ...copyOfStages [ stageIndex ] , color : palette }
1357+ copyOfSeries [ seriesIndex ] = { ...copyOfSeries [ seriesIndex ] , stages : copyOfStages }
1358+ _newConfig . series = copyOfSeries
1359+ }
1360+ } else if ( type === 'twoColor' ) {
1361+ // Two-color palette selection
1362+ if ( ! _newConfig . twoColor ) {
1363+ _newConfig . twoColor = { palette : '' , isPaletteReversed : false }
1364+ }
1365+ _newConfig . twoColor . palette = palette
1366+ } else {
1367+ // General palette selection (type === 'general' or undefined for backwards compatibility)
1368+ if ( ! _newConfig . general ) {
1369+ _newConfig . general = { }
1370+ }
1371+ if ( ! _newConfig . general . palette ) {
1372+ _newConfig . general . palette = { }
1373+ }
1374+ _newConfig . general . palette . name = palette
1375+ }
1376+
1377+ // Set version to V1
1378+ if ( ! _newConfig . general ) {
1379+ _newConfig . general = { }
1380+ }
12381381 if ( ! _newConfig . general . palette ) {
12391382 _newConfig . general . palette = { }
12401383 }
1241- _newConfig . general . palette . name = pendingPaletteSelection . palette
12421384 _newConfig . general . palette . version = '1.0'
1385+
12431386 updateConfig ( _newConfig )
12441387 }
12451388 setShowConversionModal ( false )
@@ -1514,7 +1657,8 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
15141657 handleUpdateHighlightedBarColor,
15151658 setLollipopShape,
15161659 handlePaletteSelection,
1517- handleTwoColorPaletteSelection
1660+ handleTwoColorPaletteSelection,
1661+ handleForecastPaletteSelection
15181662 }
15191663 if ( isLoading ) {
15201664 return < > </ >
@@ -1609,7 +1753,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
16091753 options = { getColumns ( ) }
16101754 />
16111755 { config . series && config . series . length !== 0 && (
1612- < Panels . Series . Wrapper getColumns = { getColumns } >
1756+ < Panels . Series . Wrapper getColumns = { getColumns } handleForecastPaletteSelection = { handleForecastPaletteSelection } >
16131757 < fieldset >
16141758 < legend className = 'edit-label float-left' > Displaying</ legend >
16151759 < Tooltip style = { { textTransform : 'none' } } >
0 commit comments