Clear Overview
This commit is contained in:
parent
b88bdc0896
commit
9ee76a0742
@ -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 (
|
||||
<div
|
||||
className={`${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 dark:border-dark-700 p-6 ${cardsStyle} ${
|
||||
widget.widget_type === 'chart'
|
||||
? 'col-span-2'
|
||||
: 'lg:col-span-1 col-span-2'
|
||||
}`}
|
||||
>
|
||||
<div className='flex justify-between flex-col h-full'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='text-lg leading-tight text-gray-500 dark:text-dark-600 line-clamp-2'>
|
||||
{widget.label}
|
||||
</div>
|
||||
|
||||
{admin && (
|
||||
<BaseButton
|
||||
icon={icons.mdiClose}
|
||||
color='whiteDark'
|
||||
roundedFull
|
||||
onClick={deleteWidget}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='flex justify-center items-center h-full'>
|
||||
<div
|
||||
className={`${
|
||||
widget.widget_type === WidgetType.chart ? 'w-5/6 justify-center' : ''
|
||||
} items-center flex flex-grow justify-center`}
|
||||
>
|
||||
{widget.value ? (
|
||||
widget.widget_type === WidgetType.chart ? (
|
||||
widget.chart_type === WidgetChartType.bar ? (
|
||||
<BarChart widget={widget}/>
|
||||
) : widget.chart_type === WidgetChartType.line ? (
|
||||
<LineChart widget={widget}/>
|
||||
) : widget.chart_type === WidgetChartType.pie ? (
|
||||
<PieChart widget={widget}/>
|
||||
) : widget.chart_type === WidgetChartType.area ? (
|
||||
<AreaChart widget={widget}/>
|
||||
) : widget.chart_type === WidgetChartType.funnel ? (
|
||||
<AreaChart widget={widget}/>
|
||||
) : null
|
||||
) : (
|
||||
<div className='text-3xl leading-tight font-semibold truncate'>
|
||||
{widget.value}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className='text-center text-red-400'>
|
||||
Something went wrong, please try again or use a different query.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{widget.type === WidgetType.scalar && widget.mdiIcon && (
|
||||
<div className='flex justify-end w-1/4'>
|
||||
<BaseIcon
|
||||
className='text-blue-500 flex-shrink-0'
|
||||
w='w-16'
|
||||
h='h-16'
|
||||
size={48}
|
||||
fill={widget.color}
|
||||
path={icons[widget.mdiIcon]}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -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 && <ChartJSAreaChart widget={widget} />}
|
||||
{widget.lib_name === WidgetLibName.chartjs && (
|
||||
<ChartJSAreaChart widget={widget} />
|
||||
)}
|
||||
{widget.lib_name === WidgetLibName.apex && (
|
||||
<ApexAreaChart widget={widget} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<Chart
|
||||
options={optionsForLineChart(
|
||||
widget.value,
|
||||
widget.color_array,
|
||||
widget.currency,
|
||||
)}
|
||||
series={dataForLineChart(widget.value)}
|
||||
type={widget.chart_type}
|
||||
height={200}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<Line
|
||||
data={dataForBarChart(widget.value, widget.color_array)}
|
||||
options={options}
|
||||
height={350}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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 && <ChartJSBarChart widget={widget} />}
|
||||
{widget.lib_name === WidgetLibName.chartjs && (
|
||||
<ChartJSBarChart widget={widget} />
|
||||
)}
|
||||
{widget.lib_name === WidgetLibName.apex && (
|
||||
<ApexBarChart widget={widget} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<Chart
|
||||
options={optionsForBarChart(
|
||||
widget.value,
|
||||
widget.color_array,
|
||||
widget.currency,
|
||||
)}
|
||||
series={dataForBarChart(widget.value)}
|
||||
type={widget.chart_type}
|
||||
height={300}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<Bar
|
||||
data={dataForBarChart(widget.value, widget.color_array)}
|
||||
options={options()}
|
||||
height={350}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<Chart
|
||||
options={optionsForBarChart(
|
||||
widget.value,
|
||||
widget.color_array,
|
||||
widget.currency,
|
||||
)}
|
||||
series={dataForBarChart(widget.value)}
|
||||
type={widget.chart_type}
|
||||
height={300}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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 && <ChartJSLineChart widget={widget} />}
|
||||
{widget.lib_name === WidgetLibName.chartjs && (
|
||||
<ChartJSLineChart widget={widget} />
|
||||
)}
|
||||
{widget.lib_name === WidgetLibName.apex && (
|
||||
<ApexLineChart widget={widget} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<Chart
|
||||
options={optionsForLineChart(
|
||||
widget.value,
|
||||
widget.color_array,
|
||||
widget.currency,
|
||||
)}
|
||||
series={dataForLineChart(widget.value)}
|
||||
type={widget.chart_type}
|
||||
height={200}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<Line
|
||||
data={dataForBarChart(props.widget.value, props.widget.color_array)}
|
||||
options={options}
|
||||
height={350}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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 && <ChartJSPieChart widget={widget} />}
|
||||
{widget.lib_name === WidgetLibName.chartjs && (
|
||||
<ChartJSPieChart widget={widget} />
|
||||
)}
|
||||
{widget.lib_name === WidgetLibName.apex && (
|
||||
<ApexPieChart widget={widget} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<Chart
|
||||
options={optionsForPieChart(widget.value, widget.color_array)}
|
||||
series={dataForPieChart(widget.value)}
|
||||
type={widget.chart_type}
|
||||
height={200}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<Pie
|
||||
data={dataForBarChart(widget.value, widget.color_array)}
|
||||
options={options()}
|
||||
height={350}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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[];
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
import { humanize } from '../../helpers/humanize';
|
||||
|
||||
interface DataObject {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const findFirstNumericKey = (obj: Record<string, any>): 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(' / ');
|
||||
};
|
||||
@ -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 (
|
||||
<AsyncPaginate
|
||||
classNames={{
|
||||
control: () => 'px-1 py-2',
|
||||
}}
|
||||
classNamePrefix={'react-select'}
|
||||
instanceId={useId()}
|
||||
value={value}
|
||||
debounceTimeout={1000}
|
||||
loadOptions={callApi}
|
||||
onChange={handleChange}
|
||||
defaultOptions
|
||||
isDisabled={disabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<>
|
||||
<CardBox id="widgetCreator" className='mb-6 relative'>
|
||||
<BaseButton
|
||||
className='absolute top-0 right-0 m-4'
|
||||
icon={mdiCog}
|
||||
color='whiteDark'
|
||||
roundedFull
|
||||
onClick={openModal}
|
||||
/>
|
||||
<Formik
|
||||
initialValues={{
|
||||
description: '',
|
||||
}}
|
||||
onSubmit={(values, { resetForm }) => smartSearch(values, resetForm)}
|
||||
>
|
||||
<Form>
|
||||
<FormField
|
||||
label='Create Chart or Widget'
|
||||
help={
|
||||
isFetchingQuery ?
|
||||
'Loading...' :
|
||||
'Describe your new widget or chart in natural language. For example: "Number of admin users" OR "red chart with number of closed contracts grouped by month"'
|
||||
}
|
||||
>
|
||||
<Field type='input' name='description' disabled={isFetchingQuery} />
|
||||
</FormField>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
<Formik
|
||||
initialValues={{
|
||||
role: '',
|
||||
}}
|
||||
onSubmit={(values) => handleCloseModal(values)}
|
||||
>
|
||||
{({ submitForm }) => (
|
||||
<CardBoxModal
|
||||
title='Widget Creator Settings'
|
||||
buttonColor='info'
|
||||
buttonLabel='Done'
|
||||
isActive={isModalOpen}
|
||||
onConfirm={submitForm}
|
||||
onCancel={() => setIsModalOpen(false)}
|
||||
>
|
||||
<p>What role are we showing and creating widgets for?</p>
|
||||
|
||||
<Form>
|
||||
<FormField>
|
||||
<Field
|
||||
name='role'
|
||||
id='role'
|
||||
component={RoleSelect}
|
||||
options={widgetsRole?.role || []}
|
||||
itemRef={'roles'}
|
||||
currentUser={currentUser}
|
||||
></Field>
|
||||
</FormField>
|
||||
</Form>
|
||||
</CardBoxModal>
|
||||
)}
|
||||
</Formik>
|
||||
<ToastContainer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
{hasPermission(currentUser, 'CREATE_ROLES') && <WidgetCreator
|
||||
currentUser={currentUser}
|
||||
isFetchingQuery={isFetchingQuery}
|
||||
setWidgetsRole={setWidgetsRole}
|
||||
widgetsRole={widgetsRole}
|
||||
/>}
|
||||
{!!rolesWidgets.length &&
|
||||
hasPermission(currentUser, 'CREATE_ROLES') && (
|
||||
<p className=' text-gray-500 dark:text-gray-400 mb-4'>
|
||||
{`${widgetsRole?.role?.label || 'Users'}'s widgets`}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-6 grid-flow-dense'>
|
||||
{(isFetchingQuery || loading) && (
|
||||
<div className={` ${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 text-lg leading-tight text-gray-500 flex items-center ${cardsStyle} dark:border-dark-700 p-6`}>
|
||||
<BaseIcon
|
||||
className={`${iconsColor} animate-spin mr-5`}
|
||||
w='w-16'
|
||||
h='h-16'
|
||||
size={48}
|
||||
path={icon.mdiLoading}
|
||||
/>{' '}
|
||||
Loading widgets...
|
||||
</div>
|
||||
)}
|
||||
|
||||
{ rolesWidgets &&
|
||||
rolesWidgets.map((widget) => (
|
||||
<SmartWidget
|
||||
key={widget.id}
|
||||
userId={currentUser?.id}
|
||||
widget={widget}
|
||||
roleId={widgetsRole?.role?.value || ''}
|
||||
admin={hasPermission(currentUser, 'CREATE_ROLES')}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!!rolesWidgets.length && <hr className='my-6 text-midnightBlueTheme-mainBG ' />}
|
||||
|
||||
<div id="dashboard" className='grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6'>
|
||||
|
||||
|
||||
{hasPermission(currentUser, 'READ_USERS') && <Link href={'/users/users-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Users
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{users}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={icon.mdiAccountGroup || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_ROLES') && <Link href={'/roles/roles-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Roles
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{roles}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={icon.mdiShieldAccountVariantOutline || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_PERMISSIONS') && <Link href={'/permissions/permissions-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Permissions
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{permissions}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={icon.mdiShieldAccountOutline || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_STAFF_ROLES') && <Link href={'/staff_roles/staff_roles-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Staff roles
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{staff_roles}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiShieldAccount' in icon ? icon['mdiShieldAccount' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_CUSTOMERS') && <Link href={'/customers/customers-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Customers
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{customers}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiAccount' in icon ? icon['mdiAccount' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_TABLES') && <Link href={'/tables/tables-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Tables
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{tables}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiSeat' in icon ? icon['mdiSeat' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_RESERVATIONS') && <Link href={'/reservations/reservations-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Reservations
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{reservations}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiCalendar' in icon ? icon['mdiCalendar' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_TABS') && <Link href={'/tabs/tabs-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Tabs
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{tabs}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiWallet' in icon ? icon['mdiWallet' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_LINE_ITEMS') && <Link href={'/line_items/line_items-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Line items
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{line_items}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiFormatListBulleted' in icon ? icon['mdiFormatListBulleted' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_MENU_ITEMS') && <Link href={'/menu_items/menu_items-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Menu items
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{menu_items}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiFood' in icon ? icon['mdiFood' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_INVENTORY_ITEMS') && <Link href={'/inventory_items/inventory_items-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Inventory items
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{inventory_items}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiWarehouse' in icon ? icon['mdiWarehouse' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_SUPPLIERS') && <Link href={'/suppliers/suppliers-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Suppliers
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{suppliers}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiTruck' in icon ? icon['mdiTruck' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_PAYMENTS') && <Link href={'/payments/payments-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Payments
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{payments}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiCreditCard' in icon ? icon['mdiCreditCard' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_PROMOTIONS') && <Link href={'/promotions/promotions-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Promotions
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{promotions}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiTag' in icon ? icon['mdiTag' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
|
||||
</div>
|
||||
</SectionMain>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -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<any, any>) => {
|
||||
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;
|
||||
|
||||
@ -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: '',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user