diff --git a/.gitignore b/.gitignore index 224ad62..bd90973 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ yarn-error.log* !.yarn/versions .pnp.* +yarn.lock +package-lock.json diff --git a/package.json b/package.json index 905edda..846b556 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@mui/x-date-pickers": "6.19.0", "@popperjs/core": "2.11.8", "@reduxjs/toolkit": "2.0.1", + "apexcharts": "^3.45.2", "autosuggest-highlight": "3.3.4", "axios": "1.6.5", "axios-mock-adapter": "1.21.5", @@ -37,6 +38,7 @@ "prismjs": "1.29.0", "qs": "6.11.2", "react": "18.2.0", + "react-apexcharts": "^1.4.1", "react-app-alias": "2.2.2", "react-autosuggest": "10.1.0", "react-dom": "18.2.0", @@ -82,9 +84,9 @@ "@tailwindcss/aspect-ratio": "0.4.2", "@tailwindcss/typography": "0.5.10", "@types/autosuggest-highlight": "3.2.3", + "@types/babel-traverse": "6.25.10", "@types/babel__parser": "7.1.1", "@types/babel__traverse": "7.20.5", - "@types/babel-traverse": "6.25.10", "@types/crypto-js": "4.2.1", "@types/draft-js": "0.11.17", "@types/draftjs-to-html": "0.8.4", diff --git a/src/app/configs/navigation-i18n/es.ts b/src/app/configs/navigation-i18n/es.ts new file mode 100644 index 0000000..f50c65c --- /dev/null +++ b/src/app/configs/navigation-i18n/es.ts @@ -0,0 +1,7 @@ +const locale = { + APPLICATIONS: 'Applications', + EXAMPLE: 'Example', + DASHBOARD: 'Tablero' +}; + +export default locale; diff --git a/src/app/configs/navigationConfig.ts b/src/app/configs/navigationConfig.ts index 58b83cf..dbe4bb7 100644 --- a/src/app/configs/navigationConfig.ts +++ b/src/app/configs/navigationConfig.ts @@ -19,6 +19,14 @@ const navigationConfig: FuseNavItemType[] = [ type: 'item', icon: 'heroicons-outline:star', url: 'example' + }, + { + id: 'dashboard-component', + title: 'Tablero', + translate: 'TABLERO', + type: 'item', + icon: 'heroicons-outline:star', + url: 'dashboards/project' } ]; diff --git a/src/app/configs/routesConfig.tsx b/src/app/configs/routesConfig.tsx index 5ced73e..2546ba5 100644 --- a/src/app/configs/routesConfig.tsx +++ b/src/app/configs/routesConfig.tsx @@ -8,8 +8,9 @@ import SignUpConfig from '../main/sign-up/SignUpConfig'; import SignOutConfig from '../main/sign-out/SignOutConfig'; import Error404Page from '../main/404/Error404Page'; import ExampleConfig from '../main/example/ExampleConfig'; +import ProjectDashboardAppConfig from '../main/dashboard/project/ProjectDashboardAppConfig'; -const routeConfigs: FuseRouteConfigsType = [ExampleConfig, SignOutConfig, SignInConfig, SignUpConfig]; +const routeConfigs: FuseRouteConfigsType = [ExampleConfig, SignOutConfig, SignInConfig, SignUpConfig, ProjectDashboardAppConfig]; /** * The routes of the application. diff --git a/src/app/main/dashboard/project/ProjectDashboardApi.ts b/src/app/main/dashboard/project/ProjectDashboardApi.ts new file mode 100644 index 0000000..478d785 --- /dev/null +++ b/src/app/main/dashboard/project/ProjectDashboardApi.ts @@ -0,0 +1,51 @@ +import { apiService as api } from 'app/store/apiService'; + +export const addTagTypes = ['project_dashboard_widgets', 'project_dashboard_projects'] as const; +const ProjectDashboardApi = api + .enhanceEndpoints({ + addTagTypes + }) + .injectEndpoints({ + endpoints: (build) => ({ + getProjectDashboardWidgets: build.query< + GetProjectDashboardWidgetsApiResponse, + GetProjectDashboardWidgetsApiArg + >({ + query: () => ({ url: `/mock-api/dashboards/project/widgets` }), + providesTags: ['project_dashboard_widgets'] + }), + getProjectDashboardProjects: build.query< + GetProjectDashboardProjectsApiResponse, + GetProjectDashboardProjectsApiArg + >({ + query: () => ({ url: `/mock-api/dashboards/project/projects` }), + providesTags: ['project_dashboard_projects'] + }) + }), + overrideExisting: false + }); +export default ProjectDashboardApi; + +export type GetProjectDashboardWidgetsApiResponse = /** status 200 OK */ object; +export type GetProjectDashboardWidgetsApiArg = void; + +export type GetProjectDashboardProjectsApiResponse = /** status 200 OK */ ProjectType[]; +export type GetProjectDashboardProjectsApiArg = void; + +export type ProjectType = { + id: number; + name: string; +}; + +export const { useGetProjectDashboardWidgetsQuery, useGetProjectDashboardProjectsQuery } = ProjectDashboardApi; + +export type ProjectDashboardApiType = { + [ProjectDashboardApi.reducerPath]: ReturnType; +}; + +export const selectWidget = + (id: string) => + (state: ProjectDashboardApiType) => { + const widgets = ProjectDashboardApi.endpoints.getProjectDashboardWidgets.select()(state)?.data; + return widgets?.[id] as T; + }; diff --git a/src/app/main/dashboard/project/ProjectDashboardApp.tsx b/src/app/main/dashboard/project/ProjectDashboardApp.tsx new file mode 100644 index 0000000..81d1983 --- /dev/null +++ b/src/app/main/dashboard/project/ProjectDashboardApp.tsx @@ -0,0 +1,86 @@ +import FusePageSimple from '@fuse/core/FusePageSimple'; +import Tab from '@mui/material/Tab'; +import Tabs from '@mui/material/Tabs'; +import { useState } from 'react'; +import Box from '@mui/material/Box'; +import { styled } from '@mui/material/styles'; +import * as React from 'react'; +import FuseLoading from '@fuse/core/FuseLoading'; +import ProjectDashboardAppHeader from './ProjectDashboardAppHeader'; +import HomeTab from './tabs/home/HomeTab'; +import TeamTab from './tabs/team/TeamTab'; +import BudgetTab from './tabs/budget/BudgetTab'; +import { useGetProjectDashboardWidgetsQuery } from './ProjectDashboardApi'; + +const Root = styled(FusePageSimple)(({ theme }) => ({ + '& .FusePageSimple-header': { + backgroundColor: theme.palette.background.paper, + boxShadow: `inset 0 0 0 1px ${theme.palette.divider}` + } +})); + +/** + * The ProjectDashboardApp page. + */ +function ProjectDashboardApp() { + const { isLoading } = useGetProjectDashboardWidgetsQuery(); + + const [tabValue, setTabValue] = useState(0); + + function handleChangeTab(event: React.SyntheticEvent, value: number) { + setTabValue(value); + } + + if (isLoading) { + return ; + } + + return ( + } + content={ +
+ + ) + }} + > + + + + + {tabValue === 0 && } + {tabValue === 1 && } + {tabValue === 2 && } +
+ } + /> + ); +} + +export default ProjectDashboardApp; diff --git a/src/app/main/dashboard/project/ProjectDashboardAppConfig.tsx b/src/app/main/dashboard/project/ProjectDashboardAppConfig.tsx new file mode 100644 index 0000000..48357a7 --- /dev/null +++ b/src/app/main/dashboard/project/ProjectDashboardAppConfig.tsx @@ -0,0 +1,22 @@ +import { lazy } from 'react'; + +const ProjectDashboardApp = lazy(() => import('./ProjectDashboardApp')); + +/** + * The ProjectDashboardApp configuration. + */ +const ProjectDashboardAppConfig = { + settings: { + layout: { + config: {} + } + }, + routes: [ + { + path: 'dashboards/project', + element: + } + ] +}; + +export default ProjectDashboardAppConfig; diff --git a/src/app/main/dashboard/project/ProjectDashboardAppHeader.tsx b/src/app/main/dashboard/project/ProjectDashboardAppHeader.tsx new file mode 100644 index 0000000..9f5b8e2 --- /dev/null +++ b/src/app/main/dashboard/project/ProjectDashboardAppHeader.tsx @@ -0,0 +1,170 @@ +import Avatar from '@mui/material/Avatar'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import Typography from '@mui/material/Typography'; +import { useState } from 'react'; +import _ from '@lodash'; +import Button from '@mui/material/Button'; +import FuseSvgIcon from '@fuse/core/FuseSvgIcon'; +import FuseLoading from '@fuse/core/FuseLoading'; +import { darken } from '@mui/material/styles'; +import { useSelector } from 'react-redux'; +import { selectUser } from 'src/app/auth/user/store/userSlice'; +import { useGetProjectDashboardProjectsQuery } from './ProjectDashboardApi'; + +/** + * The ProjectDashboardAppHeader page. + */ +function ProjectDashboardAppHeader() { + const { isLoading } = useGetProjectDashboardProjectsQuery(); + + + const projects=[ + { + "id": 1, + "name": "ACME Corp. Backend App" + }, + { + "id": 2, + "name": "ACME Corp. Frontend App" + }, + { + "id": 3, + "name": "Creapond" + }, + { + "id": 4, + "name": "Withinpixels" + } + ] + + const user = useSelector(selectUser); + + const [selectedProject, setSelectedProject] = useState<{ id: number; menuEl: HTMLElement | null }>({ + id: 1, + menuEl: null + }); + + function handleChangeProject(id: number) { + setSelectedProject({ + id, + menuEl: null + }); + } + + function handleOpenProjectMenu(event: React.MouseEvent) { + setSelectedProject({ + id: selectedProject.id, + menuEl: event.currentTarget + }); + } + + function handleCloseProjectMenu() { + setSelectedProject({ + id: selectedProject.id, + menuEl: null + }); + } + + if (isLoading) { + return ; + } + + return ( +
+
+
+ darken(theme.palette.background.default, 0.05), + color: (theme) => theme.palette.text.secondary + }} + className="flex-0 w-64 h-64" + alt="user photo" + src={user?.data?.photoURL} + > + {user?.data?.displayName?.[0]} + +
+ + {`Welcome back, ${user.data.displayName}!`} + + +
+ + heroicons-solid:bell + + + You have 2 new messages and 15 new tasks + +
+
+
+
+ + +
+
+
+ + + {/* {projects && + projects.map((project) => ( + { + handleChangeProject(project.id); + }} + > + {project.name} + + ))} */} + +
+
+ ); +} + +export default ProjectDashboardAppHeader; diff --git a/src/app/main/dashboard/project/tabs/budget/BudgetTab.tsx b/src/app/main/dashboard/project/tabs/budget/BudgetTab.tsx new file mode 100644 index 0000000..eafe969 --- /dev/null +++ b/src/app/main/dashboard/project/tabs/budget/BudgetTab.tsx @@ -0,0 +1,70 @@ +import { motion } from 'framer-motion'; +import BudgetDistributionWidget from './widgets/BudgetDistributionWidget'; +import WeeklyExpensesWidget from './widgets/WeeklyExpensesWidget'; +import MonthlyExpensesWidget from './widgets/MonthlyExpensesWidget'; +import YearlyExpensesWidget from './widgets/YearlyExpensesWidget'; +import BudgetDetailsWidget from './widgets/BudgetDetailsWidget'; + +/** + * The BudgetTab component. + */ +function BudgetTab() { + const container = { + show: { + transition: { + staggerChildren: 0.04 + } + } + }; + + const item = { + hidden: { opacity: 0, y: 20 }, + show: { opacity: 1, y: 0 } + }; + + return ( + + + + + +
+ + + + + + + + + +
+ + + + +
+ ); +} + +export default BudgetTab; diff --git a/src/app/main/dashboard/project/tabs/budget/widgets/BudgetDetailsWidget.tsx b/src/app/main/dashboard/project/tabs/budget/widgets/BudgetDetailsWidget.tsx new file mode 100644 index 0000000..9ac5fe8 --- /dev/null +++ b/src/app/main/dashboard/project/tabs/budget/widgets/BudgetDetailsWidget.tsx @@ -0,0 +1,118 @@ +import Paper from '@mui/material/Paper'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Typography from '@mui/material/Typography'; +import { memo } from 'react'; +import Chip from '@mui/material/Chip'; +import { useSelector } from 'react-redux'; +import BudgetDetailsDataType from './types/BudgetDetailsDataType'; +import { selectWidget } from '../../../ProjectDashboardApi'; + +/** + * The BudgetDetailsWidget widget. + */ +function BudgetDetailsWidget() { + const widget = useSelector(selectWidget('budgetDetails')); + + if (!widget) { + return null; + } + + const { columns, rows } = widget; + + return ( + + Budget Details + +
+ + + + {columns.map((column, index) => ( + + + {column} + + + ))} + + + + + {rows.map((row, index) => ( + + {Object.entries(row).map(([key, value]) => { + switch (key) { + case 'type': { + return ( + + + + ); + } + case 'total': + case 'expensesAmount': + case 'remainingAmount': { + return ( + + + {value.toLocaleString('en-US', { + style: 'currency', + currency: 'USD' + })} + + + ); + } + case 'expensesPercentage': + case 'remainingPercentage': { + return ( + + {`${value}%`} + + ); + } + default: { + return ( + + {value} + + ); + } + } + })} + + ))} + +
+
+
+ ); +} + +export default memo(BudgetDetailsWidget); diff --git a/src/app/main/dashboard/project/tabs/budget/widgets/BudgetDistributionWidget.tsx b/src/app/main/dashboard/project/tabs/budget/widgets/BudgetDistributionWidget.tsx new file mode 100644 index 0000000..fa10d43 --- /dev/null +++ b/src/app/main/dashboard/project/tabs/budget/widgets/BudgetDistributionWidget.tsx @@ -0,0 +1,105 @@ +import Paper from '@mui/material/Paper'; +import { useTheme } from '@mui/material/styles'; +import Typography from '@mui/material/Typography'; +import { memo } from 'react'; +import ReactApexChart from 'react-apexcharts'; +import { ApexOptions } from 'apexcharts'; +import { useSelector } from 'react-redux'; +import BudgetDistributionDataType from './types/BudgetDistributionDataType'; +import { selectWidget } from '../../../ProjectDashboardApi'; + +/** + * The BudgetDistributionWidget widget. + */ +function BudgetDistributionWidget() { + const widget = useSelector(selectWidget('budgetDistribution')); + + if (!widget) { + return null; + } + + const { categories, series } = widget; + + const theme = useTheme(); + + const chartOptions: ApexOptions = { + chart: { + fontFamily: 'inherit', + foreColor: 'inherit', + height: '100%', + type: 'radar', + sparkline: { + enabled: true + } + }, + colors: [theme.palette.secondary.main], + dataLabels: { + enabled: true, + formatter: (val: string) => `${val}%`, + textAnchor: 'start', + style: { + fontSize: '13px', + fontWeight: 500 + }, + background: { + borderWidth: 0, + padding: 4 + }, + offsetY: -15 + }, + markers: { + strokeColors: theme.palette.primary.main, + strokeWidth: 4 + }, + plotOptions: { + radar: { + polygons: { + strokeColors: theme.palette.divider, + connectorColors: theme.palette.divider + } + } + }, + stroke: { + width: 2 + }, + tooltip: { + theme: 'dark', + y: { + formatter: (val) => `${val}%` + } + }, + xaxis: { + labels: { + show: true, + style: { + fontSize: '12px', + fontWeight: '500' + } + }, + categories + }, + yaxis: { + max: (max) => parseInt((max + 10).toFixed(0), 10), + tickAmount: 7 + } + }; + return ( + + + Budget Distribution + + +
+ +
+
+ ); +} + +export default memo(BudgetDistributionWidget); diff --git a/src/app/main/dashboard/project/tabs/budget/widgets/MonthlyExpensesWidget.tsx b/src/app/main/dashboard/project/tabs/budget/widgets/MonthlyExpensesWidget.tsx new file mode 100644 index 0000000..7537b8b --- /dev/null +++ b/src/app/main/dashboard/project/tabs/budget/widgets/MonthlyExpensesWidget.tsx @@ -0,0 +1,100 @@ +import Paper from '@mui/material/Paper'; +import FuseSvgIcon from '@fuse/core/FuseSvgIcon'; +import IconButton from '@mui/material/IconButton'; +import ReactApexChart from 'react-apexcharts'; +import { useTheme } from '@mui/material/styles'; +import Typography from '@mui/material/Typography'; +import { ApexOptions } from 'apexcharts'; +import { useSelector } from 'react-redux'; +import ExpensesDataType from './types/ExpensesDataType'; +import { selectWidget } from '../../../ProjectDashboardApi'; + +/** + * The MonthlyExpensesWidget widget. + */ +function MonthlyExpensesWidget() { + const widget = useSelector(selectWidget('monthlyExpenses')); + + if (!widget) { + return null; + } + + const { amount, series, labels } = widget; + const theme = useTheme(); + + const chartOptions: ApexOptions = { + chart: { + animations: { + enabled: false + }, + fontFamily: 'inherit', + foreColor: 'inherit', + height: '100%', + type: 'line', + sparkline: { + enabled: true + } + }, + colors: [theme.palette.success.main], + stroke: { + curve: 'smooth' + }, + tooltip: { + theme: 'dark' + }, + xaxis: { + type: 'category', + categories: labels + }, + yaxis: { + labels: { + formatter: (val) => `$${val}` + } + } + }; + return ( + +
+
Monthly Expenses
+
+ + heroicons-solid:dots-vertical + +
+
+
+
+
+ {amount.toLocaleString('en-US', { + style: 'currency', + currency: 'USD' + })} +
+
+ + heroicons-solid:trending-up + + + 2% + above projected + +
+
+
+ +
+
+
+ ); +} + +export default MonthlyExpensesWidget; diff --git a/src/app/main/dashboard/project/tabs/budget/widgets/WeeklyExpensesWidget.tsx b/src/app/main/dashboard/project/tabs/budget/widgets/WeeklyExpensesWidget.tsx new file mode 100644 index 0000000..4e9efe1 --- /dev/null +++ b/src/app/main/dashboard/project/tabs/budget/widgets/WeeklyExpensesWidget.tsx @@ -0,0 +1,101 @@ +import Paper from '@mui/material/Paper'; +import FuseSvgIcon from '@fuse/core/FuseSvgIcon'; +import IconButton from '@mui/material/IconButton'; +import ReactApexChart from 'react-apexcharts'; +import { useTheme } from '@mui/material/styles'; +import Typography from '@mui/material/Typography'; +import { ApexOptions } from 'apexcharts'; +import { useSelector } from 'react-redux'; +import ExpensesDataType from './types/ExpensesDataType'; +import { selectWidget } from '../../../ProjectDashboardApi'; + +/** + * The MonthlyExpensesWidget widget. + */ +function WeeklyExpensesWidget() { + const widget = useSelector(selectWidget('weeklyExpenses')); + + if (!widget) { + return null; + } + + const { amount, series, labels } = widget; + + const theme = useTheme(); + + const chartOptions: ApexOptions = { + chart: { + animations: { + enabled: false + }, + fontFamily: 'inherit', + foreColor: 'inherit', + height: '100%', + type: 'line', + sparkline: { + enabled: true + } + }, + colors: [theme.palette.secondary.main], + stroke: { + curve: 'smooth' + }, + tooltip: { + theme: 'dark' + }, + xaxis: { + type: 'category', + categories: labels + }, + yaxis: { + labels: { + formatter: (val) => `$${val}` + } + } + }; + return ( + +
+
Weekly Expenses
+
+ + heroicons-solid:dots-vertical + +
+
+
+
+
+ {amount.toLocaleString('en-US', { + style: 'currency', + currency: 'USD' + })} +
+
+ + heroicons-solid:trending-down + + + 2% + below projected + +
+
+
+ +
+
+
+ ); +} + +export default WeeklyExpensesWidget; diff --git a/src/app/main/dashboard/project/tabs/budget/widgets/YearlyExpensesWidget.tsx b/src/app/main/dashboard/project/tabs/budget/widgets/YearlyExpensesWidget.tsx new file mode 100644 index 0000000..457342c --- /dev/null +++ b/src/app/main/dashboard/project/tabs/budget/widgets/YearlyExpensesWidget.tsx @@ -0,0 +1,100 @@ +import Paper from '@mui/material/Paper'; +import FuseSvgIcon from '@fuse/core/FuseSvgIcon'; +import IconButton from '@mui/material/IconButton'; +import ReactApexChart from 'react-apexcharts'; +import { useTheme } from '@mui/material/styles'; +import Typography from '@mui/material/Typography'; +import { ApexOptions } from 'apexcharts'; +import { useSelector } from 'react-redux'; +import ExpensesDataType from './types/ExpensesDataType'; +import { selectWidget } from '../../../ProjectDashboardApi'; + +/** + * The YearlyExpensesWidget widget. + */ +function YearlyExpensesWidget() { + const widget = useSelector(selectWidget('yearlyExpenses')); + + if (!widget) { + return null; + } + + const { amount, series, labels } = widget; + const theme = useTheme(); + + const chartOptions: ApexOptions = { + chart: { + animations: { + enabled: false + }, + fontFamily: 'inherit', + foreColor: 'inherit', + height: '100%', + type: 'line', + sparkline: { + enabled: true + } + }, + colors: [theme.palette.error.main], + stroke: { + curve: 'smooth' + }, + tooltip: { + theme: 'dark' + }, + xaxis: { + type: 'category', + categories: labels + }, + yaxis: { + labels: { + formatter: (val) => `$${val}` + } + } + }; + return ( + +
+
Yearly Expenses
+
+ + heroicons-solid:dots-vertical + +
+
+
+
+
+ {amount.toLocaleString('en-US', { + style: 'currency', + currency: 'USD' + })} +
+
+ + heroicons-solid:trending-up + + + 2% + above projected + +
+
+
+ +
+
+
+ ); +} + +export default YearlyExpensesWidget; diff --git a/src/app/main/dashboard/project/tabs/budget/widgets/types/BudgetDetailsDataType.ts b/src/app/main/dashboard/project/tabs/budget/widgets/types/BudgetDetailsDataType.ts new file mode 100644 index 0000000..412c00e --- /dev/null +++ b/src/app/main/dashboard/project/tabs/budget/widgets/types/BudgetDetailsDataType.ts @@ -0,0 +1,21 @@ +/** + * The type definition for a row in the budget details table. + */ +type BudgetDetailsRow = { + type: string; + total: number; + expensesAmount: number; + expensesPercentage: number; + remainingAmount: number; + remainingPercentage: number; +}; + +/** + * The type definition for the data used to populate the budget details table. + */ +type BudgetDetailsDataType = { + columns: string[]; + rows: BudgetDetailsRow[]; +}; + +export default BudgetDetailsDataType; diff --git a/src/app/main/dashboard/project/tabs/budget/widgets/types/BudgetDistributionDataType.ts b/src/app/main/dashboard/project/tabs/budget/widgets/types/BudgetDistributionDataType.ts new file mode 100644 index 0000000..a0e0085 --- /dev/null +++ b/src/app/main/dashboard/project/tabs/budget/widgets/types/BudgetDistributionDataType.ts @@ -0,0 +1,17 @@ +/** + * The type definition for the data used to populate the budget distribution chart series. + */ +type BudgetDistributionSeriesData = { + name: string; + data: number[]; +}; + +/** + * The type definition for the data used to populate the budget distribution chart. + */ +type BudgetDistributionDataType = { + categories: string[]; + series: BudgetDistributionSeriesData[]; +}; + +export default BudgetDistributionDataType; diff --git a/src/app/main/dashboard/project/tabs/budget/widgets/types/ExpensesDataType.ts b/src/app/main/dashboard/project/tabs/budget/widgets/types/ExpensesDataType.ts new file mode 100644 index 0000000..015627f --- /dev/null +++ b/src/app/main/dashboard/project/tabs/budget/widgets/types/ExpensesDataType.ts @@ -0,0 +1,18 @@ +/** + * The type definition for the data used to populate the expenses chart. + */ +type ExpensesSeriesData = { + name: string; + data: number[]; +}; + +/** + * The type definition for the data used to populate the expenses chart. + */ +type ExpensesDataType = { + amount: number; + labels: string[]; + series: ExpensesSeriesData[]; +}; + +export default ExpensesDataType; diff --git a/src/app/main/dashboard/project/tabs/home/HomeTab.tsx b/src/app/main/dashboard/project/tabs/home/HomeTab.tsx new file mode 100644 index 0000000..6a1075f --- /dev/null +++ b/src/app/main/dashboard/project/tabs/home/HomeTab.tsx @@ -0,0 +1,68 @@ +import { motion } from 'framer-motion'; +import SummaryWidget from './widgets/SummaryWidget'; +import OverdueWidget from './widgets/OverdueWidget'; +import IssuesWidget from './widgets/IssuesWidget'; +import FeaturesWidget from './widgets/FeaturesWidget'; +import GithubIssuesWidget from './widgets/GithubIssuesWidget'; +import TaskDistributionWidget from './widgets/TaskDistributionWidget'; +import ScheduleWidget from './widgets/ScheduleWidget'; + +/** + * The HomeTab component. + */ +function HomeTab() { + const container = { + show: { + transition: { + staggerChildren: 0.04 + } + } + }; + + const item = { + hidden: { opacity: 0, y: 20 }, + show: { opacity: 1, y: 0 } + }; + + return ( + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +export default HomeTab; diff --git a/src/app/main/dashboard/project/tabs/home/widgets/FeaturesWidget.tsx b/src/app/main/dashboard/project/tabs/home/widgets/FeaturesWidget.tsx new file mode 100644 index 0000000..f89333f --- /dev/null +++ b/src/app/main/dashboard/project/tabs/home/widgets/FeaturesWidget.tsx @@ -0,0 +1,54 @@ +import IconButton from '@mui/material/IconButton'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; +import { memo } from 'react'; +import FuseSvgIcon from '@fuse/core/FuseSvgIcon'; +import { useSelector } from 'react-redux'; +import WidgetDataType from './types/WidgetDataType'; +import { selectWidget } from '../../../ProjectDashboardApi'; + +/** + * The FeaturesWidget widget. + */ +function FeaturesWidget() { + const widget = useSelector(selectWidget('features')); + + if (!widget) { + return null; + } + + const { data, title } = widget; + + return ( + +
+ + {title} + + + heroicons-outline:dots-vertical + +
+
+ + {String(data.count)} + + {data.name} +
+ + {data.extra.name}:{String(data.extra.count)} + +
+ ); +} + +export default memo(FeaturesWidget); diff --git a/src/app/main/dashboard/project/tabs/home/widgets/GithubIssuesWidget.tsx b/src/app/main/dashboard/project/tabs/home/widgets/GithubIssuesWidget.tsx new file mode 100644 index 0000000..3be5499 --- /dev/null +++ b/src/app/main/dashboard/project/tabs/home/widgets/GithubIssuesWidget.tsx @@ -0,0 +1,249 @@ +import Paper from '@mui/material/Paper'; +import { lighten, useTheme } from '@mui/material/styles'; +import Tab from '@mui/material/Tab'; +import Tabs from '@mui/material/Tabs'; +import Typography from '@mui/material/Typography'; +import { memo, useEffect, useState } from 'react'; +import ReactApexChart from 'react-apexcharts'; +import Box from '@mui/material/Box'; +import { ApexOptions } from 'apexcharts'; +import { useSelector } from 'react-redux'; +import GithubIssuesDataType from './types/GithubIssuesDataType'; +import { selectWidget } from '../../../ProjectDashboardApi'; + +/** + * The GithubIssuesWidget widget. + */ +function GithubIssuesWidget() { + const theme = useTheme(); + const [awaitRender, setAwaitRender] = useState(true); + const [tabValue, setTabValue] = useState(0); + + const widget = useSelector(selectWidget('githubIssues')); + + if (!widget) { + return null; + } + + const { overview, series, ranges, labels } = widget; + const currentRange = Object.keys(ranges)[tabValue]; + + const chartOptions: ApexOptions = { + chart: { + fontFamily: 'inherit', + foreColor: 'inherit', + height: '100%', + type: 'line', + toolbar: { + show: false + }, + zoom: { + enabled: false + } + }, + colors: [theme.palette.primary.main, theme.palette.secondary.main], + labels, + dataLabels: { + enabled: true, + enabledOnSeries: [0], + background: { + borderWidth: 0 + } + }, + grid: { + borderColor: theme.palette.divider + }, + legend: { + show: false + }, + plotOptions: { + bar: { + columnWidth: '50%' + } + }, + states: { + hover: { + filter: { + type: 'darken', + value: 0.75 + } + } + }, + stroke: { + width: [3, 0] + }, + tooltip: { + followCursor: true, + theme: theme.palette.mode + }, + xaxis: { + axisBorder: { + show: false + }, + axisTicks: { + color: theme.palette.divider + }, + labels: { + style: { + colors: theme.palette.text.secondary + } + }, + tooltip: { + enabled: false + } + }, + yaxis: { + labels: { + offsetX: -16, + style: { + colors: theme.palette.text.secondary + } + } + } + }; + + useEffect(() => { + setAwaitRender(false); + }, []); + + if (awaitRender) { + return null; + } + + return ( + +
+ + Github Issues Summary + +
+ setTabValue(value)} + indicatorColor="secondary" + textColor="inherit" + variant="scrollable" + scrollButtons={false} + className="-mx-4 min-h-40" + classes={{ indicator: 'flex justify-center bg-transparent w-full h-full' }} + TabIndicatorProps={{ + children: ( + + ) + }} + > + {Object.entries(ranges).map(([key, label]) => ( + + ))} + +
+
+
+
+ + New vs. Closed + +
+ +
+
+
+ + Overview + +
+
+ + {overview[currentRange]['new-issues']} + + New Issues +
+
+ + {overview[currentRange]['closed-issues']} + + Closed +
+ + _theme.palette.mode === 'light' + ? lighten(theme.palette.background.default, 0.4) + : lighten(theme.palette.background.default, 0.02) + }} + className="col-span-2 sm:col-span-1 flex flex-col items-center justify-center py-32 px-4 rounded-2xl" + > + + {overview[currentRange].fixed} + + Fixed + + + _theme.palette.mode === 'light' + ? lighten(theme.palette.background.default, 0.4) + : lighten(theme.palette.background.default, 0.02) + }} + className="col-span-2 sm:col-span-1 flex flex-col items-center justify-center py-32 px-4 rounded-2xl" + > + + {overview[currentRange]['wont-fix']} + + Won't Fix + + + _theme.palette.mode === 'light' + ? lighten(theme.palette.background.default, 0.4) + : lighten(theme.palette.background.default, 0.02) + }} + className="col-span-2 sm:col-span-1 flex flex-col items-center justify-center py-32 px-4 rounded-2xl" + > + + {overview[currentRange]['re-opened']} + + Re-opened + + + _theme.palette.mode === 'light' + ? lighten(theme.palette.background.default, 0.4) + : lighten(theme.palette.background.default, 0.02) + }} + className="col-span-2 sm:col-span-1 flex flex-col items-center justify-center py-32 px-4 rounded-2xl" + > + + {overview[currentRange]['needs-triage']} + + Needs Triage + +
+
+
+
+ ); +} + +export default memo(GithubIssuesWidget); diff --git a/src/app/main/dashboard/project/tabs/home/widgets/IssuesWidget.tsx b/src/app/main/dashboard/project/tabs/home/widgets/IssuesWidget.tsx new file mode 100644 index 0000000..221886f --- /dev/null +++ b/src/app/main/dashboard/project/tabs/home/widgets/IssuesWidget.tsx @@ -0,0 +1,53 @@ +import IconButton from '@mui/material/IconButton'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; +import { memo } from 'react'; +import FuseSvgIcon from '@fuse/core/FuseSvgIcon'; +import { useSelector } from 'react-redux'; +import { selectWidget } from '../../../ProjectDashboardApi'; +import WidgetDataType from './types/WidgetDataType'; + +/** + * The IssuesWidget widget. + */ +function IssuesWidget() { + const widget = useSelector(selectWidget('issues')); + + if (!widget) { + return null; + } + const { data, title } = widget; + + return ( + +
+ + {title} + + + heroicons-outline:dots-vertical + +
+
+ + {String(data.count)} + + {data.name} +
+ + {data.extra.name}:{String(data.extra.count)} + +
+ ); +} + +export default memo(IssuesWidget); diff --git a/src/app/main/dashboard/project/tabs/home/widgets/OverdueWidget.tsx b/src/app/main/dashboard/project/tabs/home/widgets/OverdueWidget.tsx new file mode 100644 index 0000000..973a3d3 --- /dev/null +++ b/src/app/main/dashboard/project/tabs/home/widgets/OverdueWidget.tsx @@ -0,0 +1,54 @@ +import IconButton from '@mui/material/IconButton'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; +import { memo } from 'react'; +import FuseSvgIcon from '@fuse/core/FuseSvgIcon'; +import { useSelector } from 'react-redux'; +import { selectWidget } from '../../../ProjectDashboardApi'; +import WidgetDataType from './types/WidgetDataType'; + +/** + * The OverdueWidget widget. + */ +function OverdueWidget() { + const widget = useSelector(selectWidget('overdue')); + + if (!widget) { + return null; + } + + const { data, title } = widget; + + return ( + +
+ + {title} + + + heroicons-outline:dots-vertical + +
+
+ + {String(data.count)} + + {data.name} +
+ + {data.extra.name}:{String(data.extra.count)} + +
+ ); +} + +export default memo(OverdueWidget); diff --git a/src/app/main/dashboard/project/tabs/home/widgets/ScheduleWidget.tsx b/src/app/main/dashboard/project/tabs/home/widgets/ScheduleWidget.tsx new file mode 100644 index 0000000..2c9decd --- /dev/null +++ b/src/app/main/dashboard/project/tabs/home/widgets/ScheduleWidget.tsx @@ -0,0 +1,129 @@ +import IconButton from '@mui/material/IconButton'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; +import ListItemText from '@mui/material/ListItemText'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; +import { memo, useState } from 'react'; +import Tabs from '@mui/material/Tabs'; +import Box from '@mui/material/Box'; +import Tab from '@mui/material/Tab'; +import FuseSvgIcon from '@fuse/core/FuseSvgIcon'; +import { useSelector } from 'react-redux'; +import { selectWidget } from '../../../ProjectDashboardApi'; +import ScheduleDataType from './types/ScheduleDataType'; + +/** + * The ScheduleWidget widget. + */ +function ScheduleWidget() { + const widget = useSelector(selectWidget('schedule')); + + if (!widget) { + return null; + } + + const { series, ranges } = widget; + const [tabValue, setTabValue] = useState(0); + const currentRange = Object.keys(ranges)[tabValue]; + + return ( + +
+ Schedule +
+ setTabValue(value)} + indicatorColor="secondary" + textColor="inherit" + variant="scrollable" + scrollButtons={false} + className="-mx-16 min-h-40" + classes={{ indicator: 'flex justify-center bg-transparent w-full h-full' }} + TabIndicatorProps={{ + children: ( + + ) + }} + > + {Object.entries(ranges).map(([key, label]) => ( + + ))} + +
+
+ + {series[currentRange].map((item, index) => ( + + + {item.time && ( + + + heroicons-solid:clock + + + {item.time} + + + )} + + {item.location && ( + + + heroicons-solid:location-marker + + + {item.location} + + + )} + + } + /> + + + heroicons-solid:chevron-right + + + + ))} + +
+ ); +} + +export default memo(ScheduleWidget); diff --git a/src/app/main/dashboard/project/tabs/home/widgets/SummaryWidget.tsx b/src/app/main/dashboard/project/tabs/home/widgets/SummaryWidget.tsx new file mode 100644 index 0000000..7aa160b --- /dev/null +++ b/src/app/main/dashboard/project/tabs/home/widgets/SummaryWidget.tsx @@ -0,0 +1,79 @@ +import IconButton from '@mui/material/IconButton'; +import Paper from '@mui/material/Paper'; +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import Typography from '@mui/material/Typography'; +import { memo, useState } from 'react'; +import MenuItem from '@mui/material/MenuItem'; +import FuseSvgIcon from '@fuse/core/FuseSvgIcon'; +import { useSelector } from 'react-redux'; +import { selectWidget } from '../../../ProjectDashboardApi'; +import WidgetDataType, { RangeType } from './types/WidgetDataType'; + +/** + * The SummaryWidget widget. + */ +function SummaryWidget() { + const widget = useSelector(selectWidget('summary')); + + if (!widget) { + return null; + } + + const { data, ranges, currentRange: currentRangeDefault } = widget; + + const [currentRange, setCurrentRange] = useState(currentRangeDefault as RangeType); + + function handleChangeRange(event: SelectChangeEvent) { + setCurrentRange(event.target.value as RangeType); + } + + return ( + +
+ + + heroicons-outline:dots-vertical + +
+
+ + {data.count[currentRange]} + + {data.name} +
+ + {data.extra.name}: + {data.extra.count[currentRange]} + +
+ ); +} + +export default memo(SummaryWidget); diff --git a/src/app/main/dashboard/project/tabs/home/widgets/TaskDistributionWidget.tsx b/src/app/main/dashboard/project/tabs/home/widgets/TaskDistributionWidget.tsx new file mode 100644 index 0000000..d251b23 --- /dev/null +++ b/src/app/main/dashboard/project/tabs/home/widgets/TaskDistributionWidget.tsx @@ -0,0 +1,167 @@ +import Paper from '@mui/material/Paper'; +import { lighten, useTheme } from '@mui/material/styles'; +import Typography from '@mui/material/Typography'; +import { memo, useEffect, useState } from 'react'; +import ReactApexChart from 'react-apexcharts'; +import Tabs from '@mui/material/Tabs'; +import Box from '@mui/material/Box'; +import Tab from '@mui/material/Tab'; +import { ApexOptions } from 'apexcharts'; +import { useSelector } from 'react-redux'; +import { selectWidget } from '../../../ProjectDashboardApi'; +import TaskDistributionDataType from './types/TaskDistributionDataType'; + +/** + * The TaskDistributionWidget widget. + */ +function TaskDistributionWidget() { + const widget = useSelector(selectWidget('taskDistribution')); + + if (!widget) { + return null; + } + + const { overview, series, labels, ranges } = widget; + const [tabValue, setTabValue] = useState(0); + const currentRange = Object.keys(ranges)[tabValue]; + const [awaitRender, setAwaitRender] = useState(true); + + const theme = useTheme(); + + const chartOptions: ApexOptions = { + chart: { + fontFamily: 'inherit', + foreColor: 'inherit', + height: '100%', + type: 'polarArea', + toolbar: { + show: false + }, + zoom: { + enabled: false + } + }, + labels, + legend: { + position: 'bottom' + }, + plotOptions: { + polarArea: { + spokes: { + connectorColors: theme.palette.divider + }, + rings: { + strokeColor: theme.palette.divider + } + } + }, + states: { + hover: { + filter: { + type: 'darken', + value: 0.75 + } + } + }, + stroke: { + width: 2 + }, + theme: { + monochrome: { + enabled: true, + color: theme.palette.secondary.main, + shadeIntensity: 0.75, + shadeTo: 'dark' + } + }, + tooltip: { + followCursor: true, + theme: 'dark' + }, + yaxis: { + labels: { + style: { + colors: theme.palette.text.secondary + } + } + } + }; + + useEffect(() => { + setAwaitRender(false); + }, []); + + if (awaitRender) { + return null; + } + return ( + +
+ + Task Distribution + +
+ setTabValue(value)} + indicatorColor="secondary" + textColor="inherit" + variant="scrollable" + scrollButtons={false} + className="-mx-4 min-h-40" + classes={{ indicator: 'flex justify-center bg-transparent w-full h-full' }} + TabIndicatorProps={{ + children: ( + + ) + }} + > + {Object.entries(ranges).map(([key, label]) => ( + + ))} + +
+
+
+ +
+ + _theme.palette.mode === 'light' + ? lighten(theme.palette.background.default, 0.4) + : lighten(theme.palette.background.default, 0.02) + }} + className="grid grid-cols-2 border-t divide-x -m-24 mt-16" + > +
+
+ {overview[currentRange].new} +
+ New tasks +
+
+
+ {overview[currentRange].completed} +
+ Completed tasks +
+
+
+ ); +} + +export default memo(TaskDistributionWidget); diff --git a/src/app/main/dashboard/project/tabs/home/widgets/types/GithubIssuesDataType.ts b/src/app/main/dashboard/project/tabs/home/widgets/types/GithubIssuesDataType.ts new file mode 100644 index 0000000..50038d3 --- /dev/null +++ b/src/app/main/dashboard/project/tabs/home/widgets/types/GithubIssuesDataType.ts @@ -0,0 +1,21 @@ +type GithubIssueOverviewData = { + [key: string]: number; +}; + +type GithubIssueSeriesData = { + name: string; + type: string; + data: number[]; +}; + +/** + * The type definition for the data used to populate the github issues chart. + */ +type GithubIssuesDataType = { + overview: Record; + ranges: Record; + labels: string[]; + series: Record; +}; + +export default GithubIssuesDataType; diff --git a/src/app/main/dashboard/project/tabs/home/widgets/types/ScheduleDataType.ts b/src/app/main/dashboard/project/tabs/home/widgets/types/ScheduleDataType.ts new file mode 100644 index 0000000..8c0d58c --- /dev/null +++ b/src/app/main/dashboard/project/tabs/home/widgets/types/ScheduleDataType.ts @@ -0,0 +1,15 @@ +type ScheduleItem = { + title: string; + time: string; + location?: string; +}; + +/** + * The type definition for the data used to populate the schedule. + */ +type ScheduleDataType = { + ranges: Record; + series: Record; +}; + +export default ScheduleDataType; diff --git a/src/app/main/dashboard/project/tabs/home/widgets/types/TaskDistributionDataType.ts b/src/app/main/dashboard/project/tabs/home/widgets/types/TaskDistributionDataType.ts new file mode 100644 index 0000000..ceeaf3e --- /dev/null +++ b/src/app/main/dashboard/project/tabs/home/widgets/types/TaskDistributionDataType.ts @@ -0,0 +1,19 @@ +/** + * The type definition for the data used to populate the task distribution chart. + */ +type TaskDistributionOverviewData = { + new: number; + completed: number; +}; + +/** + * The type definition for the data used to populate the task distribution chart. + */ +type TaskDistributionDataType = { + ranges: Record; + overview: Record; + labels: string[]; + series: Record; +}; + +export default TaskDistributionDataType; diff --git a/src/app/main/dashboard/project/tabs/home/widgets/types/WidgetDataType.ts b/src/app/main/dashboard/project/tabs/home/widgets/types/WidgetDataType.ts new file mode 100644 index 0000000..170900a --- /dev/null +++ b/src/app/main/dashboard/project/tabs/home/widgets/types/WidgetDataType.ts @@ -0,0 +1,25 @@ +type ExtraData = { + name: string; + count: Record; +}; + +type WidgetInnerData = { + name: string; + count: Record; + extra: ExtraData; +}; + +/** + * The type definition for the data used to populate the widget. + */ +type WidgetDataType = { + title?: string; + ranges: Record; + currentRange?: string; + data: WidgetInnerData; + detail?: string; +}; + +export type RangeType = 'DY' | 'DT' | 'DTM'; + +export default WidgetDataType; diff --git a/src/app/main/dashboard/project/tabs/team/TeamTab.tsx b/src/app/main/dashboard/project/tabs/team/TeamTab.tsx new file mode 100644 index 0000000..9c04a88 --- /dev/null +++ b/src/app/main/dashboard/project/tabs/team/TeamTab.tsx @@ -0,0 +1,38 @@ +import { motion } from 'framer-motion'; +import TeamMembersWidget from './widgets/TeamMembersWidget'; + +/** + * The TeamTab component. + */ +function TeamTab() { + const container = { + show: { + transition: { + staggerChildren: 0.04 + } + } + }; + + const item = { + hidden: { opacity: 0, y: 20 }, + show: { opacity: 1, y: 0 } + }; + + return ( + + + + + + ); +} + +export default TeamTab; diff --git a/src/app/main/dashboard/project/tabs/team/widgets/TeamMembersWidget.tsx b/src/app/main/dashboard/project/tabs/team/widgets/TeamMembersWidget.tsx new file mode 100644 index 0000000..7dcddd1 --- /dev/null +++ b/src/app/main/dashboard/project/tabs/team/widgets/TeamMembersWidget.tsx @@ -0,0 +1,92 @@ +import Typography from '@mui/material/Typography'; +import { memo } from 'react'; +import Paper from '@mui/material/Paper'; +import { motion } from 'framer-motion'; +import FuseSvgIcon from '@fuse/core/FuseSvgIcon'; +import { useSelector } from 'react-redux'; +import { selectWidget } from '../../../ProjectDashboardApi'; +import TeamMemberType from './types/TeamMemberType'; + +/** + * The TeamMembersWidget widget. + */ +function TeamMembersWidget() { + const members = useSelector(selectWidget('teamMembers')); + + if (!members) { + return null; + } + + const container = { + show: { + transition: { + staggerChildren: 0.04 + } + } + }; + + const item = { + hidden: { opacity: 0, y: 20 }, + show: { opacity: 1, y: 0 } + }; + + return ( + + {members.map((member) => ( + +
+
+ member +
+ {member.name} + {member.title} +
+ +
+ ))} +
+ ); +} + +export default memo(TeamMembersWidget); diff --git a/src/app/main/dashboard/project/tabs/team/widgets/types/TeamMemberType.ts b/src/app/main/dashboard/project/tabs/team/widgets/types/TeamMemberType.ts new file mode 100644 index 0000000..0f73282 --- /dev/null +++ b/src/app/main/dashboard/project/tabs/team/widgets/types/TeamMemberType.ts @@ -0,0 +1,13 @@ +/** + * Team Member Type + */ +type TeamMemberType = { + id: string; + avatar: string; + name: string; + email: string; + phone: string; + title: string; +}; + +export default TeamMemberType; diff --git a/yarn.lock b/yarn.lock index 393f868..b530f32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3288,6 +3288,11 @@ "@types/babel__core" "^7.20.5" react-refresh "^0.14.0" +"@yr/monotone-cubic-spline@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz#7272d89f8e4f6fb7a1600c28c378cc18d3b577b9" + integrity sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA== + acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -3395,6 +3400,19 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +apexcharts@^3.45.2: + version "3.45.2" + resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.45.2.tgz#18a6343da122c12d71ac9f35260aacd31b16fbfb" + integrity sha512-PpuM4sJWy70sUh5U1IFn1m1p45MdHSChLUNnqEoUUUHSU2IHZugFrsVNhov1S8Q0cvfdrCRCvdBtHGSs6PSAWQ== + dependencies: + "@yr/monotone-cubic-spline" "^1.0.3" + svg.draggable.js "^2.2.2" + svg.easing.js "^2.0.0" + svg.filter.js "^2.0.2" + svg.pathmorphing.js "^0.1.3" + svg.resize.js "^1.4.3" + svg.select.js "^3.0.1" + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -7323,6 +7341,13 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +react-apexcharts@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/react-apexcharts/-/react-apexcharts-1.4.1.tgz#95ab31e4d2201308f59f3d2a4b65d10d9d0ea4bb" + integrity sha512-G14nVaD64Bnbgy8tYxkjuXEUp/7h30Q0U33xc3AwtGFijJB9nHqOt1a6eG0WBn055RgRg+NwqbKGtqPxy15d0Q== + dependencies: + prop-types "^15.8.1" + react-app-alias@2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/react-app-alias/-/react-app-alias-2.2.2.tgz#2b486cd21cdba362df9ef71a06ab73e0a8ea660d" @@ -8274,6 +8299,61 @@ svg-parser@^2.0.4: resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== +svg.draggable.js@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz#c514a2f1405efb6f0263e7958f5b68fce50603ba" + integrity sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw== + dependencies: + svg.js "^2.0.1" + +svg.easing.js@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/svg.easing.js/-/svg.easing.js-2.0.0.tgz#8aa9946b0a8e27857a5c40a10eba4091e5691f12" + integrity sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA== + dependencies: + svg.js ">=2.3.x" + +svg.filter.js@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/svg.filter.js/-/svg.filter.js-2.0.2.tgz#91008e151389dd9230779fcbe6e2c9a362d1c203" + integrity sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw== + dependencies: + svg.js "^2.2.5" + +svg.js@>=2.3.x, svg.js@^2.0.1, svg.js@^2.2.5, svg.js@^2.4.0, svg.js@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.7.1.tgz#eb977ed4737001eab859949b4a398ee1bb79948d" + integrity sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA== + +svg.pathmorphing.js@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz#c25718a1cc7c36e852ecabc380e758ac09bb2b65" + integrity sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww== + dependencies: + svg.js "^2.4.0" + +svg.resize.js@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/svg.resize.js/-/svg.resize.js-1.4.3.tgz#885abd248e0cd205b36b973c4b578b9a36f23332" + integrity sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw== + dependencies: + svg.js "^2.6.5" + svg.select.js "^2.1.2" + +svg.select.js@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-2.1.2.tgz#e41ce13b1acff43a7441f9f8be87a2319c87be73" + integrity sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ== + dependencies: + svg.js "^2.2.5" + +svg.select.js@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-3.0.1.tgz#a4198e359f3825739226415f82176a90ea5cc917" + integrity sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw== + dependencies: + svg.js "^2.6.5" + swagger2openapi@^7.0.4, swagger2openapi@^7.0.8: version "7.0.8" resolved "https://registry.yarnpkg.com/swagger2openapi/-/swagger2openapi-7.0.8.tgz#12c88d5de776cb1cbba758994930f40ad0afac59"