Skip to content

Gantt Chart Block(#548)#737

Merged
johbaxter merged 20 commits intodevfrom
Echart-Bar-Pie-Scatter-chart
Apr 4, 2025
Merged

Gantt Chart Block(#548)#737
johbaxter merged 20 commits intodevfrom
Echart-Bar-Pie-Scatter-chart

Conversation

@bannaarisamy-shanmugham-kanini
Copy link
Copy Markdown
Contributor

Description

Created Gantt chart with the similar to that of legacy UI

Changes Made

Added following Data Fields:

  • Task
  • Start Date
  • End Date
  • Task Group
  • Task Progress
  • Milestone
  • Tooltip

Added Following Features:

  • Add Fiscal Axis
  • Add Target Date line
  • Toggle Group View
  • Toggle Today's Date on/off
  • Legend
  • Customize Symbol

Added Following Events:

  • Click Event

How to Test

  1. In an app, select Gantt chart block under blocks section.
  2. Drag and Drop Gantt chart in the container block
  3. Verify all features under the tools section after selecting fields value.

Notes

@bannaarisamy-shanmugham-kanini bannaarisamy-shanmugham-kanini requested a review from a team as a code owner March 20, 2025 09:26
@bannaarisamy-shanmugham-kanini bannaarisamy-shanmugham-kanini linked an issue Mar 20, 2025 that may be closed by this pull request
14 tasks
@github-actions
Copy link
Copy Markdown

@CodiumAI-Agent /describe

@QodoAI-Agent
Copy link
Copy Markdown

Title

Gantt Chart Block(#548)


User description

Description

Created Gantt chart with the similar to that of legacy UI

Changes Made

Added following Data Fields:

  • Task
  • Start Date
  • End Date
  • Task Group
  • Task Progress
  • Milestone
  • Tooltip

Added Following Features:

  • Add Fiscal Axis
  • Add Target Date line
  • Toggle Group View
  • Toggle Today's Date on/off
  • Legend
  • Customize Symbol

Added Following Events:

  • Click Event

How to Test

  1. In an app, select Gantt chart block under blocks section.
  2. Drag and Drop Gantt chart in the container block
  3. Verify all features under the tools section after selecting fields value.

Notes


PR Type

  • Enhancement

Description

  • Implemented a comprehensive Gantt chart component

  • Added fiscal axis, target line and legend controls

  • Introduced symbol customization and group view features

  • Integrated new Gantt tools into visualization block menu


Changes walkthrough 📝

Relevant files
Enhancement
13 files
Gantt.tsx
New comprehensive Gantt chart component with eCharts based rendering
+860/-0 
CustomizeSymbol.tsx
Add customize symbol functionality for gantt milestone icons
+677/-0 
GanttFrameSection.tsx
Create frame section for mapping data to chart fields       
+447/-0 
GanttTargetLine.tsx
Implement target line configuration and interactivity for gantt
+302/-0 
UpgradedVisualizationTool.tsx
Integrate new gantt tool options into upgraded visualization toolbar
+245/-44
GanttFiscal.tsx
Add fiscal axis support with configurable start month and color
+299/-0 
GanttToolsSection.tsx
Aggregate gantt customization tools into a single menu section
+228/-0 
VisualizationBlock.tsx
Integrate Gantt chart component into visualization block framework
+148/-0 
VisualizationBlockMenu.tsx
Update block menu to include new Gantt chart tool options
+113/-0 
GanttDisplayValueLabels.tsx
Add toggle for showing display value labels on gantt tasks
+105/-0 
GanttGroupView.tsx
Implement group view toggle to manage overlapping gantt tasks
+104/-0 
GanttLegend.tsx
Enable legend toggle for displaying gantt chart series information
+102/-0 
index.ts
Export visualization block configurations and constants   
+2/-0     
Additional files
11 files
Visualization.constants.ts +5/-0     
VisualizationBlock.tsx +7/-0     
VisualizationBlockMenu.tsx +4/-0     
chart-utility.ts +15/-0   
Visualization.constants.ts +61/-0   
VizBlockContextMenu.tsx +94/-0   
config.tsx +40/-0   
package.json +2/-2     
index.ts +2/-0     
default-menu.ts +81/-1   
pnpm-lock.yaml [link]   

Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • @github-actions
    Copy link
    Copy Markdown

    @CodiumAI-Agent /review

    @github-actions
    Copy link
    Copy Markdown

    @CodiumAI-Agent /improve

    @QodoAI-Agent
    Copy link
    Copy Markdown

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 5 🔵🔵🔵🔵🔵
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Complexity

    The Gantt chart component is quite long and complex. Consider refactoring by extracting some of the logic into smaller, reusable sub-components to improve maintainability and readability.

    import { useEffect, useMemo, useRef, useState } from "react";
    import { Paper, Table } from "@mui/material";
    import { TableHead } from "@mui/material";
    import { styled, TableContainer } from "@mui/material";
    import { TableRow, TableCell, TableBody } from "@mui/material";
    import { computed } from "mobx";
    import { observer } from "mobx-react-lite";
    import ReactECharts from "echarts-for-react";
    import { useBlock, useBlockSettings, useFrame } from "../../../../../hooks";
    import { BlockDef } from "../../../../../store";
    import { getValueByPath } from "@/utility";
    import { VizBlockContextMenu } from "../../VizBlockContextMenu";
    import { GANTT_CHART } from "../../Visualization.constants";
    import { EchartVisualizationBlockDef } from "../../VisualizationBlock";
    //Main container where gantt chart will render
    const StyledMainContainer = styled("div")(({ theme }) => ({
        width: "100%",
        height: "100%",
    }));
    //sub styled container to manage fiscal axis with chart
    const StyledContainer = styled("div")(() => ({
        display: "flex",
        justifyContent: "flex-start",
        width: "100%",
        height: "20%",
        maxHeight: "25%",
        overflow: "auto",
    }));
    //styled span to render series name
    const StyledDataSpan = styled("span")(({}) => ({}));
    //styled table cell to have background color
    const StyledTableCell = styled(TableCell)<{ backgroundColor?: string }>(
        ({ backgroundColor }) => ({
            backgroundColor: backgroundColor ?? "#fff",
            border: "1px solid #e6e6e6",
        }),
    );
    //Gantt chart props
    interface GanttProps {
        id: string;
        updateChart: (dataOption, path) => void;
    }
    //Gantt chart main component
    export const Gantt = observer(
        <D extends BlockDef = BlockDef>({ id, updateChart }: GanttProps) => {
            const { data, setData } =
                useBlockSettings<EchartVisualizationBlockDef>(id); //Data for the current block
            //computed value to hold the most recent data
            const computedValue = useMemo(() => {
                return computed(() => {
                    if (!data) {
                        return "";
                    }
                    const v = getValueByPath(data, "option");
                    if (typeof v === "undefined") {
                        return "";
                    } else if (typeof v === "string") {
                        return v;
                    }
                    return JSON.stringify(v, null, 2);
                });
            }, [data, "option"]).get();
            //custom context menu to show when user right clicks
            const [contextMenu, setContextMenu] = useState<{
                mouseX: number; //x axis position for the click/brush event
                mouseY: number; //y axis position for the click/brush event
                value: unknown; //value can be of object or string or number type
            } | null>(null);
            let chartRef = useRef(null);
            //table reference variable to align series name with fiscal axis
            const tableRef = useRef(null);
            const [seriesNameCol, setSeriesNameCol] = useState(70);
            //selector to fetch data from the frame
            let selector = "";
            if (data.columns !== undefined) {
                selector = `Select(${data.columns
                    .map((item, index) => {
                        return item.selector;
                    })
                    .join(",")}).as([${data.columns
                    .map((item, index) => {
                        return item.name;
                    })
                    .join(",")}])`;
            }
            //frame object to get the data from the frame
            const frame = useFrame(data.frame?.name, {
                selector: selector,
            });
            // custom variable to hold the chart data to render
            let dataOption = useMemo(() => {
                let option = JSON.parse(computedValue);
                var resourceRows = []; // Stores resource related details
                var seriesData = []; // series data to be used for rendering chart
                let yAxisName = "";
                let toolTipSelected = [];
                let toolTipSelectedIndex = [];
                let mileStoneIndex = "";
                let milestoneData = [];
                //detect task progress column is selected or not
                let taskProgressSelected = Object.keys(
                    option["customSettings"]["columnDetails"],
                ).some((item) => item === "taskprogress");
                //default properties for milestone display
                let mileStoneProperties = {
                    symbol: GANTT_CHART.MILESTONE_SYMBOL,
                    color: GANTT_CHART.MILESTONE_COLOR,
                    symbolSize: GANTT_CHART.MILESTONE_SYMBOL_SIZE,
                };
                // symbol value, size, color based on the milestone selected
                let symbolValue = [];
                let symbolSize = [];
                let symbolColor = [];
                //show legend or not
                let legendShow = false;
                //show group view or not
                let groupViewShow = false;
                //column details selected in the data section
                let columnIndexDetails =
                    option["customSettings"]["columnIndexDetails"];
                //frame data values
                if (frame.data.values.length) {
                    frame.data.values.forEach((item, index) => {
                        let itemIndex = parseInt(columnIndexDetails["milestone"]);
                        let mileStoneDate = new Date(
                            item[itemIndex] as Date,
                        ).getTime();
    
                        let ganttToolsLength =
                            option["customSettings"]?.["gantttools"]?.[
                                "customizeSymbol"
                            ]?.length;
                        let ganttToolsSelected =
                            ganttToolsLength > -1
                                ? option["customSettings"]?.["gantttools"]?.[
                                      "customizeSymbol"
                                  ]?.[ganttToolsLength - 1]
                                : {};
    
                        let ganttToolsDimensionValues =
                            ganttToolsSelected?.dimensionValues?.map(
                                (item, index) => new Date(item).getTime(),
                            ) || [];
    
                        if (
                            ganttToolsSelected?.dimensionSelected === "milestone" &&
                            ganttToolsDimensionValues?.includes(mileStoneDate)
                        ) {
                            symbolValue.push(ganttToolsSelected.symbol);
                            symbolSize.push(ganttToolsSelected.symbolSize);
                            symbolColor.push(ganttToolsSelected.symbolColor);
                        } else {
                            symbolValue.push(mileStoneProperties.symbol);
                            symbolSize.push(mileStoneProperties.symbolSize);
                            symbolColor.push(mileStoneProperties.color);
                        }
                    });
                }
                if (frame.data.values.length) {
                    // Step 1: Group tasks by resource
                    var groupedData = {};
                    let dataGrouped = Object.keys(
                        option["customSettings"]["columnDetails"],
                    ).some((item) => item === "taskgroup");
                    let taskGroupIndex =
                        option["customSettings"]["columnIndexDetails"][
                            "taskgroup"
                        ] || -1;
                    let toolTipData = Object.keys(
                        option["customSettings"]["columnDetails"],
                    ).filter((item) => item === "tooltip");
                    toolTipData.forEach((item, index) => {
                        option["customSettings"]["columnDetails"][item].forEach(
                            (item) => {
                                toolTipSelected.push(item.name);
                            },
                        );
                    });
                    legendShow =
                        option["customSettings"]?.["gantttools"]?.["showLegend"] ||
                        false;
                    groupViewShow =
                        option["customSettings"]?.["gantttools"]?.[
                            "showGroupView"
                        ] || false;
                    toolTipSelectedIndex =
                        option["customSettings"]["columnIndexDetails"]["tooltip"] ||
                        [];
    
                    if (dataGrouped && groupViewShow) {
                        yAxisName =
                            taskGroupIndex > -1
                                ? option["customSettings"]["columnDetails"][
                                      "taskgroup"
                                  ]["name"]
                                : "";
                        frame.data.values.forEach((d: string[], index) => {
                            if (!groupedData[d[taskGroupIndex]])
                                groupedData[d[taskGroupIndex]] = [];
                            groupedData[d[taskGroupIndex]].push(d);
                        });
    
                        Object.keys(groupedData).forEach((resource) => {
                            let tasks = groupedData[resource];
                            tasks.sort(
                                (a: any, b: any) =>
                                    new Date(a[1]).getTime() -
                                    new Date(b[1]).getTime(),
                            ); // Sort by start date
    
                            let rowIndexes = []; // Tracks task end times per row
                            resourceRows.push(resource); // First row for the resource
    
                            tasks.forEach((task) => {
                                let taskStart = new Date(task[1]).getTime();
                                let taskEnd = new Date(task[2]).getTime();
                                // Find an available row (avoid overlap)
                                let rowIndex = rowIndexes.findIndex(
                                    (endTime) => taskStart >= endTime,
                                );
                                if (rowIndex === -1) {
                                    rowIndex = rowIndexes.length;
                                    resourceRows.push(""); // Add an empty row for stacking
                                }
                                rowIndexes[rowIndex] = taskEnd; // Update row availability
                                // Push formatted task data
                                seriesData.push({
                                    name: task[0],
                                    resource: resource,
                                    taskprogress:
                                        task[columnIndexDetails["taskprogress"]],
                                    value: [
                                        taskStart,
                                        resourceRows.length - 1,
                                        taskEnd,
                                        ...toolTipSelectedIndex.map(
                                            (item) => task[item],
                                        ),
                                    ],
                                });
                            });
                        });
                    } else {
                        yAxisName =
                            option["customSettings"]["columnDetails"]["task"][
                                "name"
                            ];
                        // Convert data to proper format
                        seriesData = frame.data.values.map(
                            (d: string[], index) => ({
                                name: d[columnIndexDetails["task"]],
                                taskprogress: d[columnIndexDetails["taskprogress"]],
                                value: [
                                    new Date(
                                        d[columnIndexDetails["startdate"]],
                                    ).getTime(),
                                    index,
                                    new Date(
                                        d[columnIndexDetails["enddate"]],
                                    ).getTime(),
                                    ...toolTipSelectedIndex.map((item) => d[item]),
                                ],
                            }),
                        );
                        resourceRows = frame.data.values.map((d, index) => d[0]);
                    }
                    if (
                        columnIndexDetails.hasOwnProperty("milestone") &&
                        columnIndexDetails["milestone"]
                    ) {
                        let gantttools = option["customSettings"]["gantttools"];
                        milestoneData = frame.data.values.map(
                            (d: string[], index) => {
                                let mileStoneSymbol = mileStoneProperties.symbol;
                                let symbolSize = mileStoneProperties.symbolSize;
                                let mileStoneDate = new Date(
                                    d[columnIndexDetails["milestone"]],
                                ).getTime();
                                let endDate = new Date(
                                    d[columnIndexDetails["enddate"]],
                                ).getTime();
                                return {
                                    name: `MileStone ${index + 1}`,
                                    value: [
                                        mileStoneDate,
                                        d[columnIndexDetails["task"]],
                                        endDate,
                                    ],
                                    mileStoneOriginalDate:
                                        d[columnIndexDetails["milestone"]],
                                    symbol: symbolValue[index],
                                    symbolSize: symbolSize[index],
                                    itemStyle: {
                                        color: symbolColor[index],
                                    },
                                };
                            },
                        );
                    }
                }
    
                let lineData = [];
                let showDisplayValueLabels =
                    option["customSettings"]?.["gantttools"]?.[
                        "showDisplayValueLabels"
                    ] || false;
                let mainSeriesName =
                    option["customSettings"]?.["columnDetails"]?.["task"]?.["name"];
                let mainSeriesFrameName =
                    option["customSettings"]?.["columnDetails"]?.["task"]?.[
                        "selector"
                    ];
                if (
                    option["series"].some(
                        (series) => series.name === "targetDateSegment",
                    )
                ) {
                    let targetDateSegment = option["series"].filter(
                        (item) => item.name === "targetDateSegment",
                    );
                    targetDateSegment[0] = {
                        ...targetDateSegment[0],
                        targetDateSegment: true,
                        // name: targetDateSegment[0]?.["data"]?.length
                        //     ? "Target Data Segment"
                        //     : "",
                        renderItem: (params, api) => {
                            const x = api.coord([api.value(0), 0])[0];
                            const targetText =
                                option["customSettings"]?.["gantttools"]?.[
                                    "targetLineName"
                                ] || "";
                            const targetColor =
                                option["customSettings"]?.["gantttools"]?.[
                                    "targetLineColor"
                                ] || "#FF0000";
                            // Convert date to x-axis position
                            const height = params.coordSys.height; // Full chart height return
                            const yBottom =
                                params.coordSys.y + params.coordSys.height;
                            const yTop = params.coordSys.y;
                            //if targetdate is not empty then show the target date line
                            if (
                                option["customSettings"]?.["gantttools"]?.[
                                    "targetDate"
                                ] != ""
                            ) {
                                return {
                                    type: "group",
                                    children: [
                                        {
                                            type: "line",
                                            originX: 0,
                                            originY: 0,
                                            shape: {
                                                x1: x,
                                                y1: yBottom,
                                                x2: x,
                                                y2: yTop,
                                            },
                                            style: {
                                                stroke: targetColor, // Line color
                                                lineWidth: 2, // Line thickness
                                                type: "dashed", // Line style
                                            },
                                        },
                                        {
                                            type: "text",
                                            style: {
                                                x: x,
                                                y: yTop - 10,
                                                text: targetText,
                                                textAlign: "center",
                                                textVerticalAlign: "bottom",
                                            },
                                        },
                                    ],
                                };
                            }
                            return {};
                        },
                    };
                    //line data setting if target date is not empty
                    if (
                        option["customSettings"]?.["gantttools"]?.["targetDate"] !=
                        ""
                    ) {
                        lineData = targetDateSegment;
                    } else {
                        lineData = [];
                    }
                }
                //final data option to set to chart
                option = {
                    ...option,
                    tooltip: {
                        trigger: "item",
                        formatter: (params: any) =>
                            chartFormatter(
                                params,
                                toolTipSelectedIndex,
                                frame.data.headers,
                                frame.data.values,
                            ),
                    },
                    xAxis: {
                        type: "time",
                        // name: 'Date',
                        axisLabel: {
                            formatter: (value) =>
                                new Date(value).toLocaleDateString(),
                        },
                        splitLine: { show: true },
                        axisLine: {
                            show: true,
                        },
                        axisTick: {
                            show: true,
                        },
                    },
                    yAxis: {
                        type: "category",
                        data: resourceRows,
                        inverse: true,
                    },
                    legend: {
                        show: legendShow,
                    },
                    series: [
                        ...lineData,
                        {
                            type: "custom",
                            chartrendered: true,
                            name: mainSeriesName,
                            frameName: mainSeriesFrameName,
                            renderItem: function (params, api) {
                                var categoryIndex = api.value(1);
                                var start = api.coord([
                                    api.value(0),
                                    categoryIndex,
                                ]);
                                var end = api.coord([api.value(2), categoryIndex]);
                                var height = api.size([0, 1])[1] * 0.6;
                                let tooltipName = seriesData[params.dataIndex].name
                                    ? seriesData[params.dataIndex].name
                                    : "";
                                if (taskProgressSelected) {
                                    let partialWidth = seriesData[params.dataIndex]
                                        .taskprogress
                                        ? (end[0] - start[0]) *
                                          (seriesData[params.dataIndex]
                                              .taskprogress /
                                              100)
                                        : end[0] - start[0];
    
                                    return {
                                        type: "group",
                                        children: [
                                            {
                                                type: "rect",
                                                shape: {
                                                    x: start[0],
                                                    y: start[1] - height / 2,
                                                    width: end[0] - start[0],
                                                    height: height,
                                                },
                                                style: {
                                                    fill: "lightgrey",
                                                    stroke: "#333",
                                                },
                                            },
                                            {
                                                type: "rect",
                                                shape: {
                                                    x: start[0],
                                                    y: start[1] - height / 2,
                                                    width: partialWidth,
                                                    height: height,
                                                },
                                                style: {
                                                    fill: "#6495ED",
                                                    stroke: "#333",
                                                },
                                            },
                                            {
                                                type: "text",
                                                style: {
                                                    text: tooltipName,
                                                    x: start[0],
                                                    y: start[1] - height / 2,
                                                    textVerticalAlign: "middle",
                                                    textAlign: "center",
                                                    fontSize: 15,
                                                    opacity: showDisplayValueLabels
                                                        ? 1
                                                        : 0,
                                                },
                                            },
                                        ],
                                    };
                                }
                                return {
                                    type: "group",
                                    children: [
                                        {
                                            type: "rect",
                                            chartrendered: true,
                                            shape: {
                                                x: start[0],
                                                y: start[1] - height / 2,
                                                width: end[0] - start[0],
                                                height: height,
                                            },
                                            style: {
                                                fill: "#6495ED",
                                                stroke: "#333",
                                            },
                                        },
                                        {
                                            type: "text",
                                            style: {
                                                text: tooltipName,
                                                x: start[0],
                                                y: start[1] - height / 2,
                                                textVerticalAlign: "middle",
                                                textAlign: "center",
                                                fontSize: 15,
                                                opacity: showDisplayValueLabels
                                                    ? 1
                                                    : 0,
                                            },
                                        },
                                    ],
                                };
                            },
                            encode: { x: [0, 2], y: 1 },
                            data: seriesData,
                        },
                        {
                            type: "scatter",
                            name: milestoneData.length ? "Milestones" : "",
                            milestonerendered: true,
                            label: {
                                show: showDisplayValueLabels ? true : false,
                                position: "top",
                                formatter: "{b}",
                            },
                            data: milestoneData,
                        },
                    ],
                };
                return option;
            }, [frame.data.values, data.columns, computedValue]);
            //get quarter and month list with fiscal year details
            function getQuarterAndMonthList(startFiscalMonth) {
                let startMonth = startFiscalMonth;
                let month = [
                    "Jan",
                    "Feb",
                    "Mar",
                    "Apr",
                    "May",
                    "Jun",
                    "Jul",
                    "Aug",
                    "Sep",
                    "Oct",
                    "Nov",
                    "Dec",
                ];
                let startIndex = month.indexOf(startMonth);
                let startIndexTemp = startIndex;
                let quarterObject = {};
                //create quarter object
                [1, 2, 3, 4].forEach((item) => {
                    quarterObject["Q" + item] = [];
                    let countsPerQuarter = 3;
                    for (let i = 0; i < countsPerQuarter; i++) {
                        if (startIndexTemp == month.length) {
                            startIndexTemp = month.length % 12;
                        }
                        quarterObject["Q" + item][i] = month[startIndexTemp];
                        startIndexTemp++;
                    }
                });
                //month based on quarter from Jan to Dec based on fiscal year start
                let monthBasedQuarter = [];
                let lastMonthInQuarter = "";
                month.forEach((item, index) => {
                    let monthExistsInQuarter = "";
                    for (let i = 0; i < 4; i++) {
                        if (
                            quarterObject["Q" + (i + 1)].some(
                                (qoItem) => item === qoItem,
                            )
                        ) {
                            monthExistsInQuarter = "Q" + (i + 1);
                        }
                    }
                    let quarterExistsInArray = monthBasedQuarter
                        .reverse()
                        .findIndex(
                            (mbitem, mbindex) =>
                                monthExistsInQuarter === mbitem.name,
                        );
                    if (
                        quarterExistsInArray >= 0 &&
                        lastMonthInQuarter == monthExistsInQuarter
                    ) {
                        monthBasedQuarter[quarterExistsInArray]["month"] = [
                            ...monthBasedQuarter[quarterExistsInArray]["month"],
                            item,
                        ];
                    } else {
                        monthBasedQuarter = [
                            ...monthBasedQuarter,
                            {
                                name: monthExistsInQuarter,
                                month: [item],
                                order: monthBasedQuarter.length + 1,
                            },
                        ];
                    }
                    lastMonthInQuarter = monthExistsInQuarter;
                });
                //set initial fiscal year based on current month selection, if month data is not available, then first record of seriesdata is selected
                let FYYear =
                    parseInt(
                        dataOption["customSettings"]?.["gantttools"]?.[
                            "fiscalYearValue"
                        ]?.substring(2),
                    ) + 1;
                monthBasedQuarter = monthBasedQuarter.map((item, index) => {
                    return {
                        ...item,
                        ["colSpan"]: item.month.length,
                    };
                });
                //sorting records based on date
                monthBasedQuarter = monthBasedQuarter.sort(
                    (item, item1) => item.order - item1.order,
                );
                let monthSelected =
                    dataOption["customSettings"]?.["gantttools"]?.[
                        "fiscalYearStart"
                    ];
                let yearQuarterIndex = monthBasedQuarter.findIndex((item) =>
                    item.month.includes(monthSelected),
                );
                monthBasedQuarter = monthBasedQuarter.map((item, index) => {
                    return {
                        ...item,
                        ["fiscalYear"]: isNaN(FYYear)
                            ? ""
                            : index < yearQuarterIndex
                            ? "FY" + (FYYear - 1)
                            : "FY" + FYYear,
                    };
                });
                return monthBasedQuarter;
            }
            //enable or disable fiscal axis
            const enableFiscalAxis =
                dataOption["customSettings"]?.["gantttools"]?.[
                    "enableFiscalAxis"
                ] || false;
            //update chart data when frame values are changed
            useEffect(() => {
                if (!frame.isLoading && frame.data.values.length > 0) {
                    updateChart(dataOption, "option");
                }
            }, [frame.data.values]);
            //update chart data when data is updated
            useEffect(() => {
                let echartsInstance = chartRef.current?.getEchartsInstance();
                if (echartsInstance) {
                    echartsInstance.setOption(dataOption, { notMerge: true });
                }
            }, [dataOption]);
            //update height series name section based on table height
            useEffect(() => {
                const table = tableRef.current;
    
                if (!table) return;
    
                // Create a ResizeObserver instance
                const resizeObserver = new ResizeObserver((entries) => {
                    for (let entry of entries) {
                        const { height } = entry.contentRect;
                        setSeriesNameCol(height);
                    }
                });
    
                // Observe the table element
                resizeObserver.observe(table);
    
                // Clean up the observer on unmount
                return () => {
                    resizeObserver.disconnect();
                };
            }, [enableFiscalAxis]);
            //tooltip function to render tooltip based on options provided
            function chartFormatter(
                params,
                tooltipData,
                frameHeaders,
                frameValues,
            ) {
                let chartToolTip = `<b>${params.name}</b><br>
                Start: ${new Date(params.value[0]).toLocaleDateString()}<br>
                End: ${new Date(params.value[2]).toLocaleDateString()}<br>`;
                tooltipData.forEach((item, index) => {
                    chartToolTip += `${frameHeaders[item]}: ${
                        frameValues[params.dataIndex][item]
                    }<br>`;
                });
                return chartToolTip;
            }
            //fiscal start month
            const fiscalStartMonth =
                dataOption["customSettings"]?.["gantttools"]?.["fiscalYearStart"] ||
                "Jan";
            //fiscal axis background color
            const fiscalAxisBackgroundColor =
                dataOption["customSettings"]?.["gantttools"]?.[
                    "fiscalAxisBackgroundColor"
                ] || "#0471f0";
            //getquarter and month list with fiscal year
            const quarterAndMonth = getQuarterAndMonthList(fiscalStartMonth);
            //get the series name for chart side heading
            let seriesName =
                dataOption["customSettings"]?.["columnDetails"]?.["task"]?.name ||
                "";
            const onClickChart = {
                //when contextmenu event is raised, default context menu made hidden, and custom component is shown
                contextmenu: (params) => {
                    if (params.data) {
                        let taskColumn = params.data.name;
                        let parsedJson = JSON.parse(computedValue);
                        let taskName =
                            parsedJson["series"][params.seriesIndex]["frameName"];
                        setContextMenu(
                            contextMenu === null
                                ? {
                                      mouseX: params.event.event.clientX,
                                      mouseY: params.event.event.clientY,
                                      value: {
                                          label: taskName,
                                          value: taskColumn,
                                      },
                                  }
                                : // repeated contextmenu when it is already open closes it with Chrome 84 on Ubuntu
                                  // Other native context menus might behave different.
                                  // With this behavior we prevent contextmenu from the backdrop to re-locale existing context menus.
                                  null,
                        );
                        params.event.event.preventDefault();
                    } else {
                        params.event.event.preventDefault();
                    }
                },
            };
            return (
                <>
                    <StyledMainContainer id={id}>
                        {enableFiscalAxis && (
                            <StyledContainer>
                                <StyledDataSpan
                                    style={{
                                        backgroundColor: fiscalAxisBackgroundColor,
                                        height: seriesNameCol + "px",
                                        width: "50px",
                                        textAlign: "center",
                                        display: "flex",
                                        margin: "auto",
                                        alignContent: "space-around",
                                        flexWrap: "wrap",
                                        borderRadius: "5px",
                                        justifyContent: "center",
                                    }}
                                >
                                    {seriesName}
                                </StyledDataSpan>
                                <Table
                                    aria-label="simple table"
                                    ref={(e) => (tableRef.current = e)}
                                >
                                    <TableHead>
                                        <TableRow>
                                            {quarterAndMonth.length &&
                                                quarterAndMonth.map((item) => (
                                                    <StyledTableCell
                                                        backgroundColor={
                                                            fiscalAxisBackgroundColor
                                                        }
                                                        size="small"
                                                        colSpan={item?.colSpan}
                                                        align="center"
                                                    >
                                                        {item.name}{" "}
                                                        {item.hasOwnProperty(
                                                            "fiscalYear",
                                                        ) &&
                                                        item["fiscalYear"] != ""
                                                            ? `(${item["fiscalYear"]})`
                                                            : ""}
                                                    </StyledTableCell>
                                                ))}
                                        </TableRow>
                                    </TableHead>
                                    <TableBody>
                                        <TableRow
                                            sx={{
                                                "&:last-child td, &:last-child th":
                                                    {
                                                        border: "1px solid grey",
                                                    },
                                            }}
                                        >
                                            {quarterAndMonth.length &&
                                                quarterAndMonth.map((item) =>
                                                    item["month"].map(
                                                        (monthItem) => (
                                                            <StyledTableCell
                                                                component={"td"}
                                                                scope="row"
                                                                size="small"
                                                            >
                                                                {monthItem}
                                                            </StyledTableCell>
                                                        ),
                                                    ),
                                                )}
                                        </TableRow>
                                    </TableBody>
                                </Table>
                            </StyledContainer>
                        )}
    
                        <ReactECharts
                            option={dataOption}
                            onEvents={onClickChart}
                            ref={(e) => (chartRef.current = e)}
                            style={{
                                width: "inherit",
                                height: enableFiscalAxis ? "75%" : "100%",
                                maxHeight: enableFiscalAxis ? "75%" : "100%",
                            }}
                        />
                        <VizBlockContextMenu
                            id={id}
                            frame={frame}
                            contextMenu={contextMenu}
                            onClose={() => setContextMenu(null)}
                        />
                    </StyledMainContainer>
                </>
            );
        },
    );
    Maintainability

    The CustomizeSymbol component handles many state variables and effects in one file. Additional inline documentation and possible modularization of related logic would help future developers understand and maintain the code.

    import { ChangeEvent, useEffect, useMemo, useState } from "react";
    import { computed } from "mobx";
    import { observer } from "mobx-react-lite";
    import {
        Autocomplete,
        Button,
        Chip,
        IconButton,
        Select,
        Slider,
        Switch,
        TextField,
    } from "@semoss/ui";
    import styled from "@emotion/styled";
    import { getValueByPath } from "@/utility";
    import { PathValue } from "@/types";
    import { useBlockSettings, useBlock } from "../../../../../hooks";
    import { EchartVisualizationBlockDef } from "../../../echart-visualization-blocks/VisualizationBlock";
    import { BaseSettingSection } from "../../../../block-settings";
    import { GANTT_CHART } from "../../../echart-visualization-blocks/Visualization.constants";
    import { BlockDef } from "../../../../../store";
    //Sub container with column based field setting
    const StyledSubContainer = styled("div")(({}) => ({
        padding: "0.5rem",
        display: "flex",
        flexDirection: "column",
    }));
    //Applied custom style container
    const StyledAppliedContainer = styled("div")(() => ({
        border: "1px solid grey",
        borderRadius: 9,
        padding: "10px",
    }));
    //Styled span with custom background color for mentioning color selected for a custom symbol
    const StyledSpan = styled("span")<{ backgroundColor: string }>((props) => ({
        backgroundColor: props.backgroundColor ?? "",
        padding: "3px",
        borderRadius: "50px",
        width: "15px",
        height: "15px",
        display: "flex",
    }));
    //Main container for symbol section with padding
    const StyledMainContainer = styled("div")(({}) => ({
        padding: "0.5rem",
        borderBottom: "1px solid #E6E6E6",
    }));
    //Default custom style
    const INITIAL_CUSTOM_STYLE = {
        dimension: "",
        symbol: "",
        symbolSize: 5,
        symbolColorSelected: false,
        symbolColor: "",
        dimensionInstance: [],
    };
    export const CustomizeSymbol = observer(
        <D extends BlockDef = BlockDef>({ id, path }) => {
            const { data, setData } =
                useBlockSettings<EchartVisualizationBlockDef>(id); //block data to manage settings
            const [customizeSymbolData, setCustomizeSymbolData] =
                useState(INITIAL_CUSTOM_STYLE); //customize symbol component state
            const [appliedSymbolData, setAppliedSymbolData] = useState([]); //applied symbol data state
            const [dimensionList, setDimensionList] = useState([]); //dimension list to select for custom symbol
            const [dimensionSelected, setDimensionSelected] = useState(""); //selected dimention in the dimension list
            const [dimensionInstance, setDimensionInstance] = useState({
                startdate: [],
                enddate: [],
                milestone: [],
            }); // dimension instance data for the available dimensions
            const [editingInstanceIndex, setEditingInstanceIndex] = useState(-1); //if editing this will be set with some index greater than -1
            //List of symbols to select for custom symbol
            const symbolList = [
                { label: "Circle", value: "circle" },
                { label: "Empty Circle", value: "emptycircle" },
                { label: "Rectangle", value: "rectangle" },
                { label: "Round Rectangle", value: "roundrectangle" },
                { label: "Triangle", value: "triangle" },
                { label: "Diamond", value: "diamond" },
                { label: "Pin", value: "pin" },
                { label: "Arrow", value: "arrow" },
            ];
            //Computed value from the block data
            const computedValue = useMemo(() => {
                return computed(() => {
                    if (!data) {
                        return "";
                    }
                    const v = getValueByPath(data, "option");
                    if (typeof v === "undefined") {
                        return "";
                    } else if (typeof v === "string") {
                        return v;
                    }
                    return JSON.stringify(v, null, 2);
                });
            }, [data, "option"]).get();
            //useeffect to update initial state if available
            useEffect(() => {
                let option = JSON.parse(computedValue);
                let columnDetails = option["customSettings"]?.["columnDetails"];
                if (columnDetails) {
                    let startDate = {},
                        endDate = {},
                        milestone = {};
                    startDate = {
                        ...columnDetails["startdate"],
                        ["currentKey"]: "startdate",
                    };
                    endDate = {
                        ...columnDetails["enddate"],
                        ["currentKey"]: "enddate",
                    };
                    milestone = columnDetails.hasOwnProperty("milestone")
                        ? {
                              ...columnDetails["milestone"],
                              ["currentKey"]: "milestone",
                          }
                        : {};
                    let finalData = [];
                    if (Object.keys(startDate).length) {
                        finalData.push(startDate);
                    }
                    if (Object.keys(endDate).length) {
                        finalData.push(endDate);
                    }
                    if (Object.keys(milestone).length) {
                        finalData.push(milestone);
                    }
                    setDimensionList((prevDimensionList) => {
                        return [...finalData];
                    });
                }
                let existingOption = option["customSettings"]["gantttools"];
                let existingOptionList = customizeSymbolData;
                if (existingOption?.["dimension"]) {
                    existingOptionList["dimension"] = existingOption["dimension"];
                }
                if (existingOption?.["symbol"]) {
                    existingOptionList["symbol"] = existingOption["symbol"];
                }
                if (existingOption?.["symbolSize"]) {
                    existingOptionList["symbolSize"] = existingOption["symbolSize"];
                }
                if (existingOption?.["symbolColor"]) {
                    existingOptionList["symbolColor"] =
                        existingOption["symbolColor"];
                }
                if (existingOption?.["symbolColorSelected"]) {
                    existingOptionList["symbolColorSelected"] =
                        existingOption["symbolColorSelected"];
                }
                setCustomizeSymbolData((prevCustomizeSymbolData) => {
                    return {
                        ...prevCustomizeSymbolData,
                        ...existingOptionList,
                    };
                });
                let seriesIndex = option["series"].findIndex((item) =>
                    item.hasOwnProperty("chartrendered"),
                );
                let mileStoneIndex = option["series"].findIndex((item) =>
                    item.hasOwnProperty("milestonerendered"),
                );
                let startDateData = [];
                let endDateData = [];
                let mileStone = [];
                if (seriesIndex >= 0) {
                    startDateData =
                        option["series"][seriesIndex]?.["data"]?.map(
                            (item, index) => {
                                return item.value[0];
                            },
                        ) || [];
                    endDateData =
                        option["series"][seriesIndex]?.["data"]?.map(
                            (item, index) => {
                                return item.value[2];
                            },
                        ) || [];
                    mileStone =
                        option["series"][mileStoneIndex]?.["data"]?.map(
                            (item, index) => {
                                return item.mileStoneOriginalDate;
                            },
                        ) || [];
                    setDimensionInstance((prevDimensionInstance) => {
                        return {
                            ["startdate"]: startDateData,
                            ["enddate"]: endDateData,
                            ["milestone"]: mileStone,
                        };
                    });
                }
                //if applied symbol data is available then set the applied symbol data
                let customizeSettings =
                    option["customSettings"]["gantttools"]?.["customizeSymbol"] ||
                    [];
                setAppliedSymbolData((prevAppliedSymbol) => customizeSettings);
            }, []);
            function convertTimeZone(date) {
                let currentTimezone =
                    Intl.DateTimeFormat().resolvedOptions().timeZone;
                let dateConvertedToTimeZone = new Date(date).toLocaleString(
                    "en-US",
                    {
                        timeZone: currentTimezone,
                    },
                );
                return (
                    new Date(dateConvertedToTimeZone).getFullYear() +
                    "-" +
                    (new Date(dateConvertedToTimeZone).getMonth() + 1 < 10
                        ? "0" + (new Date(dateConvertedToTimeZone).getMonth() + 1)
                        : new Date(dateConvertedToTimeZone).getMonth() + 1) +
                    "-" +
                    (new Date(dateConvertedToTimeZone).getDate() < 10
                        ? "0" + new Date(dateConvertedToTimeZone).getDate()
                        : new Date(dateConvertedToTimeZone).getDate())
                );
            }
            //update fields function will be called when a field is changed
            function updateFields(e, field, directValue = undefined) {
                setCustomizeSymbolData((prevSymbolData) => {
                    return {
                        ...prevSymbolData,
                        [field]: directValue ?? e.target.value,
                    };
                });
                //if a dimension field is changed, then dimension selected is also updated
                if (field === "dimension") {
                    let value = e.target.value;
                    let dimensionSelected = dimensionList.find(
                        (item) => item.selector === value,
                    );
                    if (dimensionSelected.hasOwnProperty("currentKey")) {
                        setDimensionSelected((prevDimensionSelected) => {
                            return dimensionSelected["currentKey"];
                        });
                    }
                }
            }
            //updated dimension list with label and value
            const dimensionListUpdated =
                dimensionList.map((item, index) => ({
                    label: item.name,
                    value: item.selector,
                })) || [];
            //symbol color switch
            const showSymbolColor = customizeSymbolData.symbolColorSelected
                ? true
                : false;
            //based on the selected dimension, the instance values are updated and rendered in instance selection field
            const dimensionInstanceToRender =
                dimensionInstance[dimensionSelected]?.map((item, index) => {
                    return item;
                }) || [];
            //dimension name selected based on the selected dimension as selected dimension will have selector value
            const dimensionNameSelected =
                dimensionList.find(
                    (item) => item.selector === customizeSymbolData.dimension,
                )?.name || customizeSymbolData.dimension;
            //update chart data when a field is changed
            function updateChartData() {
                let option = JSON.parse(computedValue);
                //if customize symbol is newly being created
                if (editingInstanceIndex === -1) {
                    option["customSettings"] = {
                        ...option["customSettings"],
                        ["gantttools"]: {
                            ...option["customSettings"]["gantttools"],
                            ["customizeSymbol"]: option["customSettings"][
                                "gantttools"
                            ]?.["customizeSymbol"]
                                ? [
                                      ...option["customSettings"]["gantttools"][
                                          "customizeSymbol"
                                      ],
                                      {
                                          ["dimension"]:
                                              customizeSymbolData.dimension,
                                          ["symbol"]: customizeSymbolData.symbol,
                                          ["symbolSize"]:
                                              customizeSymbolData.symbolSize,
                                          ["symbolColor"]:
                                              customizeSymbolData.symbolColor ||
                                              GANTT_CHART.MILESTONE_COLOR,
                                          ["symbolColorSelected"]:
                                              customizeSymbolData.symbolColorSelected,
                                          ["dimensionSelected"]: dimensionSelected,
                                          ["dimensionValues"]:
                                              customizeSymbolData.dimensionInstance,
                                      },
                                  ]
                                : [
                                      {
                                          ["dimension"]:
                                              customizeSymbolData.dimension,
                                          ["symbol"]: customizeSymbolData.symbol,
                                          ["symbolSize"]:
                                              customizeSymbolData.symbolSize,
                                          ["symbolColor"]:
                                              customizeSymbolData.symbolColor ||
                                              GANTT_CHART.MILESTONE_COLOR,
                                          ["symbolColorSelected"]:
                                              customizeSymbolData.symbolColorSelected,
                                          ["dimensionSelected"]: dimensionSelected,
                                          ["dimensionValues"]:
                                              customizeSymbolData.dimensionInstance,
                                      },
                                  ],
                        },
                    };
                } else {
                    //if the instance is selected for editing, then respective index in the appliedSymbolData is used to update records
                    if (
                        option["customSettings"]["gantttools"].hasOwnProperty(
                            "customizeSymbol",
                        )
                    ) {
                        if (
                            option["customSettings"]["gantttools"][
                                "customizeSymbol"
                            ]?.[editingInstanceIndex]
                        ) {
                            option["customSettings"]["gantttools"][
                                "customizeSymbol"
                            ][editingInstanceIndex] = {
                                ["dimension"]: customizeSymbolData.dimension,
                                ["symbol"]: customizeSymbolData.symbol,
                                ["symbolSize"]: customizeSymbolData.symbolSize,
                                ["symbolColor"]:
                                    customizeSymbolData.symbolColor ||
                                    GANTT_CHART.MILESTONE_COLOR,
                                ["symbolColorSelected"]:
                                    customizeSymbolData.symbolColorSelected,
                                ["dimensionSelected"]: dimensionSelected,
                                ["dimensionValues"]:
                                    customizeSymbolData.dimensionInstance,
                            };
                        }
                    }
                }
                setTimeout(() => {
                    try {
                        setData(
                            "option",
                            option as PathValue<D["data"], typeof path>,
                        );
                        //updating the applied symbol data only when the state is updated
                        let appliedSymbolDataList = appliedSymbolData;
                        if (
                            editingInstanceIndex > -1 &&
                            appliedSymbolDataList?.[editingInstanceIndex]
                        ) {
                            appliedSymbolDataList[editingInstanceIndex] = {
                                ["dimension"]: customizeSymbolData.dimension,
                                ["symbol"]: customizeSymbolData.symbol,
                                ["symbolSize"]: customizeSymbolData.symbolSize,
                                ["symbolColor"]:
                                    customizeSymbolData.symbolColor ||
                                    GANTT_CHART.MILESTONE_COLOR,
                                ["symbolColorSelected"]:
                                    customizeSymbolData.symbolColorSelected,
                                ["dimensionSelected"]: dimensionSelected,
                                ["dimensionValues"]:
                                    customizeSymbolData.dimensionInstance,
                            };
                        } else {
                            appliedSymbolDataList.push({
                                ["dimension"]: customizeSymbolData.dimension,
                                ["symbol"]: customizeSymbolData.symbol,
                                ["symbolSize"]: customizeSymbolData.symbolSize,
                                ["symbolColor"]:
                                    customizeSymbolData.symbolColor ||
                                    GANTT_CHART.MILESTONE_COLOR,
                                ["symbolColorSelected"]:
                                    customizeSymbolData.symbolColorSelected,
                                ["dimensionSelected"]: dimensionSelected,
                                ["dimensionValues"]:
                                    customizeSymbolData.dimensionInstance,
                            });
                        }
                        setAppliedSymbolData((prevAppliedSymbol) => {
                            return appliedSymbolDataList;
                        });
                        setCustomizeSymbolData((prevCustomizeData) => {
                            return INITIAL_CUSTOM_STYLE;
                        });
                    } catch (e) {}
                }, 300);
            }
            //removing the applied data
            function deleteAppliedData(index) {
                let updatedAppliedData = appliedSymbolData;
                let option = JSON.parse(computedValue);
                updatedAppliedData = updatedAppliedData.filter(
                    (item, itemIndex) => itemIndex !== index,
                );
                setAppliedSymbolData((prevSymbolData) => {
                    return updatedAppliedData;
                });
                if (option["customSettings"]["gantttools"]?.["customizeSymbol"]) {
                    let filteredData =
                        option["customSettings"]["gantttools"]["customizeSymbol"];
                    filteredData = filteredData.filter(
                        (filteritem, filterindex) => filterindex !== index,
                    );
    
                    option["customSettings"]["gantttools"]["customizeSymbol"] =
                        filteredData;
                    setTimeout(() => {
                        try {
                            setData(
                                "option",
                                option as PathValue<D["data"], typeof path>,
                            );
                        } catch (e) {
                            console.log(e);
                        }
                    }, 300);
                }
            }
            //when the existing instance under applied symbol is clicked, then respective data is loaded into cusomizeSymbolData for editing
            function applyToCurrentCustom(index) {
                if (appliedSymbolData?.[index]) {
                    setCustomizeSymbolData((prevCustSymbol) => {
                        return {
                            ...appliedSymbolData[index],
                            ["dimensionInstance"]:
                                appliedSymbolData[index].dimensionValues,
                        };
                    });
                    setEditingInstanceIndex((prevEditingInstanceIndex) => {
                        return index;
                    });
                }
            }
            //reset to Initial state will reset customize symbol component to initial state
            function resetToInitialState() {
                setCustomizeSymbolData((prevCustomizeData) => {
                    return {
                        dimension: "",
                        symbol: "",
                        symbolSize: 5,
                        symbolColorSelected: false,
                        symbolColor: "",
                        dimensionInstance: [],
                    };
                });
                setEditingInstanceIndex((prevEditingInstanceIndex) => {
                    return -1;
                });
                setAppliedSymbolData((prevAppliedSymbol) => {
                    return [];
                });
                let option = JSON.parse(computedValue);
                if (option["customSettings"]["gantttools"]?.["customizeSymbol"]) {
                    option["customSettings"]["gantttools"]["customizeSymbol"] = [];
                    setTimeout(() => {
                        try {
                            setData(
                                "option",
                                option as PathValue<D["data"], typeof path>,
                            );
                        } catch (e) {
                            console.log(e);
                        }
                    }, 300);
                }
            }
            //updated instances data
            let updatedInstances = appliedSymbolData.map((item, index) => {
                return {
                    label:
                        "Instances of " +
                            dimensionList.find(
                                (dimItem) => dimItem.selector === item.dimension,
                            )?.["name"] || item,
                    itemData: index,
                    itemColor: item.symbolColor,
                };
            });
            return (
                <StyledMainContainer>
                    <StyledSubContainer>
                        <label htmlFor="applied-custom-style">
                            Applied (Add Multiple Symbol)
                        </label>
                        {updatedInstances.length > 0 && (
                            <StyledAppliedContainer>
                                {updatedInstances.length > 0 &&
                                    updatedInstances.map((item, index) => (
                                        <Chip
                                            size="small"
                                            label={item.label}
                                            onClick={() =>
                                                applyToCurrentCustom(item.itemData)
                                            }
                                            onDelete={() =>
                                                deleteAppliedData(item.itemData)
                                            }
                                            icon={
                                                <StyledSpan
                                                    backgroundColor={item.itemColor}
                                                ></StyledSpan>
                                            }
                                        />
                                    ))}
                            </StyledAppliedContainer>
                        )}
                        {updatedInstances.length == 0 && (
                            <TextField
                                placeholder="No Symbol Applied"
                                disabled={true}
                            />
                        )}
                    </StyledSubContainer>
                    {dimensionListUpdated.length > 0 && (
                        <StyledSubContainer>
                            <label htmlFor="dimension">Select Dimension</label>
                            <Select
                                id="dimension-field"
                                label="Select Dimension Field"
                                SelectProps={{
                                    multiple: false,
                                }}
                                value={customizeSymbolData.dimension}
                                onChange={(e) => {
                                    updateFields(e, "dimension");
                                }}
                            >
                                <Select.Item value="-1">All Nodes</Select.Item>
                                {dimensionListUpdated.length &&
                                    dimensionListUpdated.map((item, index) => (
                                        <Select.Item value={item.value} key={index}>
                                            {item.label}
                                        </Select.Item>
                                    ))}
     ...

    @QodoAI-Agent
    Copy link
    Copy Markdown

    PR Code Suggestions ✨

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Update effect dependencies

    Include computedValue in the dependency array and wrap JSON.parse in a try/catch
    block to ensure reliable JSON parsing.

    libs/renderer/src/components/block-defaults/echart-visualization-block/variant/Gantt/GanttDisplayValueLabels.tsx [42-55]

     useEffect(() => {
    -    let parsedJson = JSON.parse(computedValue);
    -    if (
    -        parsedJson["customSettings"]?.["gantttools"]?.["showDisplayValueLabels"]
    -    ) {
    -        setDisplayValueLabelsData(parsedJson["customSettings"]["gantttools"]["showDisplayValueLabels"]);
    +    try {
    +        const parsedJson = JSON.parse(computedValue);
    +        if (parsedJson["customSettings"]?.["gantttools"]?.["showDisplayValueLabels"]) {
    +            setDisplayValueLabelsData(parsedJson["customSettings"]["gantttools"]["showDisplayValueLabels"]);
    +        }
    +    } catch (error) {
    +        console.error("Error parsing computedValue:", error);
         }
    -}, []);
    +}, [computedValue]);
    Suggestion importance[1-10]: 6

    __

    Why: The suggestion adds robust error handling and proper dependency management by including computedValue, which improves the effect’s reliability while being a modest enhancement.

    Low
    Ensure effect reactivity

    Add computedValue as a dependency and wrap the JSON parsing in a try/catch block so
    that state updates occur consistently when data changes.

    libs/renderer/src/components/block-defaults/echart-visualization-block/variant/Gantt/GanttFiscal.tsx [122-151]

     useEffect(() => {
    -    let option = JSON.parse(computedValue);
    -    // updating fiscalData based on option fields...
    -    if (option["customSettings"]?.["gantttools"]?.["enableFiscalAxis"]) {
    -        fiscalDataForUpdate.enableFiscalAxis = option["customSettings"]["gantttools"]["enableFiscalAxis"];
    -    }
    -    // further state updates...
    -    setFiscalData((prevFiscalData) => {
    -        return {
    +    try {
    +        let option = JSON.parse(computedValue);
    +        let fiscalDataForUpdate = { ...fiscalData };
    +        if (option["customSettings"]?.["gantttools"]?.["enableFiscalAxis"]) {
    +            fiscalDataForUpdate.enableFiscalAxis = option["customSettings"]["gantttools"]["enableFiscalAxis"];
    +        }
    +        // update other fields in a similar manner...
    +        setFiscalData((prevFiscalData) => ({
                 ...prevFiscalData,
                 ...fiscalDataForUpdate,
    -        };
    -    });
    -}, []);
    +        }));
    +    } catch (error) {
    +        console.error("Error parsing computedValue:", error);
    +    }
    +}, [computedValue]);
    Suggestion importance[1-10]: 6

    __

    Why: By adding computedValue as a dependency and wrapping JSON.parse in a try/catch block, the suggestion enhances the reactivity and robustness of the effect, addressing a minor consistency improvement.

    Low
    General
    Optimize date conversion

    Optimize timezone conversion by caching the intermediate date value to avoid
    repeating new Date() calls.

    libs/renderer/src/components/block-defaults/echart-visualization-block/variant/Gantt/GanttTargetLine.tsx [211-229]

     function convertTimeZone(date) {
    -    let currentTimezone =
    -        Intl.DateTimeFormat().resolvedOptions().timeZone;
    -    let dateConvertedToTimeZone = new Date(date).toISOString().split("T")[0];
    -    return (
    -        new Date(dateConvertedToTimeZone).getFullYear() +
    -        "-" +
    -        (new Date(dateConvertedToTimeZone).getMonth() + 1 < 10
    -            ? "0" + (new Date(dateConvertedToTimeZone).getMonth() + 1)
    -            : new Date(dateConvertedToTimeZone).getMonth() + 1) +
    -        "-" +
    -        (new Date(dateConvertedToTimeZone).getDate() < 10
    -            ? "0" + new Date(dateConvertedToTimeZone).getDate()
    -            : new Date(dateConvertedToTimeZone).getDate())
    -    );
    +    const isoDate = new Date(date).toISOString().split("T")[0];
    +    const dateObj = new Date(isoDate);
    +    const year = dateObj.getFullYear();
    +    const month = (dateObj.getMonth() + 1).toString().padStart(2, "0");
    +    const day = dateObj.getDate().toString().padStart(2, "0");
    +    return `${year}-${month}-${day}`;
     }
    Suggestion importance[1-10]: 6

    __

    Why: Caching the intermediate date value and using padStart improves readability and slightly optimizes performance, making this a modest but valid improvement.

    Low

    @bannaarisamy-shanmugham-kanini
    Copy link
    Copy Markdown
    Contributor Author

    Hi @johbaxter , i have added block,json with this PR:
    block.json

    @QodoAI-Agent
    Copy link
    Copy Markdown

    PR Code Suggestions ✨

    No code suggestions found for the PR.

    @johbaxter johbaxter merged commit b65ea9e into dev Apr 4, 2025
    3 checks passed
    @johbaxter johbaxter deleted the Echart-Bar-Pie-Scatter-chart branch April 4, 2025 18:24
    @github-actions
    Copy link
    Copy Markdown

    github-actions bot commented Apr 4, 2025

    @CodiumAI-Agent /update_changelog

    @QodoAI-Agent
    Copy link
    Copy Markdown

    Changelog updates: 🔄

    2025-04-04

    Added

    • Gantt Chart block with core features and customizations (fiscal axis, target date, group view, symbol customization, and events).

    to commit the new content to the CHANGELOG.md file, please type:
    '/update_changelog --pr_update_changelog.push_changelog_changes=true'

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    None yet

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    Create Gantt Chart

    3 participants