diff --git a/next.config.js b/next.config.js index 1e801f0b..fbffc9ab 100644 --- a/next.config.js +++ b/next.config.js @@ -1,15 +1,16 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - webpack(config, { isServer }) { - // Add rule to handle SVG imports - config.module.rules.push({ - test: /\.svg$/, - use: ['@svgr/webpack', 'url-loader'], - }); - config.resolve.alias.canvas = false; - - return config; - }, - }; - - module.exports = nextConfig; + webpack(config, { isServer }) { + // Add rule to handle SVG imports + config.module.rules.push({ + test: /\.svg$/, + use: ['@svgr/webpack', 'url-loader'], + }); + config.resolve.alias.canvas = false; + + return config; + }, + cacheHandler: require.resolve('next/dist/server/lib/incremental-cache/file-system-cache.js'), +}; + +module.exports = nextConfig; diff --git a/package.json b/package.json index 7c11e178..d30fba38 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,6 @@ "leaflet-defaulticon-compatibility": "^0.1.2", "leaflet-geosearch": "^4.0.0", "lucide-react": "^0.454.0", - "mapbox-gl": "^3.7.0", - "mapbox-gl-leaflet": "^0.0.16", "next": "14.2.10", "next-themes": "^0.2.1", "nextui-cli": "^0.3.4", diff --git a/src/app/download-portal/loading.tsx b/src/app/download-portal/loading.tsx new file mode 100644 index 00000000..c2a6192a --- /dev/null +++ b/src/app/download-portal/loading.tsx @@ -0,0 +1,23 @@ +import AccordionContainer from '@/components/Accordions/AccordionContainer'; +import { TITLE } from '@/domain/entities/download/Country'; + +export default function Loading() { + const loading = true; + + return ( +
+

Download Portal

+ +
+ ); +} diff --git a/src/app/elements/page.tsx b/src/app/elements/page.tsx index 5c85d07a..8db4e253 100644 --- a/src/app/elements/page.tsx +++ b/src/app/elements/page.tsx @@ -4,9 +4,11 @@ import { useState } from 'react'; import AccordionContainer from '@/components/Accordions/AccordionContainer'; import { CustomButton } from '@/components/Buttons/CustomButton'; +import { CategoricalChart } from '@/components/Charts/CategoricalChart'; import { LineChart } from '@/components/Charts/LineChart'; import MapSkeleton from '@/components/Map/MapSkeleton'; import SearchBar from '@/components/Search/SearchBar'; +import { CategoricalChartData } from '@/domain/entities/charts/CategoricalChartData.ts'; import { LineChartData } from '@/domain/entities/charts/LineChartData.ts'; import { LineChartDataType } from '@/domain/enums/LineChartDataType.ts'; import AccordionsOperations from '@/operations/accordions/AccordionOperations'; @@ -35,23 +37,6 @@ export default async function Elements() { ], }; - const simpleLineChartData: LineChartData = { - type: LineChartDataType.LINE_CHART_DATA, - xAxisType: 'linear', - yAxisLabel: 'yield', - lines: [ - { - name: 'Category A', - dataPoints: [ - { x: 0, y: 1 }, - { x: 1, y: 2 }, - { x: 2, y: 4 }, - { x: 3, y: 8 }, - ], - }, - ], - }; - const maxedOutLineChartData: LineChartData = { type: LineChartDataType.LINE_CHART_DATA, xAxisType: 'linear', @@ -144,50 +129,19 @@ export default async function Elements() { ], }; - const multiplePredictionsDummyChartData: LineChartData = { - type: LineChartDataType.LINE_CHART_DATA, - xAxisType: 'linear', + const categoricalDummyChartData1: CategoricalChartData = { yAxisLabel: 'Mill', - predictionVerticalLineX: 3, - lines: [ + categories: [ { name: 'Category A', - showRange: true, - dataPoints: [ - { x: 0, y: 4, yRangeMin: 3.5, yRangeMax: 5 }, - { x: 1, y: 3, yRangeMin: 2, yRangeMax: 4 }, - { x: 2, y: 4, yRangeMin: 3.5, yRangeMax: 4.5 }, - { x: 3, y: 8, yRangeMin: 7.5, yRangeMax: 8.5 }, - ], + dataPoint: { y: 5 }, }, { - name: 'Prediction 1', - prediction: true, - dataPoints: [ - { x: 0, y: 4 }, - { x: 1, y: 7 }, - { x: 2, y: 5 }, - { x: 3, y: 7 }, - { x: 4, y: 8 }, - { x: 5, y: 5 }, - { x: 6, y: 6 }, - ], - }, - { - name: 'Prediction 2', - prediction: true, - dataPoints: [ - { x: 0, y: 6 }, - { x: 1, y: 5 }, - { x: 2, y: 6 }, - { x: 3, y: 7 }, - { x: 4, y: 7 }, - { x: 5, y: 8 }, - ], + name: 'Category B', + dataPoint: { y: 8 }, }, ], }; - return (
@@ -204,17 +158,14 @@ export default async function Elements() { />
-
- -
@@ -222,16 +173,13 @@ export default async function Elements() { title="Maxed Out Line Chart" description="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor onsetetur sadipscing elitr." data={maxedOutLineChartData} - expandable - xAxisSlider - barChartSwitch />
- +
- +
diff --git a/src/app/error.tsx b/src/app/error.tsx index 6847fc17..64f60e16 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -2,7 +2,10 @@ import { useEffect } from 'react'; -export default function Error({ error, reset }: { error: Error; reset: () => void }) { +import { CustomButton } from '@/components/Buttons/CustomButton'; +import { Topbar } from '@/components/Topbar/Topbar'; + +export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) { useEffect(() => { // Log the error to an error reporting service /* eslint-disable no-console */ @@ -10,17 +13,24 @@ export default function Error({ error, reset }: { error: Error; reset: () => voi }, [error]); return ( -
-

Something went wrong!

- +
+ +
+
+

An Error Occurred

+

Please try again or go back to the Home page.

+
+
+ reset()}> + Try again + + + + Go to Home page + + +
+
); } diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx new file mode 100644 index 00000000..c3655215 --- /dev/null +++ b/src/app/not-found.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { CustomButton } from '@/components/Buttons/CustomButton'; +import { Topbar } from '@/components/Topbar/Topbar'; + +export default function NotFound() { + return ( +
+ +
+
+

Ooops

+

The requested page could not be found.

+
+ +
+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 3350467d..5a362971 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,7 +2,7 @@ import AccordionModal from '@/components/Accordions/AccordionModal'; import { AlertsMenuWrapper } from '@/components/AlertsMenu/AlertsMenuWrapper'; import Chatbot from '@/components/Chatbot/Chatbot'; import HungerAlertLoader from '@/components/HungerAlert/HungerAlertLoader'; -import MapLegend from '@/components/Legend/MapLegend'; +import MapLegendLoader from '@/components/Legend/MapLegendLoader'; import MapLoader from '@/components/Map/MapLoader'; import { Sidebar } from '@/components/Sidebar/Sidebar'; import container from '@/container'; @@ -28,7 +28,7 @@ export default async function Home() { - + ); diff --git a/src/app/providers.tsx b/src/app/providers.tsx index 2ffa0530..200865a1 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -13,7 +13,6 @@ import { AccordionsModalProvider } from '@/domain/contexts/AccodionsModalContext import { SelectedAlertProvider } from '@/domain/contexts/SelectedAlertContext'; import { SelectedCountryIdProvider } from '@/domain/contexts/SelectedCountryIdContext'; import { SelectedMapProvider } from '@/domain/contexts/SelectedMapContext'; -import { SelectedMapVisibilityProvider } from '@/domain/contexts/SelectedMapVisibilityContext'; import { SidebarProvider } from '@/domain/contexts/SidebarContext'; import { SnackbarProvider } from '@/domain/contexts/SnackbarContext'; @@ -30,17 +29,15 @@ export function Providers({ children, themeProps }: ProvidersProps) { - - - - - - {children} - - - - - + + + + + {children} + + + + diff --git a/src/components/AlertsMenu/AlertsMenuWrapper.tsx b/src/components/AlertsMenu/AlertsMenuWrapper.tsx index b1657739..86695e74 100644 --- a/src/components/AlertsMenu/AlertsMenuWrapper.tsx +++ b/src/components/AlertsMenu/AlertsMenuWrapper.tsx @@ -11,7 +11,7 @@ export function AlertsMenuWrapper() { return null; } return ( -
+
); diff --git a/src/components/Charts/CategoricalChart.tsx b/src/components/Charts/CategoricalChart.tsx new file mode 100644 index 00000000..24fe166a --- /dev/null +++ b/src/components/Charts/CategoricalChart.tsx @@ -0,0 +1,77 @@ +'use client'; + +import Highcharts from 'highcharts'; +import { useTheme } from 'next-themes'; +import { useEffect, useState } from 'react'; + +import { ChartContainer } from '@/components/Charts/helpers/ChartContainer'; +import { ChartType } from '@/domain/enums/ChartType.ts'; +import CategoricalChartProps from '@/domain/props/CategoricalChartProps'; +import CategoricalChartOperations from '@/operations/charts/CategoricalChartOperations.ts'; + +/** + * The `CategoricalChart` component is a box that primarily renders a title, description text, and a bar chart. + * It should be used to plot categorical data. For continues data please use the `LineChart` component. + * This component has a width of 100%, so it adjusts to the width of its parent element in which it is used. + * The height of the entire box depends on the provided text, while the chart itself has a fixed height. + * It also provides the option to open the chart in a full-screen modal, where one can download the data as well. + * + * @param data the actual data to be used in the chart + * @param title chart title (optional) + * @param description chart description text (optional) + * @param small when selected, all components in the line chart box become slightly smaller (optional) + * @param noPadding when selected, the main box has no padding on all sides (optional) + * @param transparentBackground when selected, the background of the entire component is transparent (optional) + * @param disableExpandable when selected, the functionality to open the chart in a larger modal is disabled (optional) + * @param disablePieChartSwitch when selected, the functionality to switch to a pie chart is disabled (optional) + * @param disableDownload when selected, the functionality to download the chart is disabled (optional) + */ +export function CategoricalChart({ + data, + title, + description, + small, + noPadding, + transparentBackground, + disableExpandable, + disablePieChartSwitch, + disableDownload, +}: CategoricalChartProps) { + const { theme } = useTheme(); + + // build chart options for 'Highcharts' + const defaultChartOptions: Highcharts.Options = CategoricalChartOperations.getHighChartOptions(data); + + // controlling if a bar or pie chart is rendered; bar chart is the default + const [showPieChart, setShowPieChart] = useState(false); + const [chartOptions, setChartOptions] = useState(defaultChartOptions); + + // handling the bar and pie chart switch and the theme switch; + useEffect(() => { + setChartOptions(CategoricalChartOperations.getHighChartOptions(data, showPieChart)); + }, [showPieChart, theme]); + + const alternativeSwitchButtonProps = disablePieChartSwitch + ? undefined + : { + defaultChartType: ChartType.COLUMN, + alternativeChartType: ChartType.PIE, + showAlternativeChart: showPieChart, + setShowAlternativeChart: setShowPieChart, + }; + + return ( + + ); +} diff --git a/src/components/Charts/LineChart.tsx b/src/components/Charts/LineChart.tsx index 4dd136cf..52891746 100644 --- a/src/components/Charts/LineChart.tsx +++ b/src/components/Charts/LineChart.tsx @@ -1,34 +1,20 @@ 'use client'; -import { Button } from '@nextui-org/button'; -import { useDisclosure } from '@nextui-org/modal'; import Highcharts from 'highcharts'; -import Exporting from 'highcharts/modules/exporting'; -import OfflineExporting from 'highcharts/modules/offline-exporting'; -import HighchartsReact from 'highcharts-react-official'; -import { Maximize4 } from 'iconsax-react'; import { useTheme } from 'next-themes'; import { useEffect, useState } from 'react'; -import LineChartBarLineSwitchButton from '@/components/Charts/helpers/LineChartBarLineSwitchButton'; -import LineChartSliderButton from '@/components/Charts/helpers/LineChartSliderButton'; -import LineChartXAxisSlider from '@/components/Charts/helpers/LineChartXAxisSlider'; -import { LineChartModal } from '@/components/Charts/LineChartModal'; -import { Tooltip } from '@/components/Tooltip/Tooltip'; +import { ChartContainer } from '@/components/Charts/helpers/ChartContainer'; import { LineChartData } from '@/domain/entities/charts/LineChartData'; +import { ChartType } from '@/domain/enums/ChartType.ts'; import LineChartProps from '@/domain/props/LineChartProps'; import LineChartOperations from '@/operations/charts/LineChartOperations'; -// initialize the exporting module -if (typeof Highcharts === 'object') { - Exporting(Highcharts); - OfflineExporting(Highcharts); -} - /** * The LineChart component is a box that primarily renders a title, description text, and a line chart. + * It should be used to plot categorical data. For continues data please use the `CategoricalChart` component. * This component has a width of 100%, so it adjusts to the width of its parent element in which it is used. - * The height of the line chart box depends on the provided text, while the chart itself has a fixed height. + * The height of the entire box depends on the provided text, while the chart itself has a fixed height. * It also provides the option to open the chart in a full-screen modal, where one can download the data as well. * * The data to be displayed in the chart can be provided in different types (see `LineChartProps.data`). @@ -38,44 +24,34 @@ if (typeof Highcharts === 'object') { * 1. Define an interface and add it to `LineChartProps.data`. * 2. Add another switch case in `LineChartOperations.convertToLineChartData` to convert the new interface to `LineChartData`. * + * @param data the actual data to be used in the chart * @param title chart title (optional) * @param description chart description text (optional) - * @param expandable when selected, the user is given the option to open the chart in a larger modal (optional) - * @param barChartSwitch when selected, the user is given the option to switch to a bar chart (optional) - * @param xAxisSlider when selected, the user is given the option to change the x-axis range via a slider (optional) * @param small when selected, all components in the line chart box become slightly smaller (optional) - * @param roundLines when selected, all plotted lines will be rounded (optional) * @param noPadding when selected, the main box has no padding on all sides (optional) * @param transparentBackground when selected, the background of the entire component is transparent (optional) - * @param data the actual data to be used in the chart + * @param disableExpandable when selected, the functionality to open the chart in a larger modal is disabled (optional) + * @param disableBarChartSwitch when selected, the functionality to switch to a bar chart is disabled (optional) + * @param disableXAxisSlider when selected, the functionality to change the x-axis range via a slider is disabled (optional) + * @param disableDownload when selected, the functionality to download the chart is disabled (optional) */ export function LineChart({ + data, title, description, - expandable, - barChartSwitch, - xAxisSlider, small, - roundLines, noPadding, transparentBackground, - data, + disableExpandable, + disableBarChartSwitch, + disableXAxisSlider, + disableDownload, }: LineChartProps) { - const TITLE_TEXT_SIZE = small ? 'text-sm' : 'text-md'; - const DESCRIPTION_TEXT_SIZE = small ? 'text-tiny' : 'text-sm'; - const CHART_HEIGHT = small ? 12 : 16; - const ICON_BUTTON_SIZE = small ? 3 : 4; - const HEADER_PADDING = title ? 3 : 0; - const MAIN_BOX_PADDING_FACTOR = noPadding ? 0 : 1; - - // full screen modal state handling - const { isOpen, onOpen, onClose, onOpenChange } = useDisclosure(); - // the 'chartOptions' are dependent on the theme const { theme } = useTheme(); // convert data to `LineChartData` and build chart options for 'Highcharts' (line and bar chart) const lineChartData: LineChartData = LineChartOperations.convertToLineChartData(data); - const lineChartOptions: Highcharts.Options = LineChartOperations.getHighChartOptions(lineChartData, roundLines); + const lineChartOptions: Highcharts.Options = LineChartOperations.getHighChartOptions(lineChartData); // the `selectedXAxisRange` saves the to be rendered x-axis range of the chart // can be changed using the `LinkeChartXAxisSlider` if the param `xAxisSlider==true` @@ -85,8 +61,6 @@ export function LineChart({ // controlling if a line or bar chart is rendered; line chart is the default const [showBarChart, setShowBarChart] = useState(false); const [chartOptions, setChartOptions] = useState(lineChartOptions); - // handling the x-axis range slider visibility - const [showXAxisSlider, setShowXAxisSlider] = useState(false); // handling the line and bar chart switch and the theme switch; // also handling changing the x-axis range using the `LineChartXAxisSlider`; @@ -94,115 +68,48 @@ export function LineChart({ useEffect(() => { if (showBarChart || selectedXAxisRange[1] - selectedXAxisRange[0] === 0) { setChartOptions( - LineChartOperations.getHighChartOptions( - lineChartData, - roundLines, - selectedXAxisRange[0], - selectedXAxisRange[1], - true - ) + LineChartOperations.getHighChartOptions(lineChartData, selectedXAxisRange[0], selectedXAxisRange[1], true) ); } else { setChartOptions( - LineChartOperations.getHighChartOptions(lineChartData, roundLines, selectedXAxisRange[0], selectedXAxisRange[1]) + LineChartOperations.getHighChartOptions(lineChartData, selectedXAxisRange[0], selectedXAxisRange[1]) ); } }, [showBarChart, theme, selectedXAxisRange]); - return ( - <> -
-
-

- {title} -

-
- { - // button to hide/show the slider to manipulate the plotted x-axis range of the chart; - // can be disabled via `xAxisSlider` - xAxisSlider && ( - - ) - } - { - // button to switch between line and bar chart; can be disabled via `barChartSwitch` - barChartSwitch && ( - - ) - } - { - // button to trigger the full screen modal; rendered if `expandable` - expandable && ( - - - - ) - } -
-
- { - // description text element should only be rendered if description is available - description && ( -

- {description} -

- ) - } - {/* the actual chart */} - - { - // slider to manipulate the plotted x-axis range of the chart; can be disabled via `xAxisSlider` - showXAxisSlider && ( - - ) - } -
+ // chart slider props - to manipulate the shown x-axis range + const sliderProps = disableXAxisSlider + ? undefined + : { + title: 'Adjusting x-axis range:', + sliderMin: 0, + sliderMax: xAxisLength - 1, + selectedSliderRange: selectedXAxisRange, + setSelectedSliderRange: setSelectedXAxisRange, + }; - - + const alternativeSwitchButtonProps = disableBarChartSwitch + ? undefined + : { + defaultChartType: ChartType.LINE, + alternativeChartType: ChartType.COLUMN, + showAlternativeChart: showBarChart, + setShowAlternativeChart: setShowBarChart, + }; + + return ( + ); } diff --git a/src/components/Charts/LineChartModal.tsx b/src/components/Charts/LineChartModal.tsx deleted file mode 100644 index 3c1b9157..00000000 --- a/src/components/Charts/LineChartModal.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import { Button } from '@nextui-org/button'; -import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@nextui-org/modal'; -import { Popover, PopoverContent, PopoverTrigger } from '@nextui-org/popover'; -import Highcharts from 'highcharts'; -import ExportDataModule from 'highcharts/modules/export-data'; -import Exporting from 'highcharts/modules/exporting'; -import OfflineExporting from 'highcharts/modules/offline-exporting'; -import HighchartsReact from 'highcharts-react-official'; -import { DocumentDownload, GalleryImport, Minus } from 'iconsax-react'; -import { useRef } from 'react'; - -import LineChartBarLineSwitchButton from '@/components/Charts/helpers/LineChartBarLineSwitchButton'; -import LineChartSliderButton from '@/components/Charts/helpers/LineChartSliderButton'; -import LineChartXAxisSlider from '@/components/Charts/helpers/LineChartXAxisSlider'; -import { Tooltip } from '@/components/Tooltip/Tooltip'; -import LineChartModalProps from '@/domain/props/LineChartModalProps'; -import LineChartOperations from '@/operations/charts/LineChartOperations.ts'; - -// initialize the exporting module -if (typeof Highcharts === 'object') { - Exporting(Highcharts); - ExportDataModule(Highcharts); - OfflineExporting(Highcharts); -} - -/** - * This component is tied to the `LineChart` component and should not be used independently. - * It renders the modal, which can be opened by the user from the `LineChart` to display the chart - * in a larger view and access additional functionalities, such as downloading the chart as a PNG. - * For more details, please refer to the `LineChart` component. - */ -export function LineChartModal({ - title, - description, - barChartSwitch, - xAxisSlider, - lineChartData, - chartOptions, - isOpen, - onClose, - onOpenChange, - showXAxisSlider, - setShowXAxisSlider, - showBarChart, - setShowBarChart, - selectedXAxisRange, - setSelectedXAxisRange, -}: LineChartModalProps) { - // referencing the Highcharts chart object (needed for download the chart as a png) - const chartRef = useRef(null); - - // full screen modal by the 'LineChart' component that can be opened if `expandable==true`; - // offers a larger chart and an additional features (see buttons) - return ( - - - -
-

{title}

-
- { - // button to hide/show the slider to manipulate the plotted x-axis range of the chart; - // can be disabled via `xAxisSlider` - xAxisSlider && ( - - ) - } - { - // button to switch between line and bar chart; can be disabled via `barChartSwitch` - barChartSwitch && ( - - ) - } - - {/* chart download dropdown */} - - - - - - - - - - - - - {/* close model button */} - - - -
-
-
- - - {/* modal main content: description and chart */} -

{description}

-
- -
-
- { - // slider to manipulate the plotted x-axis range of the chart; can be disabled via `xAxisSlider` - showXAxisSlider && ( - -
- -
-
- ) - } -
-
- ); -} diff --git a/src/components/Charts/helpers/ChartContainer.tsx b/src/components/Charts/helpers/ChartContainer.tsx new file mode 100644 index 00000000..85d006c7 --- /dev/null +++ b/src/components/Charts/helpers/ChartContainer.tsx @@ -0,0 +1,139 @@ +'use client'; + +import { Button } from '@nextui-org/button'; +import { useDisclosure } from '@nextui-org/modal'; +import Highcharts from 'highcharts'; +import HighchartsReact from 'highcharts-react-official'; +import { Maximize4 } from 'iconsax-react'; +import { useRef, useState } from 'react'; + +import ChartAlternativeSwitchButton from '@/components/Charts/helpers/buttons/ChartAlternativeSwitchButton'; +import ChartDownloadButton from '@/components/Charts/helpers/buttons/ChartDownloadButton'; +import ChartSliderButton from '@/components/Charts/helpers/buttons/ChartSliderButton'; +import { ChartModal } from '@/components/Charts/helpers/ChartModal'; +import ChartSlider from '@/components/Charts/helpers/ChartSlider'; +import { Tooltip } from '@/components/Tooltip/Tooltip'; +import ChartContainerProps from '@/domain/props/ChartContainerProps'; + +/** + * This component is the general component, which renders a box that primarily displays a title, description text, and a chart. + * This component has a width of 100%, so it adjusts to the width of its parent element in which it is used. + * The height of the entire box depends on the provided text, while the chart itself has a fixed height. + * It also provides the option to open the chart in a full-screen modal, where one can download the data as well. + * + * It is used by the `CategoricalChart` and `LineChart` components, which define the type of chart through the passed `chartOptions`. + * The main goal of this component is to prevents code redundancy between `LineChart` and `CategoricalChart`. + */ +export function ChartContainer({ + chartOptions, + chartData, + title, + description, + small, + noPadding, + transparentBackground, + disableExpandable, + disableDownload, + alternativeSwitchButtonProps, + sliderProps, +}: ChartContainerProps) { + const TITLE_TEXT_SIZE = small ? 'text-sm' : 'text-md'; + const DESCRIPTION_TEXT_SIZE = small ? 'text-tiny' : 'text-sm'; + const CHART_HEIGHT = small ? 12 : 16; + const ICON_BUTTON_SIZE = small ? 3 : 4; + const HEADER_PADDING = title ? 3 : 0; + const MAIN_BOX_PADDING_FACTOR = noPadding ? 0 : 1; + + const chartRef = useRef(null); + + // full screen modal state handling + const { isOpen, onOpen, onClose, onOpenChange } = useDisclosure(); + + // handling the x-axis range slider visibility + const [showSlider, setShowSlider] = useState(false); + return ( + <> +
+
+

+ {title} +

+
+ { + // button to hide/show the slider to e.g. manipulate the plotted x-axis range of the chart + sliderProps && ( + + ) + } + { + // button to switch between different chart types + alternativeSwitchButtonProps && ( + + ) + } + { + // button to download chart as png, svg, etc. + !disableDownload && ( + + ) + } + { + // button to trigger the full screen modal; rendered if `disableExpandable` is not selected + !disableExpandable && ( + + + + ) + } +
+
+ { + // description text element should only be rendered if description is available + description && ( +

+ {description} +

+ ) + } + {/* the actual chart */} + + { + // slider to e.g. manipulate the plotted x-axis range of the chart + showSlider && sliderProps && + } +
+ + + + ); +} diff --git a/src/components/Charts/helpers/ChartModal.tsx b/src/components/Charts/helpers/ChartModal.tsx new file mode 100644 index 00000000..eb10f838 --- /dev/null +++ b/src/components/Charts/helpers/ChartModal.tsx @@ -0,0 +1,96 @@ +import { Button } from '@nextui-org/button'; +import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@nextui-org/modal'; +import Highcharts from 'highcharts'; +import HighchartsReact from 'highcharts-react-official'; +import { Minus } from 'iconsax-react'; +import { useRef } from 'react'; + +import ChartAlternativeSwitchButton from '@/components/Charts/helpers/buttons/ChartAlternativeSwitchButton'; +import ChartDownloadButton from '@/components/Charts/helpers/buttons/ChartDownloadButton'; +import ChartSliderButton from '@/components/Charts/helpers/buttons/ChartSliderButton'; +import ChartSlider from '@/components/Charts/helpers/ChartSlider'; +import { Tooltip } from '@/components/Tooltip/Tooltip'; +import ChartModalProps from '@/domain/props/ChartModalProps'; + +/** + * This component is tied to the `ChartContainer` component and should not be used independently. + * It renders the modal, which can be opened by the user from the `ChartContainer` + * to display the chart in a larger view. + */ +export function ChartModal({ + chartOptions, + chartData, + title, + description, + disableDownload, + isOpen, + onClose, + onOpenChange, + alternativeSwitchButtonProps, + sliderProps, + showSlider, + setShowSlider, +}: ChartModalProps) { + const chartRef = useRef(null); + + return ( + + + +
+

{title}

+
+ {sliderProps && showSlider && setShowSlider && ( + + )} + + {alternativeSwitchButtonProps && ( + + )} + + {!disableDownload && } + + {/* close model button */} + + + +
+
+
+ + + {/* modal main content: description and chart */} +

{description}

+
+ +
+
+ { + // slider to e.g. manipulate the plotted x-axis range of the chart + sliderProps && ( + +
+ +
+
+ ) + } +
+
+ ); +} diff --git a/src/components/Charts/helpers/ChartSlider.tsx b/src/components/Charts/helpers/ChartSlider.tsx new file mode 100644 index 00000000..97564604 --- /dev/null +++ b/src/components/Charts/helpers/ChartSlider.tsx @@ -0,0 +1,41 @@ +'use client'; + +import { Slider } from '@nextui-org/slider'; + +import { ChartSliderProps } from '@/domain/props/ChartContainerProps'; + +/** + * This component is tied to the `ChartContainer` and `ChartModal` component and should not be used independently. + * It renders a simple NextUI slider. + */ +export default function ChartSlider({ + title, + sliderMin, + sliderMax, + selectedSliderRange, + setSelectedSliderRange, +}: ChartSliderProps) { + return ( +
+

{title || ''}

+ setSelectedSliderRange(e as number[])} + color="secondary" + size="sm" + showOutline + classNames={{ + base: 'max-w-md', + track: 'bg-clickableSecondary h-0.5', + filler: 'bg-surfaceGrey', + step: 'bg-clickableSecondary data-[in-range=true]:bg-surfaceGrey h-1.5 w-0.5', + thumb: 'w-5 h-5 bg-clickableSecondary data-[dragging=true]:bg-primary', + }} + /> +
+ ); +} diff --git a/src/components/Charts/helpers/LineChartBarLineSwitchButton.tsx b/src/components/Charts/helpers/LineChartBarLineSwitchButton.tsx deleted file mode 100644 index 09e72e70..00000000 --- a/src/components/Charts/helpers/LineChartBarLineSwitchButton.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Button } from '@nextui-org/button'; -import { Chart, Diagram } from 'iconsax-react'; - -import { Tooltip } from '@/components/Tooltip/Tooltip'; -import { LineChartBarLineSwitchButtonProps } from '@/domain/props/LineChartProps'; - -/** - * This component is tied to the `LineChart` and `LineChartModal` component - * and should not be used independently. - */ -export default function LineChartBarLineSwitchButton({ - showBarChart, - setShowBarChart, - size, -}: LineChartBarLineSwitchButtonProps) { - return ( - - - - ); -} diff --git a/src/components/Charts/helpers/LineChartXAxisSlider.tsx b/src/components/Charts/helpers/LineChartXAxisSlider.tsx deleted file mode 100644 index fee2ee3d..00000000 --- a/src/components/Charts/helpers/LineChartXAxisSlider.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Slider } from '@nextui-org/slider'; - -import { LineChartXAxisSlider } from '@/domain/props/LineChartProps'; -import LineChartOperations from '@/operations/charts/LineChartOperations.ts'; - -/** - * This component is tied to the `LineChart` and `LineChartModal` component - * and should not be used independently. - * It renders a NextUI slider with which the shown x-axis range of the chart - * within `LineChart` and `LineChartModal` can be manipulated. - */ -export default function LineChartXAxisSlider({ - selectedXAxisRange, - setSelectedXAxisRange, - data, -}: LineChartXAxisSlider) { - const xAxisValues: number[] = LineChartOperations.getDistinctXAxisValues(data); - - return ( -
-

Adjusting x-axis range:

- setSelectedXAxisRange(e as number[])} - color="secondary" - size="sm" - showOutline - classNames={{ - base: 'max-w-md', - track: 'bg-clickableSecondary h-0.5', - filler: 'bg-surfaceGrey', - step: 'bg-clickableSecondary data-[in-range=true]:bg-surfaceGrey h-1.5 w-0.5', - thumb: 'w-5 h-5 bg-clickableSecondary data-[dragging=true]:bg-primary', - }} - /> -
- ); -} diff --git a/src/components/Charts/helpers/buttons/ChartAlternativeSwitchButton.tsx b/src/components/Charts/helpers/buttons/ChartAlternativeSwitchButton.tsx new file mode 100644 index 00000000..962e965f --- /dev/null +++ b/src/components/Charts/helpers/buttons/ChartAlternativeSwitchButton.tsx @@ -0,0 +1,33 @@ +import { Button } from '@nextui-org/button'; +import React from 'react'; + +import { Tooltip } from '@/components/Tooltip/Tooltip'; +import { ChartAlternativeSwitchButtonProps } from '@/domain/props/ChartContainerProps'; +import ChartDownloadButtonOperations from '@/operations/charts/ChartAlternativeSwitchButtonOperations'; + +/** + * This component is tied to the `ChartContainer` and `ChartModal` component and should not be used independently. + */ +export default function ChartAlternativeSwitchButton({ + defaultChartType, + alternativeChartType, + showAlternativeChart, + setShowAlternativeChart, + size = 4, +}: ChartAlternativeSwitchButtonProps) { + const chartSwitchTitle = showAlternativeChart + ? ChartDownloadButtonOperations.getChartTypeTitle(defaultChartType) + : ChartDownloadButtonOperations.getChartTypeTitle(alternativeChartType); + + const chartSwitchIcon = showAlternativeChart + ? ChartDownloadButtonOperations.getChartTypeIcon(defaultChartType, size) + : ChartDownloadButtonOperations.getChartTypeIcon(alternativeChartType, size); + + return ( + + + + ); +} diff --git a/src/components/Charts/helpers/buttons/ChartDownloadButton.tsx b/src/components/Charts/helpers/buttons/ChartDownloadButton.tsx new file mode 100644 index 00000000..d36f76d7 --- /dev/null +++ b/src/components/Charts/helpers/buttons/ChartDownloadButton.tsx @@ -0,0 +1,71 @@ +import { Button } from '@nextui-org/button'; +import { Popover, PopoverContent, PopoverTrigger } from '@nextui-org/popover'; +import { DocumentDownload, GalleryImport } from 'iconsax-react'; + +import { Tooltip } from '@/components/Tooltip/Tooltip'; +import { ChartDownloadButtonProps } from '@/domain/props/ChartContainerProps'; +import ChartDownloadButtonOperations from '@/operations/charts/ChartDownloadButtonOperations.ts'; + +/** + * This component is tied to the `ChartContainer` and `ChartModal` component and should not be used independently. + * It renders a button to open a dropdown menu to download the chart as csv, png, etc. + */ +export default function ChartDownloadButton({ chartRef, chartData, size }: ChartDownloadButtonProps) { + return ( + + + + + + + + + + + + ); +} diff --git a/src/components/Charts/helpers/LineChartSliderButton.tsx b/src/components/Charts/helpers/buttons/ChartSliderButton.tsx similarity index 52% rename from src/components/Charts/helpers/LineChartSliderButton.tsx rename to src/components/Charts/helpers/buttons/ChartSliderButton.tsx index b68dae90..b9337eaa 100644 --- a/src/components/Charts/helpers/LineChartSliderButton.tsx +++ b/src/components/Charts/helpers/buttons/ChartSliderButton.tsx @@ -2,17 +2,12 @@ import { Button } from '@nextui-org/button'; import { Settings } from 'iconsax-react'; import { Tooltip } from '@/components/Tooltip/Tooltip'; -import { LineChartSliderButtonProps } from '@/domain/props/LineChartProps'; +import { LineChartSliderButtonProps } from '@/domain/props/ChartContainerProps'; /** - * This component is tied to the `LineChart` and `LineChartModal` component - * and should not be used independently. + * This component is tied to the `ChartContainer` and `ChartModal` component and should not be used independently. */ -export default function LineChartSliderButton({ - showXAxisSlider, - setShowXAxisSlider, - size, -}: LineChartSliderButtonProps) { +export default function ChartSliderButton({ showSlider, setShowSlider, size }: LineChartSliderButtonProps) { return (