diff --git a/frontend/src/components/SmartWidget/SmartWidget.tsx b/frontend/src/components/SmartWidget/SmartWidget.tsx deleted file mode 100644 index 6f6c0e0..0000000 --- a/frontend/src/components/SmartWidget/SmartWidget.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React from 'react'; -import BaseButton from '../BaseButton'; -import BaseIcon from '../BaseIcon'; -import * as icons from '@mdi/js'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; - -import { fetchWidgets, removeWidget } from '../../stores/roles/rolesSlice'; -import { WidgetChartType, WidgetType } from "./models/widget.model"; -import { BarChart } from "./components/BarChart"; -import { PieChart } from "./components/PieChart"; -import { AreaChart } from "./components/AreaChart"; -import { LineChart } from "./components/LineChart"; - -export const SmartWidget = ({ widget, userId, admin, roleId }) => { - const dispatch = useAppDispatch(); - const corners = useAppSelector((state) => state.style.corners); - const cardsStyle = useAppSelector((state) => state.style.cardsStyle); - - const deleteWidget = async () => { - await dispatch( - removeWidget({ id: userId, widgetId: widget.widget_id, roleId }), - ); - await dispatch(fetchWidgets(roleId)); - }; - - return ( -
-
-
-
- {widget.label} -
- - {admin && ( - - )} -
- -
-
- {widget.value ? ( - widget.widget_type === WidgetType.chart ? ( - widget.chart_type === WidgetChartType.bar ? ( - - ) : widget.chart_type === WidgetChartType.line ? ( - - ) : widget.chart_type === WidgetChartType.pie ? ( - - ) : widget.chart_type === WidgetChartType.area ? ( - - ) : widget.chart_type === WidgetChartType.funnel ? ( - - ) : null - ) : ( -
- {widget.value} -
- ) - ) : ( -
- Something went wrong, please try again or use a different query. -
- )} -
- - {widget.type === WidgetType.scalar && widget.mdiIcon && ( -
- -
- )} -
-
-
- ); -}; diff --git a/frontend/src/components/SmartWidget/components/AreaChart.tsx b/frontend/src/components/SmartWidget/components/AreaChart.tsx deleted file mode 100644 index 50bd5c7..0000000 --- a/frontend/src/components/SmartWidget/components/AreaChart.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { WidgetLibName } from '../models/widget.model'; -import { ApexAreaChart } from './AreaChart/ApexAreaChart'; -import { ChartJSAreaChart } from './AreaChart/ChartJSAreaChart'; - -export const AreaChart = ({ widget }) => { - return ( - <> - {!widget.lib_name && } - {widget.lib_name === WidgetLibName.chartjs && ( - - )} - {widget.lib_name === WidgetLibName.apex && ( - - )} - - ); -}; diff --git a/frontend/src/components/SmartWidget/components/AreaChart/ApexAreaChart.tsx b/frontend/src/components/SmartWidget/components/AreaChart/ApexAreaChart.tsx deleted file mode 100644 index 656008a..0000000 --- a/frontend/src/components/SmartWidget/components/AreaChart/ApexAreaChart.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React from 'react'; -import dynamic from 'next/dynamic'; -import { humanize } from '../../../../helpers/humanize'; - -const Chart = dynamic(() => import('react-apexcharts'), { ssr: false }); -type ValueType = { [key: string]: string | number }[]; - -export const ApexAreaChart = ({ widget }) => { - const dataForLineChart = (value: any[]) => { - if (!value?.length || value?.length > 10000) - return [{ name: '', data: [] }]; - - const valueKey = Object.keys(value[0])[1]; - const data = value.map((el) => +el[valueKey]); - - return [{ name: humanize(valueKey), data }]; - }; - - const optionsForLineChart = ( - value: ValueType, - chartColor: string[], - currency: boolean, - ) => { - const chartColors = Array.isArray(chartColor) - ? chartColor - : [chartColor || '#3751FF']; - const defaultOptions = { - xaxis: {}, - chart: { - toolbar: { - show: true, - offsetX: 0, - offsetY: 0, - tools: { - download: true, - selection: true, - zoom: true, - zoomin: true, - zoomout: true, - pan: true, - }, - export: { - csv: { - filename: undefined, - columnDelimiter: ',', - headerCategory: 'category', - headerValue: 'value', - }, - svg: { - filename: undefined, - }, - png: { - filename: undefined, - }, - }, - }, - }, - plotOptions: { - bar: { - distributed: false, - }, - }, - colors: [], - }; - - if (!value?.length || value?.length > 10000) return defaultOptions; - - const key = Object.keys(value[0])[0]; - const categories = value - .map((el) => el[key]) - .map((item) => - typeof item === 'string' && item?.length > 7 - ? item?.slice(0, 7) - : item || '', - ); - - if (categories.length <= 3) { - defaultOptions.plotOptions = { - bar: { - distributed: true, - }, - }; - } - - const colors = []; - for (let i = 0; i < categories.length; i++) { - colors.push(chartColors[i % chartColors.length]); - } - - return { - ...defaultOptions, - yaxis: { - labels: { - formatter: function (value) { - if (currency) { - return '$' + value; - } else { - return value; - } - }, - }, - }, - dataLabels: { - formatter: (val) => { - if (currency) { - return '$' + val; - } else { - return val; - } - }, - }, - legend: { - show: false, - }, - xaxis: { - categories, - }, - colors, - }; - }; - - return ( - - ); -}; diff --git a/frontend/src/components/SmartWidget/components/AreaChart/ChartJSAreaChart.tsx b/frontend/src/components/SmartWidget/components/AreaChart/ChartJSAreaChart.tsx deleted file mode 100644 index 5bb4cd2..0000000 --- a/frontend/src/components/SmartWidget/components/AreaChart/ChartJSAreaChart.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; -import { Line } from 'react-chartjs-2'; -import chroma from 'chroma-js'; -import { humanize } from '../../../../helpers/humanize'; -import { collectOtherData, findFirstNumericKey } from '../../widgetHelpers'; -import { - Chart as ChartJS, - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Filler, - Legend, - ChartData, -} from 'chart.js'; - -ChartJS.register( - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Filler, - Legend, -); - -export const ChartJSAreaChart = ({ widget }) => { - const options = { - responsive: true, - maintainAspectRatio: false, - scales: { - y: { - display: true, - }, - x: { - display: true, - }, - }, - plugins: { - legend: { - display: true, - }, - }, - }; - - const dataForBarChart = ( - value: any[], - chartColors: string[], - ): ChartData<'line', number[], string> => { - if (!value?.length) return { labels: [''], datasets: [{ data: [] }] }; - const initColors = Array.isArray(chartColors) - ? chartColors - : [chartColors || '#3751FF']; - - const valueKey = findFirstNumericKey(value[0]); - const label = humanize(valueKey); - const data = value.map((el) => +el[valueKey]); - const labels = value.map((el) => - Object.keys(el).length <= 2 - ? humanize(String(el[Object.keys(el)[0]])) - : collectOtherData(el, valueKey), - ); - - const backgroundColor = - labels.length > initColors.length - ? chroma - .scale([ - chroma(initColors[0]).brighten(), - chroma(initColors.slice(-1)[0]).darken(), - ]) - .colors(labels.length) - : initColors; - - return { - labels, - datasets: [ - { - label, - data, - backgroundColor, - }, - ], - }; - }; - - return ( - - ); -}; diff --git a/frontend/src/components/SmartWidget/components/BarChart.tsx b/frontend/src/components/SmartWidget/components/BarChart.tsx deleted file mode 100644 index bf7a79b..0000000 --- a/frontend/src/components/SmartWidget/components/BarChart.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { ChartJSBarChart } from './BarChart/ChartJSBarChart'; -import { ApexBarChart } from './BarChart/ApexBarChart'; -import { WidgetLibName } from '../models/widget.model'; - -export const BarChart = ({ widget }) => { - return ( - <> - {!widget.lib_name && } - {widget.lib_name === WidgetLibName.chartjs && ( - - )} - {widget.lib_name === WidgetLibName.apex && ( - - )} - - ); -}; diff --git a/frontend/src/components/SmartWidget/components/BarChart/ApexBarChart.tsx b/frontend/src/components/SmartWidget/components/BarChart/ApexBarChart.tsx deleted file mode 100644 index 5b3d3e7..0000000 --- a/frontend/src/components/SmartWidget/components/BarChart/ApexBarChart.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import React from 'react'; -import dynamic from 'next/dynamic'; -import { humanize } from '../../../../helpers/humanize'; - -const Chart = dynamic(() => import('react-apexcharts'), { ssr: false }); -type ValueType = { [key: string]: string | number }[]; - -export const ApexBarChart = ({ widget }) => { - const dataForBarChart = (value: any[]) => { - if (!value?.length || value?.length > 10000) - return [{ name: '', data: [] }]; - - const valueKey = Object.keys(value[0])[1]; - const data = value.map((el) => +el[valueKey]); - - return [{ name: humanize(valueKey), data }]; - }; - const optionsForBarChart = ( - value: ValueType, - chartColor: string[], - currency: boolean, - ) => { - const chartColors = Array.isArray(chartColor) - ? chartColor - : [chartColor || '#3751FF']; - const defaultOptions = { - xaxis: {}, - chart: { - toolbar: { - show: true, - offsetX: 0, - offsetY: 0, - tools: { - download: true, - selection: true, - zoom: true, - }, - export: { - csv: { - filename: undefined, - columnDelimiter: ',', - headerCategory: 'category', - headerValue: 'value', - }, - svg: { - filename: undefined, - }, - png: { - filename: undefined, - }, - }, - }, - }, - plotOptions: { - bar: { - distributed: false, - }, - }, - colors: [], - }; - - if (!value?.length || value?.length > 10000) return defaultOptions; - - const key = Object.keys(value[0])[0]; - const categories = value - .map((el) => el[key]) - .map((item) => - typeof item === 'string' && item?.length > 20 - ? item?.slice(0, 15) - : item || '', - ); - - if (categories.length <= 3) { - defaultOptions.plotOptions = { - bar: { - distributed: true, - }, - }; - } - - const colors = []; - for (let i = 0; i < categories.length; i++) { - colors.push(chartColors[i % chartColors.length]); - } - - return { - ...defaultOptions, - yaxis: { - labels: { - formatter: function (value) { - if (currency) { - return '$' + value; - } else { - return value; - } - }, - }, - }, - dataLabels: { - formatter: (val) => { - if (currency) { - return '$' + val; - } else { - return val; - } - }, - }, - legend: { - show: true, - }, - xaxis: { - categories, - }, - colors, - }; - }; - - return ( - - ); -}; diff --git a/frontend/src/components/SmartWidget/components/BarChart/ChartJSBarChart.tsx b/frontend/src/components/SmartWidget/components/BarChart/ChartJSBarChart.tsx deleted file mode 100644 index d6b802c..0000000 --- a/frontend/src/components/SmartWidget/components/BarChart/ChartJSBarChart.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react'; -import { humanize } from '../../../../helpers/humanize'; -import { Bar } from 'react-chartjs-2'; -import { collectOtherData, findFirstNumericKey } from '../../widgetHelpers'; -import { - BarElement, - CategoryScale, - Chart as ChartJS, - ChartData, - Legend, - LinearScale, - Title, - Tooltip, -} from 'chart.js'; -import chroma from 'chroma-js'; - -ChartJS.register( - CategoryScale, - LinearScale, - BarElement, - Title, - Tooltip, - Legend, -); - -export const ChartJSBarChart = ({ widget }) => { - console.log(widget) - const options = () => { - return { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - position: 'top' as const, - }, - title: { - display: true, - text: widget.label, - }, - }, - }; - }; - - const dataForBarChart = ( - value: any[], - chartColors: string[], - ): ChartData<'bar', number[], string> => { - if (!value?.length) return { labels: [''], datasets: [{ data: [] }] }; - - const initColors = Array.isArray(chartColors) - ? chartColors - : [chartColors || '#3751FF']; - - const valueKey = findFirstNumericKey(value[0]); - const label = humanize(valueKey); - const data = value.map((el) => +el[valueKey]); - const labels = value.map((el) => - Object.keys(el).length <= 2 - ? humanize(String(el[Object.keys(el)[0]])) - : collectOtherData(el, valueKey), - ); - - const backgroundColor = - labels.length > initColors.length - ? chroma - .scale([ - chroma(initColors[0]).brighten(), - chroma(initColors.slice(-1)[0]).darken(), - ]) - .colors(labels.length) - : initColors; - - return { - labels, - datasets: [ - { - label, - data, - backgroundColor, - }, - ], - }; - }; - - return ( - - ); -}; diff --git a/frontend/src/components/SmartWidget/components/FunnelChart.tsx b/frontend/src/components/SmartWidget/components/FunnelChart.tsx deleted file mode 100644 index 3d91280..0000000 --- a/frontend/src/components/SmartWidget/components/FunnelChart.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react'; -import dynamic from 'next/dynamic'; -import { humanize } from '../../../helpers/humanize'; - -const Chart = dynamic(() => import('react-apexcharts'), { ssr: false }); -type ValueType = { [key: string]: string | number }[]; - -export const FunnelChart = ({ widget }) => { - const dataForBarChart = (value: any[]) => { - if (!value?.length || value?.length > 10000) - return [{ name: '', data: [] }]; - const valueKey = Object.keys(value[0])[1]; - const data = value.map((el) => +el[valueKey]); - - return [{ name: humanize(valueKey), data }]; - }; - const optionsForBarChart = ( - value: ValueType, - chartColor: string[], - currency: boolean, - ) => { - const chartColors = Array.isArray(chartColor) - ? chartColor - : [chartColor || '#3751FF']; - const defaultOptions = { - xaxis: {}, - chart: { - toolbar: { - show: true, - offsetX: 0, - offsetY: 0, - tools: { - download: true, - selection: true, - zoom: true, - }, - export: { - csv: { - filename: undefined, - columnDelimiter: ',', - headerCategory: 'category', - headerValue: 'value', - }, - svg: { - filename: undefined, - }, - png: { - filename: undefined, - }, - }, - }, - }, - plotOptions: { - bar: { - distributed: false, - horizontal: true, - isFunnel: true, - }, - }, - colors: [], - }; - - if (!value?.length || value?.length > 10000) return defaultOptions; - - const key = Object.keys(value[0])[0]; - const categories = value - .map((el) => el[key]) - .map((item) => - typeof item === 'string' && item?.length > 20 - ? item?.slice(0, 15) - : item || '', - ); - - if (categories.length <= 3) { - defaultOptions.plotOptions = { - bar: { - distributed: true, - horizontal: true, - isFunnel: true, - }, - }; - } - - const colors = []; - for (let i = 0; i < categories.length; i++) { - colors.push(chartColors[i % chartColors.length]); - } - - return { - ...defaultOptions, - yaxis: { - labels: { - formatter: function (value) { - if (currency) { - return '$' + value; - } else { - return value; - } - }, - }, - }, - dataLabels: { - formatter: (val) => { - if (currency) { - return '$' + val; - } else { - return val; - } - }, - }, - legend: { - show: true, - }, - xaxis: { - categories, - }, - colors, - }; - }; - - return ( - - ); -}; diff --git a/frontend/src/components/SmartWidget/components/LineChart.tsx b/frontend/src/components/SmartWidget/components/LineChart.tsx deleted file mode 100644 index 3b9e7b0..0000000 --- a/frontend/src/components/SmartWidget/components/LineChart.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { WidgetLibName } from '../models/widget.model'; -import { ApexLineChart } from './LineChart/ApexLineChart'; -import { ChartJSLineChart } from './LineChart/ChartJSLineChart'; - -export const LineChart = ({ widget }) => { - return ( - <> - {!widget.lib_name && } - {widget.lib_name === WidgetLibName.chartjs && ( - - )} - {widget.lib_name === WidgetLibName.apex && ( - - )} - - ); -}; diff --git a/frontend/src/components/SmartWidget/components/LineChart/ApexLineChart.tsx b/frontend/src/components/SmartWidget/components/LineChart/ApexLineChart.tsx deleted file mode 100644 index aa77a76..0000000 --- a/frontend/src/components/SmartWidget/components/LineChart/ApexLineChart.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React from 'react'; -import dynamic from 'next/dynamic'; -import { humanize } from '../../../../helpers/humanize'; - -const Chart = dynamic(() => import('react-apexcharts'), { ssr: false }); -type ValueType = { [key: string]: string | number }[]; - -export const ApexLineChart = ({ widget }) => { - const dataForLineChart = (value: any[]) => { - if (!value?.length || value?.length > 10000) - return [{ name: '', data: [] }]; - - const valueKey = Object.keys(value[0])[1]; - const data = value.map((el) => +el[valueKey]); - - return [{ name: humanize(valueKey), data }]; - }; - - const optionsForLineChart = ( - value: ValueType, - chartColor: string[], - currency: boolean, - ) => { - const chartColors = Array.isArray(chartColor) - ? chartColor - : [chartColor || '#3751FF']; - const defaultOptions = { - xaxis: {}, - chart: { - toolbar: { - show: true, - offsetX: 0, - offsetY: 0, - tools: { - download: true, - selection: true, - zoom: true, - zoomin: true, - zoomout: true, - pan: true, - }, - export: { - csv: { - filename: undefined, - columnDelimiter: ',', - headerCategory: 'category', - headerValue: 'value', - }, - svg: { - filename: undefined, - }, - png: { - filename: undefined, - }, - }, - }, - }, - plotOptions: { - bar: { - distributed: false, - }, - }, - colors: [], - }; - - if (!value?.length || value?.length > 10000) return defaultOptions; - - const key = Object.keys(value[0])[0]; - const categories = value - .map((el) => el[key]) - .map((item) => - typeof item === 'string' && item?.length > 7 - ? item?.slice(0, 7) - : item || '', - ); - - if (categories.length <= 3) { - defaultOptions.plotOptions = { - bar: { - distributed: true, - }, - }; - } - - const colors = []; - for (let i = 0; i < categories.length; i++) { - colors.push(chartColors[i % chartColors.length]); - } - - return { - ...defaultOptions, - yaxis: { - labels: { - formatter: function (value) { - if (currency) { - return '$' + value; - } else { - return value; - } - }, - }, - }, - dataLabels: { - formatter: (val) => { - if (currency) { - return '$' + val; - } else { - return val; - } - }, - }, - legend: { - show: false, - }, - xaxis: { - categories, - }, - colors, - }; - }; - - return ( - - ); -}; diff --git a/frontend/src/components/SmartWidget/components/LineChart/ChartJSLineChart.tsx b/frontend/src/components/SmartWidget/components/LineChart/ChartJSLineChart.tsx deleted file mode 100644 index a2fe5ba..0000000 --- a/frontend/src/components/SmartWidget/components/LineChart/ChartJSLineChart.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { humanize } from '../../../../helpers/humanize'; -import { Line } from 'react-chartjs-2'; -import chroma from 'chroma-js'; -import { collectOtherData, findFirstNumericKey } from '../../widgetHelpers'; -import { Widget } from '../../models/widget.model'; -import { - Chart, - LineElement, - PointElement, - LineController, - LinearScale, - CategoryScale, - Tooltip, - ChartData, -} from 'chart.js'; - -Chart.register( - LineElement, - PointElement, - LineController, - LinearScale, - CategoryScale, - Tooltip, -); - -interface Props { - widget: Widget; -} - -export const ChartJSLineChart = (props: Props) => { - const options = { - responsive: true, - maintainAspectRatio: false, - scales: { - y: { - display: true, - }, - x: { - display: true, - }, - }, - plugins: { - legend: { - display: true, - }, - }, - }; - - const dataForBarChart = ( - value: any[], - chartColors: string[], - ): ChartData<'line', number[], string> => { - if (!value?.length) return { labels: [''], datasets: [{ data: [] }] }; - const initColors = Array.isArray(chartColors) - ? chartColors - : [chartColors || '#3751FF']; - - const valueKey = findFirstNumericKey(value[0]); - const label = humanize(valueKey); - const data = value.map((el) => +el[valueKey]); - const labels = value.map((el) => - Object.keys(el).length <= 2 - ? humanize(String(el[Object.keys(el)[0]])) - : collectOtherData(el, valueKey), - ); - - const backgroundColor = - labels.length > initColors.length - ? chroma - .scale([ - chroma(initColors[0]).brighten(), - chroma(initColors.slice(-1)[0]).darken(), - ]) - .colors(labels.length) - : initColors; - - return { - labels, - datasets: [ - { - label, - data, - backgroundColor, - }, - ], - }; - }; - - return ( - - ); -}; diff --git a/frontend/src/components/SmartWidget/components/PieChart.tsx b/frontend/src/components/SmartWidget/components/PieChart.tsx deleted file mode 100644 index f6f0fd3..0000000 --- a/frontend/src/components/SmartWidget/components/PieChart.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { WidgetLibName } from '../models/widget.model'; -import { ApexPieChart } from './PieChart/ApexPieChart'; -import { ChartJSPieChart } from './PieChart/ChartJSPieChart'; - -export const PieChart = ({ widget }) => { - return ( - <> - {!widget.lib_name && } - {widget.lib_name === WidgetLibName.chartjs && ( - - )} - {widget.lib_name === WidgetLibName.apex && ( - - )} - - ); -}; diff --git a/frontend/src/components/SmartWidget/components/PieChart/ApexPieChart.tsx b/frontend/src/components/SmartWidget/components/PieChart/ApexPieChart.tsx deleted file mode 100644 index dfe5572..0000000 --- a/frontend/src/components/SmartWidget/components/PieChart/ApexPieChart.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import React from 'react'; -import dynamic from 'next/dynamic'; -import chroma from 'chroma-js'; - -const Chart = dynamic(() => import('react-apexcharts'), { ssr: false }); -type ValueType = { [key: string]: string | number }[]; - -export const ApexPieChart = ({ widget }) => { - const optionsForPieChart = (value: ValueType, chartColor: string) => { - const chartColors = Array.isArray(chartColor) - ? chartColor - : [chartColor || '#3751FF']; - const defaultOptions = { - xaxis: {}, - toolbar: { - show: true, - offsetX: 0, - offsetY: 0, - tools: { - download: true, - selection: true, - zoom: true, - zoomin: true, - zoomout: true, - pan: true, - customIcons: [], - }, - export: { - csv: { - filename: undefined, - columnDelimiter: ',', - headerCategory: 'category', - headerValue: 'value', - }, - svg: { - filename: undefined, - }, - png: { - filename: undefined, - }, - }, - autoSelected: 'zoom', - }, - colors: [], - }; - - if (!value?.length || value?.length > 10000) return defaultOptions; - - if ( - !isNaN(Number(value[0][Object.keys(value[0])[1]])) && - isFinite(Number(value[0][Object.keys(value[0])[1]])) - ) { - const labels = value - .map((el) => String(el[Object.keys(value[0])[0]])) - .reverse(); - - let colors: string[] | (string & any[]); - if (labels.length > chartColors.length) { - colors = chroma - .scale([ - chroma(chartColors.at(0)).brighten(), - chroma(chartColors.at(-1)).darken(), - ]) - .colors(labels.length); - } else { - colors = chartColors; - } - - return { - ...defaultOptions, - colors, - labels, - }; - } - const key = Object.keys(value[0])[1]; - const categories = value.map((el) => String(el[key])).reverse(); - - return { - ...defaultOptions, - labels: categories, - }; - }; - const dataForPieChart = (value: any[]) => { - if (!value?.length || value?.length > 10000) - return [{ name: '', data: [] }]; - - if ( - !isNaN(parseFloat(value[0][Object.keys(value[0])[1]])) && - isFinite(value[0][Object.keys(value[0])[1]]) - ) { - return value.map((el) => +el[Object.keys(value[0])[1]]).reverse(); - } - const valueKey = Object.keys(value[0])[0]; - return value.map((el) => +el[valueKey]).reverse(); - }; - - return ( - - ); -}; diff --git a/frontend/src/components/SmartWidget/components/PieChart/ChartJSPieChart.tsx b/frontend/src/components/SmartWidget/components/PieChart/ChartJSPieChart.tsx deleted file mode 100644 index 2a20155..0000000 --- a/frontend/src/components/SmartWidget/components/PieChart/ChartJSPieChart.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; -import { humanize } from '../../../../helpers/humanize'; -import { Pie } from 'react-chartjs-2'; -import chroma from 'chroma-js'; -import { collectOtherData, findFirstNumericKey } from '../../widgetHelpers'; - -import { - Chart as ChartJS, - ArcElement, - Tooltip, - Legend, - ChartData, -} from 'chart.js'; - -ChartJS.register(ArcElement, Tooltip, Legend); - -export const ChartJSPieChart = ({ widget }) => { - const options = () => { - return { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - position: 'right' as const, - }, - title: { - display: true, - text: widget.label, - }, - }, - }; - }; - - const dataForBarChart = ( - value: any[], - chartColors: string[], - ): ChartData<'pie', number[], string> => { - if (!value?.length) return { labels: [''], datasets: [{ data: [] }] }; - const initColors = Array.isArray(chartColors) - ? chartColors - : [chartColors || '#3751FF']; - - const valueKey = findFirstNumericKey(value[0]); - const label = humanize(valueKey); - const data = value.map((el) => +el[valueKey]); - const labels = value.map((el) => - Object.keys(el).length <= 2 - ? humanize(String(el[Object.keys(el)[0]])) - : collectOtherData(el, valueKey), - ); - - const backgroundColor = - labels.length > initColors.length - ? chroma - .scale([ - chroma(initColors[0]).brighten(), - chroma(initColors.slice(-1)[0]).darken(), - ]) - .colors(labels.length) - : initColors; - - return { - labels, - datasets: [ - { - label, - data, - backgroundColor, - }, - ], - }; - }; - - return ( - - ); -}; diff --git a/frontend/src/components/SmartWidget/models/widget.model.ts b/frontend/src/components/SmartWidget/models/widget.model.ts deleted file mode 100644 index 362d65c..0000000 --- a/frontend/src/components/SmartWidget/models/widget.model.ts +++ /dev/null @@ -1,35 +0,0 @@ -export enum WidgetLibName { - apex = 'apex', - chartjs = 'chartjs', -} - -export enum WidgetChartType { - scalar = 'scalar', - bar = 'bar', - line = 'line', - pie = 'pie', - area = 'area', - funnel = 'funnel', -} - -export enum WidgetType { - chart = 'chart', - scalar = 'scalar', -} - -export interface Widget { - type: WidgetType; - chartType: WidgetChartType; - query: string; - mdiIcon: string; - iconColor: string; - label: string; - id: string; - lib?: WidgetLibName; - value: any[]; - chartColors: string[]; - options?: any; - prompt: string; - color: string; - color_array: string[]; -} diff --git a/frontend/src/components/SmartWidget/widgetHelpers.tsx b/frontend/src/components/SmartWidget/widgetHelpers.tsx deleted file mode 100644 index baf7969..0000000 --- a/frontend/src/components/SmartWidget/widgetHelpers.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { humanize } from '../../helpers/humanize'; - -interface DataObject { - [key: string]: any; -} - -export const findFirstNumericKey = (obj: Record): string | undefined => { - for (const [key, value] of Object.entries(obj)) { - if (typeof value === 'string') { - const trimmedValue = value.trim(); - - // Only allow numbers, and optionally a single decimal point - const isNumeric = /^-?\d+(\.\d+)?$/.test(trimmedValue); - - if (isNumeric) { - // Check if the number is the same as the trimmed value - // This is to avoid cases like '1.0' being treated as a number - const numberValue = parseFloat(trimmedValue); - if (numberValue.toString() === trimmedValue) { - return key; - } - } - } - } - return undefined; -}; - -export const collectOtherData = ( - obj: DataObject, - excludeKey: string, -): string => { - return Object.entries(obj) - .filter(([key, _]) => key !== excludeKey) - .map(([_, value]) => humanize(value)) - .join(' / '); -}; diff --git a/frontend/src/components/WidgetCreator/RoleSelect.tsx b/frontend/src/components/WidgetCreator/RoleSelect.tsx deleted file mode 100644 index 7cc095c..0000000 --- a/frontend/src/components/WidgetCreator/RoleSelect.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useEffect, useId, useState } from 'react'; -import { AsyncPaginate } from 'react-select-async-paginate'; -import axios from 'axios'; - -export const RoleSelect = ({ options, field, form, itemRef, disabled, currentUser }) => { - const [value, setValue] = useState(null); - const PAGE_SIZE = 100; - - React.useEffect(() => { - if (currentUser.app_role.id) { - setValue({ value: currentUser.app_role.id, label: currentUser.app_role.name }); - } - }, [currentUser]); - - useEffect(() => { - if (options?.value && options?.label) { - setValue({ value: options.value, label: options.label }); - } - }, [options?.id, field?.value?.id]); - - const mapResponseToValuesAndLabels = (data) => ({ - value: data.id, - label: data.label, - }); - const handleChange = (option) => { - form.setFieldValue(field.name, option); - setValue(option); - }; - - async function callApi(inputValue: string, loadedOptions: any[]) { - const path = `/${itemRef}/autocomplete?limit=${PAGE_SIZE}&offset=${loadedOptions.length}${inputValue ? `&query=${inputValue}` : ''}`; - const { data } = await axios(path); - return { - options: data.map(mapResponseToValuesAndLabels), - hasMore: data.length === PAGE_SIZE, - } - } - return ( - 'px-1 py-2', - }} - classNamePrefix={'react-select'} - instanceId={useId()} - value={value} - debounceTimeout={1000} - loadOptions={callApi} - onChange={handleChange} - defaultOptions - isDisabled={disabled} - /> - ); -}; diff --git a/frontend/src/components/WidgetCreator/WidgetCreator.tsx b/frontend/src/components/WidgetCreator/WidgetCreator.tsx deleted file mode 100644 index 80b4999..0000000 --- a/frontend/src/components/WidgetCreator/WidgetCreator.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import CardBox from '../CardBox'; -import { mdiCog } from '@mdi/js'; -import { Field, Form, Formik } from 'formik'; -import { ToastContainer, toast } from 'react-toastify'; -import FormField from '../FormField'; -import React from 'react'; -import { - aiPrompt, - setErrorNotification, - resetNotify, -} from '../../stores/openAiSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; - -import { fetchWidgets } from '../../stores/roles/rolesSlice'; - -import BaseButton from '../BaseButton'; -import CardBoxModal from '../CardBoxModal'; -import { RoleSelect } from './RoleSelect'; - -export const WidgetCreator = ({ - currentUser, - isFetchingQuery, - setWidgetsRole, - widgetsRole, - }) => { - const dispatch = useAppDispatch(); - const [isModalOpen, setIsModalOpen] = React.useState(false); - const { notify: openAiNotify } = useAppSelector((state) => state.openAi); - - const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' }); - React.useEffect(() => { - if (openAiNotify.showNotification) { - notify(openAiNotify.typeNotification, openAiNotify.textNotification); - dispatch(resetNotify()); - } - }, [openAiNotify.showNotification]); - - const openModal = (): void => { - setIsModalOpen(true); - }; - - const handleCloseModal = (value = {}) => { - setWidgetsRole(value); - setIsModalOpen(false); - }; - - const getWidgets = async () => { - await dispatch(fetchWidgets(widgetsRole?.role?.value || '')); - }; - - const smartSearch = async (values: { description: string }, resetForm: any) => { - const description = values.description; - const projectId = ''; - - const payload = { - roleId: widgetsRole?.role?.value, - description, - projectId, - userId: currentUser?.id, - }; - const { payload: responcePayload, error }: any = await dispatch(aiPrompt(payload)); - - await getWidgets().then(); - - resetForm({ values: { description: '' } }); - if (responcePayload.data?.error || error) { - const errorMessage = - responcePayload.data?.error?.message || error?.message; - await dispatch( - setErrorNotification(errorMessage || 'Error with widget creation'), - ); - } - }; - - return ( - <> - - - smartSearch(values, resetForm)} - > -
- - - -
-
-
- handleCloseModal(values)} - > - {({ submitForm }) => ( - setIsModalOpen(false)} - > -

What role are we showing and creating widgets for?

- -
- - - -
-
- )} -
- - - ); -}; diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index 57a1b7d..fc1e9a1 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -1,94 +1,30 @@ import * as icon from '@mdi/js'; import Head from 'next/head' import React from 'react' -import axios from 'axios'; import type { ReactElement } from 'react' import LayoutAuthenticated from '../layouts/Authenticated' import SectionMain from '../components/SectionMain' import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton' -import BaseIcon from "../components/BaseIcon"; import { getPageTitle } from '../config' -import Link from "next/link"; +import { useAppSelector, useAppDispatch } from '../stores/hooks'; + -import { hasPermission } from "../helpers/userPermissions"; -import { fetchWidgets } from '../stores/roles/rolesSlice'; -import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator'; -import { SmartWidget } from '../components/SmartWidget/SmartWidget'; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; const Dashboard = () => { const dispatch = useAppDispatch(); const iconsColor = useAppSelector((state) => state.style.iconsColor); const corners = useAppSelector((state) => state.style.corners); const cardsStyle = useAppSelector((state) => state.style.cardsStyle); - const loadingMessage = 'Loading...'; + - const [users, setUsers] = React.useState(loadingMessage); - const [roles, setRoles] = React.useState(loadingMessage); - const [permissions, setPermissions] = React.useState(loadingMessage); - const [staff_roles, setStaff_roles] = React.useState(loadingMessage); - const [customers, setCustomers] = React.useState(loadingMessage); - const [tables, setTables] = React.useState(loadingMessage); - const [reservations, setReservations] = React.useState(loadingMessage); - const [tabs, setTabs] = React.useState(loadingMessage); - const [line_items, setLine_items] = React.useState(loadingMessage); - const [menu_items, setMenu_items] = React.useState(loadingMessage); - const [inventory_items, setInventory_items] = React.useState(loadingMessage); - const [suppliers, setSuppliers] = React.useState(loadingMessage); - const [payments, setPayments] = React.useState(loadingMessage); - const [promotions, setPromotions] = React.useState(loadingMessage); - - const [widgetsRole, setWidgetsRole] = React.useState({ - role: { value: '', label: '' }, - }); const { currentUser } = useAppSelector((state) => state.auth); - const { isFetchingQuery } = useAppSelector((state) => state.openAi); - - const { rolesWidgets, loading } = useAppSelector((state) => state.roles); - async function loadData() { - const entities = ['users','roles','permissions','staff_roles','customers','tables','reservations','tabs','line_items','menu_items','inventory_items','suppliers','payments','promotions',]; - const fns = [setUsers,setRoles,setPermissions,setStaff_roles,setCustomers,setTables,setReservations,setTabs,setLine_items,setMenu_items,setInventory_items,setSuppliers,setPayments,setPromotions,]; + - const requests = entities.map((entity, index) => { - - if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) { - return axios.get(`/${entity.toLowerCase()}/count`); - } else { - fns[index](null); - return Promise.resolve({data: {count: null}}); - } - - }); - - Promise.allSettled(requests).then((results) => { - results.forEach((result, i) => { - if (result.status === 'fulfilled') { - fns[i](result.value.data.count); - } else { - fns[i](result.reason.message); - } - }); - }); - } - - async function getWidgets(roleId) { - await dispatch(fetchWidgets(roleId)); - } - React.useEffect(() => { - if (!currentUser) return; - loadData().then(); - setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } }); - }, [currentUser]); - - React.useEffect(() => { - if (!currentUser || !widgetsRole?.role?.value) return; - getWidgets(widgetsRole?.role?.value || '').then(); - }, [widgetsRole?.role?.value]); return ( <> @@ -104,445 +40,6 @@ const Dashboard = () => { main> {''} - - {hasPermission(currentUser, 'CREATE_ROLES') && } - {!!rolesWidgets.length && - hasPermission(currentUser, 'CREATE_ROLES') && ( -

- {`${widgetsRole?.role?.label || 'Users'}'s widgets`} -

- )} - -
- {(isFetchingQuery || loading) && ( -
- {' '} - Loading widgets... -
- )} - - { rolesWidgets && - rolesWidgets.map((widget) => ( - - ))} -
- - {!!rolesWidgets.length &&
} - -
- - - {hasPermission(currentUser, 'READ_USERS') && -
-
-
-
- Users -
-
- {users} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_ROLES') && -
-
-
-
- Roles -
-
- {roles} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_PERMISSIONS') && -
-
-
-
- Permissions -
-
- {permissions} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_STAFF_ROLES') && -
-
-
-
- Staff roles -
-
- {staff_roles} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_CUSTOMERS') && -
-
-
-
- Customers -
-
- {customers} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_TABLES') && -
-
-
-
- Tables -
-
- {tables} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_RESERVATIONS') && -
-
-
-
- Reservations -
-
- {reservations} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_TABS') && -
-
-
-
- Tabs -
-
- {tabs} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_LINE_ITEMS') && -
-
-
-
- Line items -
-
- {line_items} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_MENU_ITEMS') && -
-
-
-
- Menu items -
-
- {menu_items} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_INVENTORY_ITEMS') && -
-
-
-
- Inventory items -
-
- {inventory_items} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_SUPPLIERS') && -
-
-
-
- Suppliers -
-
- {suppliers} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_PAYMENTS') && -
-
-
-
- Payments -
-
- {payments} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_PROMOTIONS') && -
-
-
-
- Promotions -
-
- {promotions} -
-
-
- -
-
-
- } - - -
) diff --git a/frontend/src/stores/openAiSlice.ts b/frontend/src/stores/openAiSlice.ts index 1328235..2443b72 100644 --- a/frontend/src/stores/openAiSlice.ts +++ b/frontend/src/stores/openAiSlice.ts @@ -4,7 +4,7 @@ import axios from 'axios'; interface MainState { isFetchingQuery: boolean; errorMessage: string; - smartWidgets: any[]; + gptResponse: string | null; aiResponse: any | null; isAskingQuestion: boolean; @@ -18,7 +18,7 @@ interface MainState { const initialState: MainState = { isFetchingQuery: false, errorMessage: '', - smartWidgets: [], + gptResponse: null, aiResponse: null, isAskingQuestion: false, @@ -36,20 +36,6 @@ const fulfilledNotify = (state, msg, type?: string) => { state.notify.showNotification = true; }; -export const aiPrompt = createAsyncThunk( - 'openai/aiPrompt', - async (data: any, { rejectWithValue }) => { - try { - return await axios.post('/openai/create_widget', data); - } catch (error) { - if (!error.response) { - throw error; - } - return rejectWithValue(error.response.data); - } - }, -); - export const askGpt = createAsyncThunk( 'openai/askGpt', async (prompt: string, { rejectWithValue }) => { @@ -94,21 +80,6 @@ export const openAiSlice = createSlice({ }, }, extraReducers: (builder) => { - builder.addCase(aiPrompt.pending, (state) => { - state.isFetchingQuery = true; - }); - builder.addCase(aiPrompt.fulfilled, (state, action: Record) => { - state.isFetchingQuery = false; - state.errorMessage = ''; - state.smartWidgets.unshift(action.payload.data); - }); - - builder.addCase(aiPrompt.rejected, (state) => { - state.errorMessage = 'Something was wrong. Try again'; - state.isFetchingQuery = false; - state.smartWidgets = null; - }); - builder.addCase(askGpt.pending, (state) => { state.isAskingQuestion = true; state.gptResponse = null; diff --git a/frontend/src/stores/roles/rolesSlice.ts b/frontend/src/stores/roles/rolesSlice.ts index 8fe2ed7..d6b9984 100644 --- a/frontend/src/stores/roles/rolesSlice.ts +++ b/frontend/src/stores/roles/rolesSlice.ts @@ -7,7 +7,7 @@ interface MainState { loading: boolean count: number refetch: boolean; - rolesWidgets: any[]; + notify: { showNotification: boolean textNotification: string @@ -20,7 +20,7 @@ const initialState: MainState = { loading: false, count: 0, refetch: false, - rolesWidgets: [], + notify: { showNotification: false, textNotification: '',