import Icon, { SyncOutlined } from "@ant-design/icons"; import { gql, useMutation, useQuery } from "@apollo/client"; import { Button, Dropdown, Menu, notification, PageHeader, Space } from "antd"; import i18next from "i18next"; import _ from "lodash"; import moment from "moment"; import React, { useState } from "react"; import { Responsive, WidthProvider } from "react-grid-layout"; import { useTranslation } from "react-i18next"; import { MdClose } from "react-icons/md"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { UPDATE_DASHBOARD_LAYOUT } from "../../graphql/user.queries"; import { selectBodyshop, selectCurrentUser, } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import DashboardMonthlyEmployeeEfficiency, { DashboardMonthlyEmployeeEfficiencyGql, } from "../dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component"; import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component"; import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component"; import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component"; import DashboardMonthlyRevenueGraph, { DashboardMonthlyRevenueGraphGql, } from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component"; import DashboardProjectedMonthlySales, { DashboardProjectedMonthlySalesGql, } from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component"; import DashboardTotalProductionDollars from "../dashboard-components/total-production-dollars/total-production-dollars.component"; import DashboardTotalProductionHours, { DashboardTotalProductionHoursGql, } from "../dashboard-components/total-production-hours/total-production-hours.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; //Combination of the following: // /node_modules/react-grid-layout/css/styles.css // /node_modules/react-resizable/css/styles.css import "./dashboard-grid.styles.scss"; import { GenerateDashboardData } from "./dashboard-grid.utils"; const ResponsiveReactGridLayout = WidthProvider(Responsive); const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, bodyshop: selectBodyshop, }); const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export function DashboardGridComponent({ currentUser, bodyshop }) { const { t } = useTranslation(); const [state, setState] = useState({ ...(bodyshop.associations[0].user.dashboardlayout ? bodyshop.associations[0].user.dashboardlayout : { items: [], layout: {}, layouts: [] }), }); const { loading, error, data, refetch } = useQuery( createDashboardQuery(state), { fetchPolicy: "network-only", nextFetchPolicy: "network-only" } ); const [updateLayout] = useMutation(UPDATE_DASHBOARD_LAYOUT); const handleLayoutChange = async (layout, layouts) => { logImEXEvent("dashboard_change_layout"); setState({ ...state, layout, layouts }); const result = await updateLayout({ variables: { email: currentUser.email, layout: { ...state, layout, layouts }, }, }); if (!!result.errors) { notification["error"]({ message: t("dashboard.errors.updatinglayout", { message: JSON.stringify(result.errors), }), }); } }; const handleRemoveComponent = (key) => { logImEXEvent("dashboard_remove_component", { name: key }); const idxToRemove = state.items.findIndex((i) => i.i === key); const items = _.cloneDeep(state.items); items.splice(idxToRemove, 1); setState({ ...state, items }); }; const handleAddComponent = (e) => { logImEXEvent("dashboard_add_component", { name: e }); setState({ ...state, items: [ ...state.items, { i: e.key, x: (state.items.length * 2) % (state.cols || 12), y: 99, // puts it at the bottom w: componentList[e.key].w || 2, h: componentList[e.key].h || 2, }, ], }); }; const dashboarddata = React.useMemo( () => GenerateDashboardData(data), [data] ); const existingLayoutKeys = state.items.map((i) => i.i); const addComponentOverlay = ( {Object.keys(componentList).map((key) => ( {componentList[key].label} ))} ); if (error) return ; return (
} /> {state.items.map((item, index) => { const TheComponent = componentList[item.i].component; return (
handleRemoveComponent(item.i)} />
); })}
); } export default connect( mapStateToProps, mapDispatchToProps )(DashboardGridComponent); const componentList = { ProductionDollars: { label: i18next.t("dashboard.titles.productiondollars"), component: DashboardTotalProductionDollars, gqlFragment: null, w: 1, h: 1, minW: 2, minH: 1, }, ProductionHours: { label: i18next.t("dashboard.titles.productionhours"), component: DashboardTotalProductionHours, gqlFragment: DashboardTotalProductionHoursGql, w: 3, h: 1, minW: 3, minH: 1, }, ProjectedMonthlySales: { label: i18next.t("dashboard.titles.projectedmonthlysales"), component: DashboardProjectedMonthlySales, gqlFragment: DashboardProjectedMonthlySalesGql, w: 2, h: 1, minW: 2, minH: 1, }, MonthlyRevenueGraph: { label: i18next.t("dashboard.titles.monthlyrevenuegraph"), component: DashboardMonthlyRevenueGraph, gqlFragment: DashboardMonthlyRevenueGraphGql, w: 4, h: 2, minW: 4, minH: 2, }, MonthlyJobCosting: { label: i18next.t("dashboard.titles.monthlyjobcosting"), component: DashboardMonthlyJobCosting, gqlFragment: null, minW: 6, minH: 3, w: 6, h: 3, }, MonthlyPartsSales: { label: i18next.t("dashboard.titles.monthlypartssales"), component: DashboardMonthlyPartsSales, gqlFragment: null, minW: 2, minH: 2, w: 2, h: 2, }, MonthlyLaborSales: { label: i18next.t("dashboard.titles.monthlylaborsales"), component: DashboardMonthlyLaborSales, gqlFragment: null, minW: 2, minH: 2, w: 2, h: 2, }, MonthlyEmployeeEfficency: { label: i18next.t("dashboard.titles.monthlyemployeeefficiency"), component: DashboardMonthlyEmployeeEfficiency, gqlFragment: DashboardMonthlyEmployeeEfficiencyGql, minW: 2, minH: 2, w: 2, h: 2, }, }; const createDashboardQuery = (state) => { const componentBasedAdditions = state && Array.isArray(state.layout) && state.layout .map((item, index) => componentList[item.i].gqlFragment || "") .join(""); return gql` query QUERY_DASHBOARD_DETAILS { ${componentBasedAdditions || ""} monthly_sales: jobs(where: {_and: [ { voided: {_eq: false}}, {date_invoiced: {_gte: "${moment() .startOf("month").startOf('day').toISOString()}"}}, {date_invoiced: {_lte: "${moment() .endOf("month").endOf('day').toISOString()}"}}]}) { id ro_number date_invoiced job_totals rate_la1 rate_la2 rate_la3 rate_la4 rate_laa rate_lab rate_lad rate_lae rate_laf rate_lag rate_lam rate_lar rate_las rate_lau rate_ma2s rate_ma2t rate_ma3s rate_mabl rate_macs rate_mahw rate_mapa rate_mash rate_matd joblines(where: { removed: { _eq: false } }) { id mod_lbr_ty mod_lb_hrs act_price part_qty part_type } } production_jobs: jobs(where: { inproduction: { _eq: true } }) { id ro_number ins_co_nm job_totals joblines(where: { removed: { _eq: false } }) { id mod_lbr_ty mod_lb_hrs act_price part_qty part_type } labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) { aggregate { sum { mod_lb_hrs } } } larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) { aggregate { sum { mod_lb_hrs } } } } } `; };