.js\r\n\r\n const defaultDateLabel = useMemo(() => currentWidgetGroup && currentWidgetGroup.DefaultDateRange ? currentWidgetGroup.DefaultDateRange : 'Last Year', [currentWidgetGroup])\r\n\r\n useEffect(() => {\r\n setPageLoading(true)\r\n authenticatedFetch(`${API_URL}/dashboard/${dashboardName}`)\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return response.json()\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .then((response) => {\r\n const data = response.data\r\n setCurrentDashboard(data)\r\n const { Pages: widgetGroups } = data\r\n setWidgetGroups(widgetGroups)\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Widget Groups: ' +\r\n (e.message\r\n ? e.message\r\n : 'Unable to connect to the server. Please try again later.'),\r\n })\r\n })\r\n .finally(() => setPageLoading(false))\r\n }, [API_URL, dashboardName, newObject])\r\n\r\n\r\n\r\n useEffect(() => {\r\n if (widgetGroups.length) {\r\n if (pageId) {\r\n const matchingWidgetGroup = widgetGroups.find(widgetGroup => widgetGroup.PageId === Number(pageId))\r\n setCurrentWidgetGroup(matchingWidgetGroup)\r\n } else {\r\n const location = currentPage ? `/${currentPage}` : widgetGroups[0].Location\r\n const matchingWidgetGroup = widgetGroups.find(\r\n (widgetGroup) => widgetGroup.Location === location\r\n )\r\n setCurrentWidgetGroup(matchingWidgetGroup)\r\n }\r\n }\r\n }, [location, widgetGroups, currentPage, detailKey, pageId])\r\n\r\n \r\n // update shared key (if exists) and \r\n // set shared page id to null on\r\n // when the currentWidgetGroup changes\r\n useEffect(() => {\r\n const updateLayout = async () => {\r\n setLayoutEditingState(false)\r\n if (currentWidgetGroup) {\r\n setSharedPageKey(currentWidgetGroup.SharedKey)\r\n setSharedPageId(null)\r\n }\r\n if(currentWidgetGroup && currentWidgetGroup.Widgets){\r\n const dbLayout = getLayouts(currentWidgetGroup.Widgets)\r\n const userLayout = await getLayout(currentWidgetGroup.PageId)\r\n if (userLayout) {\r\n setLayoutState(userLayout)\r\n } else {\r\n setLayoutState(dbLayout)\r\n }\r\n }\r\n }\r\n updateLayout()\r\n }, [currentWidgetGroup])\r\n\r\n useEffect(() => {\r\n if(currentDashboard && !currentDashboard.DashboardId){\r\n throw new Error(\"Dashboard Not Found\")\r\n }\r\n }, [currentDashboard, location])\r\n\r\n const [ initialLayoutState, setInitialLayoutState ] = useState(layoutState)\r\n\r\n const startLayoutEdit = useCallback(() => {\r\n setInitialLayoutState(layoutState)\r\n setLayoutEditingState(true)\r\n }, [setLayoutEditingState, setInitialLayoutState, layoutState])\r\n\r\n const saveLayoutState = useCallback(() => {\r\n const { PageId } = currentWidgetGroup\r\n setLayoutEditingState(false)\r\n saveLayout(PageId, layoutState)\r\n }, [currentWidgetGroup, layoutState])\r\n\r\n const cancelLayoutEdit = useCallback(() => {\r\n setLayoutEditingState(false)\r\n setLayoutState(initialLayoutState)\r\n }, [initialLayoutState, setLayoutState, setLayoutEditingState])\r\n\r\n const resetLayoutState = useCallback(() => {\r\n const { PageId, Widgets } = currentWidgetGroup\r\n const dbLayout = getLayouts(Widgets)\r\n clearLayout(PageId)\r\n setLayoutState(dbLayout)\r\n setLayoutEditingState(false)\r\n }, [currentWidgetGroup])\r\n \r\n return (\r\n \r\n {children}\r\n \r\n )\r\n}\r\n\r\nexport { CurrentDashboardContext }\r\nexport default withConfig(CurrentDashboardContextProvider)\r\n","import dayjs from 'dayjs'\r\n\r\nconst format = \"MM/DD/YYYY\"\r\nconst withMinuteFormat = \"MM/DD/YYYY HH:mm:ss\"\r\n\r\nconst dateToString = date => date ? dayjs(date).format(format).toString() : null\r\nconst stringToDate = date => date ? dayjs(date).toDate() : null\r\n\r\nconst constructDateQueryString = (minDate, maxDate) => {\r\n let dateFrom = null\r\n let dateTo = null\r\n\r\n // if both date components are null, return\r\n // an empty string\r\n if (!minDate && !maxDate) {\r\n return \"\"\r\n }\r\n\r\n // if there is a minDate and no maxDate,\r\n // just send dateFrom = minDate\r\n if (minDate && !maxDate) {\r\n dateFrom = minDate\r\n }\r\n\r\n // if there is a maxDate and no minDate,\r\n // just send dateTo = maxDate\r\n if (maxDate && !minDate) {\r\n dateTo = maxDate\r\n }\r\n\r\n if (minDate && maxDate) {\r\n dateFrom = new Date(minDate) < new Date(maxDate) ? minDate : maxDate\r\n dateTo = new Date(minDate) < new Date(maxDate) ? maxDate : minDate\r\n }\r\n\r\n // make the time to the end of the day on dateto\r\n dateTo = dayjs(maxDate).add(1, 'day').subtract(1, 'second').format(withMinuteFormat).toString()\r\n\r\n const dateFromQuery = dateFrom ? `dateFrom=${encodeURIComponent(dateFrom)}` : ''\r\n const dateToQuery = dateTo ? `dateTo=${encodeURIComponent(dateTo)}` : ''\r\n\r\n return [dateFromQuery, dateToQuery].join('&')\r\n} \r\n\r\nexport { dateToString, stringToDate, constructDateQueryString }","import dayjs from 'dayjs'\r\nimport quarterOfYear from 'dayjs/plugin/quarterOfYear'\r\nimport { dateToString } from '../../dateFormatting'\r\ndayjs.extend(quarterOfYear)\r\n\r\nconst currentDate = dateToString(dayjs().toDate())\r\nconst lastYearEnd = dateToString(dayjs().startOf('year').subtract(1, 'day').toDate())\r\nconst lastYearStart = dateToString(dayjs().startOf('year').subtract(1, 'year').toDate())\r\nconst twoYearsAgoStart = dateToString(dayjs().startOf('year').subtract(2, 'year').toDate())\r\nconst fiveYearsAgoStart = dateToString(dayjs().startOf('year').subtract(5, 'year').toDate())\r\nconst tenYearsAgoStart = dateToString(dayjs().startOf('year').subtract(10, 'year').toDate())\r\nconst currentYearStart = dateToString(dayjs().startOf('year').toDate())\r\nconst quarterStart = dateToString(dayjs().startOf('quarter').toDate())\r\nconst lastQuarterStart = dateToString(dayjs().subtract(1, 'quarter').startOf('quarter').toDate())\r\nconst lastQuarterEnd = dateToString(dayjs().subtract(1, 'quarter').endOf('quarter').toDate())\r\nconst thirtyDaysAgo = dateToString(dayjs().subtract(30, 'day').toDate())\r\nconst ninetyDaysAgo = dateToString(dayjs().subtract(90, 'day').toDate())\r\nconst oneYearAgo = dateToString(dayjs().subtract(365, 'day').toDate())\r\n\r\n\r\nconst dateOptionsColumnOne = [\r\n {\r\n minDate: lastYearStart,\r\n maxDate: lastYearEnd,\r\n label: `Last Year`,\r\n dateRangeString: `(${lastYearStart} - ${lastYearEnd})`,\r\n },\r\n {\r\n minDate: twoYearsAgoStart,\r\n maxDate: lastYearEnd,\r\n label: `Last 2 Years`,\r\n dateRangeString: `(${twoYearsAgoStart} - ${lastYearEnd})`,\r\n },\r\n {\r\n minDate: fiveYearsAgoStart,\r\n maxDate: lastYearEnd,\r\n label: `Last 5 Years`,\r\n dateRangeString: `(${fiveYearsAgoStart} - ${lastYearEnd})`,\r\n },\r\n {\r\n minDate: tenYearsAgoStart,\r\n maxDate: lastYearEnd,\r\n label: `Last 10 Years`,\r\n dateRangeString: `(${tenYearsAgoStart} - ${lastYearEnd})`,\r\n },\r\n {\r\n minDate: currentYearStart,\r\n maxDate: currentDate,\r\n label: `Year to Date`,\r\n dateRangeString: `(${currentYearStart} - ${currentDate})`,\r\n },\r\n {\r\n minDate: null,\r\n maxDate: currentDate,\r\n label: `All Time`,\r\n },\r\n]\r\n\r\nconst dateOptionsColumnTwo = [\r\n {\r\n minDate: quarterStart,\r\n maxDate: currentDate,\r\n label: `This Quarter`,\r\n dateRangeString: `(${quarterStart} - ${currentDate})`,\r\n },\r\n {\r\n minDate: lastQuarterStart,\r\n maxDate: lastQuarterEnd,\r\n label: `Last Quarter`,\r\n dateRangeString: `(${lastQuarterStart} - ${lastQuarterEnd})`,\r\n },\r\n {\r\n minDate: thirtyDaysAgo,\r\n maxDate: currentDate,\r\n label: `Last 30 Days`,\r\n dateRangeString: `(${thirtyDaysAgo} - ${currentDate})`,\r\n },\r\n {\r\n minDate: ninetyDaysAgo,\r\n maxDate: currentDate,\r\n label: `Last 90 Days`,\r\n dateRangeString: `(${ninetyDaysAgo} - ${currentDate})`,\r\n },\r\n {\r\n minDate: oneYearAgo,\r\n maxDate: currentDate,\r\n label: `Last 365 Days`,\r\n dateRangeString: `(${oneYearAgo} - ${currentDate})`,\r\n },\r\n]\r\n\r\nconst allDateOptions = [...dateOptionsColumnOne, ...dateOptionsColumnTwo]\r\n\r\nexport { allDateOptions, dateOptionsColumnOne, dateOptionsColumnTwo }","import React, { createContext, useState, useEffect, useContext, useMemo } from 'react'\r\n\r\nimport {\r\n constructDateQueryString,\r\n} from '../../utils/dateFormatting'\r\nimport { CurrentDashboardContext } from './CurrentDashboardContext'\r\nimport { allDateOptions } from '../../utils/constants/date/dateOptions'\r\n\r\nconst DateContext = createContext(null)\r\n\r\nconst DateContextProvider = ({ children }) => {\r\n const { defaultDateLabel } = useContext(CurrentDashboardContext) \r\n const [selectorVisible, toggleSelectorVisible] = useState(false)\r\n const defaultDate = useMemo(() => allDateOptions.find(x => x.label === defaultDateLabel), [defaultDateLabel])\r\n const [date, setDate] = useState({\r\n minDate: defaultDate.minDate,\r\n maxDate: defaultDate.maxDate\r\n })\r\n\r\n const [dateQueryString, setDateQueryString] = useState(\r\n constructDateQueryString(date.minDate, date.maxDate)\r\n )\r\n\r\n const [dateRangeLabel, setDateRangeLabel] = useState(defaultDateLabel) // default date range is last year\r\n\r\n // update the date + date range on updates to defaultDateLabel (coincides with page changes to non-null default date range values)\r\n useEffect(() => {\r\n setDate(defaultDate)\r\n setDateQueryString(constructDateQueryString(defaultDate.minDate, defaultDate.maxDate))\r\n }, [defaultDate])\r\n useEffect(() => setDateRangeLabel(defaultDateLabel), [defaultDateLabel])\r\n \r\n // on date changes, construct the date\r\n // query parameter clause that gets appended to\r\n // widget fetch urls\r\n useEffect(() => {\r\n const newDateString = constructDateQueryString(\r\n date.minDate,\r\n date.maxDate\r\n )\r\n setDateQueryString(newDateString)\r\n }, [date])\r\n\r\n return (\r\n \r\n {children}\r\n \r\n )\r\n}\r\n\r\nexport { DateContext }\r\nexport default DateContextProvider\r\n","import React, { createContext, useState, useEffect, useContext } from 'react'\r\nimport { useLocation } from 'react-router-dom'\r\nimport withConfig from './withConfig'\r\nimport toast from '../elem/Toast'\r\nimport { APIRequestContext } from './APIRequestContext'\r\n\r\nconst DashboardContext = createContext(null)\r\n\r\nconst DashboardContextProvider = ({ config, children }) => {\r\n const { API_URL } = config\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const [dashboards, setDashboards] = useState([])\r\n const location = useLocation()\r\n const [loading, setLoading] = useState(true)\r\n\r\n useEffect(() => {\r\n setLoading(true) // set the loading to true\r\n authenticatedFetch(`${API_URL}/dashboard`)\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return response.json()\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .then((response) => {\r\n setDashboards(response.dashboards)\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Dashboard Landing Page:' +\r\n (e.message\r\n ? e.message\r\n : 'Unable to connect to the server'),\r\n })\r\n })\r\n .finally(() => {\r\n setLoading(false)\r\n })\r\n }, [API_URL, location])\r\n\r\n return (\r\n \r\n {children}\r\n \r\n )\r\n}\r\n\r\nexport { DashboardContext }\r\nexport default withConfig(DashboardContextProvider)\r\n","import React from 'react'\r\nimport { IconContext } from 'react-icons'\r\nimport { FaSpinner } from 'react-icons/fa'\r\n\r\nconst Spinner = ({ size }) => {\r\n return (\r\n \r\n \r\n \r\n \r\n
\r\n )\r\n}\r\nconst LoadingSpinner = () => \r\n\r\nexport default LoadingSpinner\r\n\r\nexport { Spinner }\r\n","import React from 'react'\r\nimport { Link } from 'react-router-dom'\r\n\r\nexport default ({dashboard}) => (\r\n \r\n
\r\n
\r\n
\r\n {dashboard.DashboardName} \r\n
\r\n
\r\n {dashboard.DashboardDescription}\r\n
\r\n
\r\n \r\n
\r\n)","const submitterRegex = new RegExp('Submitter_([A-Za-z]+)')\r\n\r\nconst getRoles = user => {\r\n if (user && user.profile && user.profile.role) {\r\n const roles = user.profile.role\r\n if (Array.isArray(roles)) {\r\n return roles.join(',')\r\n } else {\r\n return roles\r\n }\r\n } else {\r\n return ''\r\n }\r\n}\r\n\r\nconst checkRole = (role, roles) => {\r\n if (roles && role) {\r\n return roles.includes(role)\r\n } else {\r\n return false\r\n }\r\n}\r\n\r\nconst getAgencyCode = (roles) => {\r\n if (roles && isSubmissionRole(roles)) {\r\n const submitterRole = roles.match(submitterRegex)\r\n if (submitterRole && submitterRole.length === 2) {\r\n return submitterRole[1]\r\n }\r\n return submitterRole\r\n } else {\r\n return null\r\n }\r\n}\r\n\r\nconst isSubmissionRole = roles => {\r\n if (roles) {\r\n return !!roles.includes('Submitter')\r\n }\r\n return false\r\n}\r\n\r\nconst isReviewerRole = roles => {\r\n if (roles) {\r\n return !!roles.includes('Form_Reviewer')\r\n }\r\n return false\r\n}\r\n\r\nconst hasPrintAccess = roles => {\r\n if (roles) {\r\n return !!roles.includes('Print')\r\n }\r\n return false\r\n}\r\n\r\n\r\nconst hasAccessToManageSampleResults = roles =>\r\n checkRole('Industry', roles) ||\r\n checkRole('DataEntry', roles) ||\r\n checkRole('Form_Reviewer', roles) ||\r\n isSubmissionRole(roles)\r\n\r\nconst hasAccessToAdmin = roles => checkRole('Administrator', roles)\r\n\r\nconst hasAccessToAgency = roles => checkRole('Agency', roles)\r\n\r\nconst hasAccessToPWS = roles => checkRole(\"PWSAccess\", roles)\r\n\r\nexport {\r\n getRoles,\r\n checkRole,\r\n hasAccessToManageSampleResults,\r\n hasAccessToAdmin,\r\n hasAccessToAgency,\r\n getAgencyCode,\r\n isReviewerRole,\r\n hasAccessToPWS,\r\n hasPrintAccess\r\n}\r\n","import React, { useEffect, createContext, useState, useCallback } from 'react'\r\nimport { AuthProvider, useAuth, UserManager } from 'oidc-react'\r\n\r\nimport { getRoles } from '../../utils/user/permissions'\r\nimport withConfig from '../wrappers/withConfig'\r\nimport dayjs from 'dayjs'\r\n// import settings from '../../utils/constants/userSettings'\r\n\r\nconst UserContext = createContext(null)\r\n\r\nconst AppUserContextProvider = withConfig(({ loadingUser, children }) => {\r\n // const { userManager: client, userData: user } = useAuth()\r\n const { userManager: client } = useAuth()\r\n const [roles, setRoles] = useState(\"\")\r\n const [ user, setUser ] = useState(null)\r\n \r\n const [idleTime, setIdleTime] = useState(dayjs().add(60, 'minute'))\r\n \r\n const resetIdleTime = () => {\r\n setIdleTime(dayjs().add(60, 'minute'))\r\n }\r\n\r\n // on app load, set the idle time onClick updater on the document\r\n useEffect(() => {\r\n document.addEventListener('click', resetIdleTime)\r\n return () => {\r\n document.removeEventListener('click', resetIdleTime)\r\n }\r\n }, [])\r\n\r\n const checkIdleState = useCallback(async () => {\r\n const now = dayjs()\r\n const idleTimeRemaining = idleTime.diff(now, 'minute')\r\n if (idleTimeRemaining < 1) {\r\n await client.signoutRedirect()\r\n }\r\n }, [user, client, idleTime])\r\n\r\n // look at the idle state and logout when idle time is less than 1 minute\r\n useEffect(() => {\r\n // check every 3 seconds\r\n const interval = setInterval(() => {\r\n checkIdleState()\r\n }, 3000)\r\n // clear the interval when the user changes or the page is refreshed\r\n return () => {\r\n clearInterval(interval)\r\n }\r\n }, [checkIdleState])\r\n\r\n // when the user changes, update roles list\r\n useEffect(() => {\r\n setRoles(getRoles(user))\r\n }, [user])\r\n\r\n useEffect(() => {\r\n const getUser = async () => {\r\n try {\r\n const u = await client.getUser()\r\n setUser(u)\r\n } catch (e) {\r\n console.log('UserContext: Error getting user -- ', e)\r\n }\r\n }\r\n if (client) {\r\n getUser()\r\n }\r\n }, [client])\r\n\r\n return (\r\n \r\n {children}\r\n \r\n )\r\n})\r\n\r\nconst UserContextProvider = withConfig(({ config, children }) => {\r\n \r\n const settings = {\r\n authority: config.REACT_APP_OIDC_AUTHORITY,\r\n client_id: config.REACT_APP_OIDC_CLIENT_ID,\r\n client_secret: config.REACT_APP_OIDC_CLIENT_SECRET,\r\n redirect_uri: config.REACT_APP_OIDC_CLIENT_REDIRECT_URI,\r\n silent_redirect_uri: config.REACT_APP_OIDC_CLIENT_SILENT_REDIRECT_URI,\r\n response_type: config.REACT_APP_OIDC_RESPONSE_TYPE,\r\n scope: config.REACT_APP_OIDC_SCOPE,\r\n post_logout_redirect_uri: config.REACT_APP_OIDC_POST_LOGOUT_REDIRECT_URI,\r\n automaticSilentRenew: true\r\n // loadUserInfo: true\r\n }\r\n \r\n const userManager = new UserManager(settings)\r\n return (\r\n \r\n {children}\r\n \r\n )\r\n})\r\n\r\nexport { UserContext }\r\nexport default UserContextProvider\r\n","export default {\r\n background: {},\r\n backgroundMask: {\r\n cover: {\r\n color: {\r\n value: '#fff',\r\n },\r\n opacity: 1,\r\n },\r\n enable: false,\r\n },\r\n detectRetina: true,\r\n fpsLimit: 30,\r\n infection: {\r\n cure: false,\r\n delay: 0,\r\n enable: false,\r\n infections: 0,\r\n stages: [],\r\n },\r\n interactivity: {\r\n detectsOn: 'canvas',\r\n events: {\r\n onClick: {\r\n enable: false,\r\n mode: 'push',\r\n },\r\n onDiv: {\r\n elementId: '',\r\n enable: false,\r\n mode: [],\r\n },\r\n onHover: {\r\n enable: false,\r\n mode: 'repulse',\r\n parallax: {\r\n enable: false,\r\n force: 60,\r\n smooth: 10,\r\n },\r\n },\r\n resize: true,\r\n },\r\n modes: {\r\n bubble: {\r\n distance: 400,\r\n duration: 2,\r\n opacity: 0.8,\r\n size: 40,\r\n },\r\n connect: {\r\n distance: 80,\r\n links: {\r\n opacity: 0.5,\r\n },\r\n radius: 60,\r\n },\r\n grab: {\r\n distance: 400,\r\n links: {\r\n opacity: 1,\r\n },\r\n },\r\n push: {\r\n quantity: 4,\r\n },\r\n remove: {\r\n quantity: 2,\r\n },\r\n repulse: {\r\n distance: 200,\r\n duration: 0.4,\r\n speed: 1,\r\n },\r\n slow: {\r\n factor: 3,\r\n radius: 200,\r\n },\r\n },\r\n },\r\n particles: {\r\n collisions: {\r\n enable: false,\r\n mode: 'bounce',\r\n },\r\n color: {\r\n value: 'rgb(51, 51, 51)',\r\n animation: {\r\n enable: false,\r\n speed: 1,\r\n sync: true,\r\n },\r\n },\r\n links: {\r\n blink: false,\r\n color: {\r\n value: 'rgb(51, 51, 51)',\r\n },\r\n consent: false,\r\n distance: 150,\r\n enable: true,\r\n opacity: 0.4,\r\n shadow: {\r\n blur: 5,\r\n color: {\r\n value: 'lime',\r\n },\r\n enable: false,\r\n },\r\n triangles: {\r\n enable: false,\r\n },\r\n width: 1,\r\n warp: false,\r\n },\r\n move: {\r\n attract: {\r\n enable: false,\r\n rotate: {\r\n x: 600,\r\n y: 1200,\r\n },\r\n },\r\n direction: 'none',\r\n enable: true,\r\n noise: {\r\n delay: {\r\n random: {\r\n enable: false,\r\n minimumValue: 0,\r\n },\r\n value: 0,\r\n },\r\n enable: false,\r\n factor: {\r\n horizontal: {\r\n value: 50,\r\n offset: 0,\r\n },\r\n vertical: {\r\n value: 10,\r\n offset: 40000,\r\n },\r\n },\r\n },\r\n outMode: 'out',\r\n random: false,\r\n speed: 2,\r\n straight: false,\r\n trail: {\r\n enable: false,\r\n length: 10,\r\n fillColor: {\r\n value: '#000000',\r\n },\r\n },\r\n vibrate: false,\r\n warp: false,\r\n },\r\n number: {\r\n density: {\r\n enable: true,\r\n area: 800,\r\n factor: 1000,\r\n },\r\n limit: 0,\r\n value: 80,\r\n },\r\n opacity: {\r\n animation: {\r\n enable: false,\r\n minimumValue: 0.1,\r\n speed: 1,\r\n sync: false,\r\n },\r\n random: {\r\n enable: false,\r\n minimumValue: 1,\r\n },\r\n value: 0.5,\r\n },\r\n rotate: {\r\n animation: {\r\n enable: false,\r\n speed: 0,\r\n sync: false,\r\n },\r\n direction: 'clockwise',\r\n random: false,\r\n value: 0,\r\n },\r\n shadow: {\r\n blur: 0,\r\n color: {\r\n value: '#000000',\r\n },\r\n enable: false,\r\n offset: {\r\n x: 0,\r\n y: 0,\r\n },\r\n },\r\n shape: {\r\n options: {\r\n character: {\r\n fill: true,\r\n close: true,\r\n font: 'Verdana',\r\n style: '',\r\n value: '*',\r\n weight: '400',\r\n },\r\n char: {\r\n fill: true,\r\n close: true,\r\n font: 'Verdana',\r\n style: '',\r\n value: '*',\r\n weight: '400',\r\n },\r\n image: {\r\n fill: true,\r\n close: true,\r\n height: 100,\r\n replaceColor: false,\r\n src:\r\n 'https://cdn.matteobruni.it/images/particles/github.svg',\r\n width: 100,\r\n },\r\n images: {\r\n fill: true,\r\n close: true,\r\n height: 100,\r\n replaceColor: false,\r\n src:\r\n 'https://cdn.matteobruni.it/images/particles/github.svg',\r\n width: 100,\r\n },\r\n polygon: {\r\n fill: true,\r\n close: true,\r\n sides: 5,\r\n },\r\n star: {\r\n fill: true,\r\n close: true,\r\n sides: 5,\r\n },\r\n },\r\n type: 'circle',\r\n },\r\n size: {\r\n animation: {\r\n destroy: 'none',\r\n enable: false,\r\n minimumValue: 0.1,\r\n speed: 40,\r\n startValue: 'max',\r\n sync: false,\r\n },\r\n random: {\r\n enable: true,\r\n minimumValue: 1,\r\n },\r\n value: 5,\r\n },\r\n stroke: {\r\n color: {\r\n value: '#000000',\r\n },\r\n width: 0,\r\n opacity: 1,\r\n },\r\n twinkle: {\r\n lines: {\r\n enable: false,\r\n frequency: 0.05,\r\n opacity: 1,\r\n },\r\n particles: {\r\n enable: false,\r\n frequency: 0.05,\r\n opacity: 1,\r\n },\r\n },\r\n },\r\n pauseOnBlur: true,\r\n}\r\n","import React, { useContext } from 'react'\r\nimport { UserContext } from '../../wrappers/UserContext'\r\n\r\nconst LogoutButton = () => {\r\n const { client } = useContext(UserContext)\r\n return (\r\n \r\n Logout\r\n
\r\n )\r\n}\r\n\r\nexport default LogoutButton","import React from 'react'\r\nimport withConfig from '../../wrappers/withConfig'\r\n\r\nconst ManageUsersButton = withConfig(({config}) => {\r\n const { CONFIGURED_URLS } = config\r\n return (\r\n \r\n )\r\n})\r\n\r\nexport default ManageUsersButton","import React from 'react'\r\nimport { FaCogs } from 'react-icons/fa'\r\nimport { Link } from 'react-router-dom'\r\n\r\nexport default () => {\r\n return (\r\n \r\n \r\n \r\n \r\n
\r\n )\r\n}\r\n","import React, { useContext } from 'react'\r\nimport { hasAccessToAdmin } from '../../../utils/user/permissions'\r\nimport { UserContext } from '../../wrappers/UserContext'\r\nimport LogoutButton from '../user/LogoutButton'\r\nimport ManageUsersButton from '../user/ManageUsersButton'\r\nimport AdminLink from './AdminLink'\r\n\r\nconst LandingPageNav = () => {\r\n const { roles } = useContext(UserContext)\r\n return (\r\n \r\n
\r\n {hasAccessToAdmin(roles) ? (\r\n
\r\n ) : null}\r\n {hasAccessToAdmin(roles) ? (\r\n
\r\n \r\n
\r\n ) : null}\r\n
\r\n \r\n
\r\n
\r\n
\r\n )\r\n}\r\n\r\nexport default LandingPageNav\r\n","import React, { useContext } from 'react'\r\nimport Particles from 'react-particles-js'\r\nimport { UserContext } from '../../wrappers/UserContext'\r\nimport particleConfig from './particleConfig'\r\nimport LandingPageNav from './LandingPageNav'\r\n\r\nconst LandingPageWrapper = ({children}) => {\r\n const { user } = useContext(UserContext)\r\n return (\r\n <>\r\n \r\n {user && }\r\n \r\n
\r\n
\r\n
\r\n Director Dashboard\r\n
\r\n
\r\n
\r\n {children}\r\n
\r\n
\r\n
\r\n >\r\n )\r\n}\r\n\r\nexport default LandingPageWrapper\r\n","import React, { useContext } from 'react'\r\nimport { DashboardContext } from '../../wrappers/DashboardContext'\r\nimport LoadingSpinner from '../../elem/LoadingSpinner'\r\nimport DashboardLink from '../../elem/landing-page/DashboardLink'\r\nimport LandingPageWrapper from '../../elem/landing-page/LandingPageWrapper'\r\n\r\nconst LandingPage = () => {\r\n const { loading, dashboards } = useContext(DashboardContext)\r\n\r\n if (loading || !dashboards) {\r\n return \r\n }\r\n\r\n return (\r\n \r\n \r\n My Dashboards:\r\n
\r\n \r\n {dashboards.map((dashboard, idx) => {\r\n return \r\n })}\r\n
\r\n \r\n )\r\n}\r\n\r\nexport default LandingPage\r\n","import React, { useContext, useMemo } from 'react'\r\nimport { useTable } from 'react-table'\r\nimport LoadingSpinner from '../../elem/LoadingSpinner'\r\nimport { FaEdit, FaTrashAlt } from 'react-icons/fa'\r\nimport { Link } from 'react-router-dom'\r\nimport { DashboardContext } from '../../wrappers/DashboardContext'\r\n\r\nconst Cell = ({ value, name, description }) => {\r\n return (\r\n <>\r\n \r\n \r\n \r\n \r\n \r\n \r\n >\r\n )\r\n}\r\n\r\nconst createColumns = () => [\r\n { Header: 'Dashboard Name', accessor: 'DashboardName', Cell: ({cell, row}) => (\r\n \r\n {row.original.DashboardName}\r\n \r\n )},\r\n { Header: 'Description', accessor: 'DashboardDescription' },\r\n {\r\n Header: 'Manage',\r\n accessor: 'DashboardId',\r\n Cell: ({ cell, row }) => (\r\n | \r\n ),\r\n },\r\n]\r\n\r\nexport default () => {\r\n const { loading, dashboards } = useContext(DashboardContext)\r\n const data = dashboards ? dashboards : null\r\n\r\n if (loading || !dashboards) {\r\n return \r\n }\r\n\r\n const columns = useMemo(() => createColumns(), [])\r\n\r\n const tableData = useMemo(() => data, [data])\r\n const {\r\n getTableProps,\r\n getTableBodyProps,\r\n headerGroups,\r\n rows,\r\n prepareRow,\r\n } = useTable({ columns, data: tableData })\r\n\r\n return (\r\n \r\n
\r\n \r\n {headerGroups.map((headerGroup) => (\r\n \r\n {headerGroup.headers.map((column) => (\r\n \r\n {column.render('Header')}\r\n | \r\n ))}\r\n
\r\n ))}\r\n \r\n \r\n {rows.map((row) => {\r\n prepareRow(row)\r\n return (\r\n \r\n {row.cells.map((cell) => {\r\n return (\r\n \r\n {cell.render('Cell')}\r\n | \r\n )\r\n })}\r\n
\r\n )\r\n })}\r\n \r\n
\r\n
\r\n )\r\n}\r\n","import React from 'react'\r\nimport { NavLink, useLocation } from 'react-router-dom'\r\n\r\nexport default () => {\r\n const location = useLocation()\r\n const dashRegex = location.pathname.match(/dashboard\\/([\\w\\s-]*)\\/?/)\r\n const pageRegex = location.pathname.match(/page\\/([\\w\\s-]*)\\/?/)\r\n const widgRegex = location.pathname.match(/widget\\/([\\w\\s-]*)\\/?/)\r\n\r\n const dashboardName = dashRegex && dashRegex[1] ? dashRegex[1] : null\r\n const pageId = pageRegex && pageRegex[1] ? pageRegex[1] : null\r\n const widgetId = widgRegex && widgRegex[1] ? widgRegex[1] : null\r\n\r\n return (\r\n \r\n )\r\n}\r\n","import React from 'react'\r\nimport Breadcrumb from '../../features/admin/Breadcrumb'\r\n\r\nconst AdminLayout = ({ title, children }) => (\r\n \r\n
\r\n
\r\n
{title}
\r\n \r\n
{children}
\r\n
\r\n)\r\nexport default AdminLayout","import React from 'react'\r\nimport AdminTable from './AdminTable'\r\nimport { Link } from 'react-router-dom'\r\nimport { FaPlus } from 'react-icons/fa'\r\nimport AdminLayout from '../../elem/admin/AdminLayout'\r\nconst AdminDashboard = () => {\r\n return (\r\n <>\r\n \r\n \r\n \r\n
\r\n
\r\n \r\n >\r\n )\r\n}\r\n\r\nexport default AdminDashboard\r\n","import React from 'react'\r\nimport ReactTooltip from 'react-tooltip'\r\n\r\nconst Tooltip = ({id, extraClass, stayOpen, place }) => {\r\n return \r\n}\r\n\r\nconst DefaultTooltip = ({id, className, place}) => (\r\n \r\n)\r\n\r\nconst IconTooltip = ({id, place}) => (\r\n \r\n)\r\n\r\nexport default DefaultTooltip\r\n\r\nexport { DefaultTooltip, IconTooltip, Tooltip }","import React from 'react'\r\nimport { FaInfoCircle } from 'react-icons/fa'\r\nimport { DefaultTooltip as Tooltip } from '../Tooltip'\r\n\r\nexport default ({\r\n label,\r\n name,\r\n register,\r\n registerParams,\r\n onChange,\r\n helper,\r\n example,\r\n value,\r\n active,\r\n errors,\r\n defaultValue,\r\n className,\r\n}) => {\r\n const text = !value ? defaultValue : value\r\n\r\n return (\r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n
\r\n {active !== false && helper && (\r\n
\r\n \r\n
\r\n )}\r\n
\r\n {active === false ? (\r\n
{text}
\r\n ) : (\r\n
\r\n )}\r\n
\r\n \r\n
\r\n
\r\n
\r\n\r\n
\r\n {errors[name] ? errors[name].message : null}\r\n {errors[name] && errors[name].type === 'validate'\r\n ? 'Must be valid JSON'\r\n : null}\r\n
\r\n
\r\n )\r\n}\r\n","import React, { useContext } from 'react'\r\nimport toast from '../../../elem/Toast'\r\nimport Input from '../../../elem/form/TextInput'\r\nimport { useForm } from 'react-hook-form'\r\nimport withConfig from '../../../wrappers/withConfig'\r\nimport { APIRequestContext } from '../../../wrappers/APIRequestContext'\r\n\r\nconst CreateForm = ({ config, closeForm }) => {\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const { register, handleSubmit, errors } = useForm()\r\n const { API_URL } = config\r\n\r\n const POST = (newDashboard) => {\r\n return {\r\n method: 'POST',\r\n mode: 'cors',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Access-Control-Allow-Origin': '*',\r\n 'Access-Control-Allow-Headers':\r\n 'Access-Control-Allow-Origin, X-Requested-With, Content-Type, Accept',\r\n },\r\n body: JSON.stringify(newDashboard),\r\n }\r\n }\r\n\r\n const createDashboard = (dashboard) => {\r\n authenticatedFetch(`${API_URL}/admin/dashboard/create`, POST(dashboard))\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return response.json()\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Create Dashboard:' +\r\n (e.message ? e.message : 'Dashboard create failed'),\r\n })\r\n })\r\n .finally(() => {\r\n closeForm(dashboard.DashboardName)\r\n })\r\n }\r\n\r\n const trimData = (data) => {\r\n Object.keys(data).map((key) => typeof data[key] === \"string\" ? data[key] = data[key].replace(/\\s\\s+/g, ' ').trim() : data[key])\r\n return data\r\n }\r\n\r\n\r\n const onSubmit = (data) => {\r\n createDashboard(trimData(data))\r\n }\r\n\r\n return (\r\n \r\n )\r\n}\r\n\r\nexport default withConfig(CreateForm)\r\n","import React, { useState } from 'react'\r\nimport CreateForm from './CreateForm'\r\nimport { Redirect } from 'react-router-dom'\r\nimport AdminLayout from '../../../elem/admin/AdminLayout'\r\n\r\nconst CreateDashboard = () => {\r\n const [name, setName] = useState(null) \r\n \r\n const closeForm = (name) => {\r\n setName(name)\r\n }\r\n\r\n if (name) {\r\n return (\r\n \r\n )\r\n }\r\n return (\r\n \r\n \r\n \r\n )\r\n}\r\n\r\nexport default CreateDashboard\r\n","import React, { useState, useEffect, useContext } from 'react'\r\nimport toast from '../../../elem/Toast'\r\nimport { useForm } from 'react-hook-form'\r\nimport { FaEdit, FaCheckCircle, FaTimesCircle } from 'react-icons/fa'\r\nimport Input from '../../../elem/form/TextInput'\r\nimport withConfig from '../../../wrappers/withConfig'\r\nimport { APIRequestContext } from '../../../wrappers/APIRequestContext'\r\n\r\nconst EditForm = ({ config, dashboard, closeForm }) => {\r\n const [state, setState] = useState(dashboard)\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const { register, handleSubmit, errors } = useForm()\r\n const [editing, setEditing] = useState(false)\r\n const formClass = editing ? 'button is-info is-small' : 'hidden'\r\n const textClass = editing ? 'hidden' : 'button is-info is-small'\r\n const { API_URL } = config\r\n\r\n useEffect(() => {\r\n setState(dashboard)\r\n }, [dashboard]) \r\n\r\n\r\n const PUT = (dashboard) => {\r\n return {\r\n method: 'PUT',\r\n mode: 'cors',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Access-Control-Allow-Origin': '*',\r\n 'Access-Control-Allow-Headers':\r\n 'Access-Control-Allow-Origin, X-Requested-With, Content-Type, Accept',\r\n },\r\n body: JSON.stringify(dashboard),\r\n }\r\n }\r\n\r\n const editDashboard = (dashboard) => {\r\n\r\n authenticatedFetch(\r\n `${API_URL}/admin/dashboard/edit/${dashboard.DashboardId}`,\r\n PUT(dashboard)\r\n )\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return \r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Edit Dashboard:' +\r\n (e.message ? e.message : 'Dashboard edit failed'),\r\n })\r\n })\r\n .finally(() => {\r\n setState(dashboard)\r\n closeForm(dashboard.DashboardName)\r\n setEditing(false)\r\n })\r\n }\r\n\r\n const handleChange = (event) => {\r\n setState({\r\n ...state,\r\n [event.target.name]: event.target.value,\r\n })\r\n }\r\n const handleClick = () => {\r\n setState(dashboard)\r\n setEditing(false)\r\n }\r\n\r\n const trimData = (data) => {\r\n Object.keys(data).map((key) => typeof data[key] === \"string\" ? data[key] = data[key].replace(/\\s\\s+/g, ' ').trim() : data[key])\r\n return data\r\n }\r\n \r\n const onSubmit = (data) => {\r\n editDashboard({...dashboard, ...trimData(data)})\r\n }\r\n\r\n return (\r\n <>\r\n \r\n >\r\n )\r\n}\r\n\r\nexport default withConfig(EditForm)\r\n","import React, { useMemo, useEffect, useState } from 'react'\r\nimport { useTable, useSortBy, usePagination } from 'react-table'\r\nimport { FaEdit, FaTrashAlt, FaAngleUp, FaAngleDown } from 'react-icons/fa'\r\nimport { Link } from 'react-router-dom'\r\n\r\n\r\nconst ManageCell = ({ value, dashboardName, name }) => {\r\n return (\r\n <>\r\n \r\n \r\n \r\n \r\n \r\n \r\n >\r\n )\r\n}\r\n\r\nconst createColumns = (name) => [\r\n { Header: 'Page Id', accessor: 'PageId' },\r\n { Header: 'Page Name', accessor: 'PageName' },\r\n { Header: 'URL', accessor: 'Location' },\r\n {\r\n Header: 'Number of Widgets',\r\n accessor: 'Widgets'\r\n },\r\n {\r\n Header: 'Manage',\r\n accessor: 'PageId',\r\n id: '5',\r\n Cell: ({ cell, row }) => (\r\n \r\n ),\r\n },\r\n]\r\n\r\nconst EditTable = ({ pages, name }) => {\r\n\r\n const [data, setData] = useState([])\r\n\r\n useEffect(() => { \r\n name ? setData(pages.map((page) => ({...page, DashboardName: name}))) : setData(pages)\r\n }, [name, pages]) \r\n\r\n const columns = useMemo(() => createColumns(name), [name])\r\n const tableData = useMemo(() => data, [data])\r\n const {\r\n getTableProps,\r\n getTableBodyProps,\r\n headerGroups,\r\n prepareRow,\r\n page,\r\n canPreviousPage,\r\n canNextPage,\r\n pageOptions,\r\n nextPage,\r\n previousPage,\r\n state: { pageIndex },\r\n } = useTable(\r\n {\r\n columns,\r\n data: tableData,\r\n initialState: { pageIndex: 0, pageSize: 5 },\r\n },\r\n useSortBy,\r\n usePagination\r\n )\r\n\r\n return (\r\n \r\n
Dashboard Pages
\r\n
\r\n \r\n {headerGroups.map((headerGroup) => (\r\n \r\n {headerGroup.headers.map((column) => (\r\n \r\n {column.render('Header')}\r\n \r\n {column.isSorted\r\n ? column.isSortedDesc\r\n ? \r\n : \r\n : ''}\r\n \r\n | \r\n ))}\r\n
\r\n ))}\r\n \r\n \r\n {page.map((row, i) => {\r\n prepareRow(row)\r\n return (\r\n \r\n {row.cells.map((cell) => {\r\n return (\r\n \r\n {cell.render('Cell')}\r\n | \r\n )\r\n })}\r\n
\r\n )\r\n })}\r\n \r\n
\r\n
\r\n {' '}\r\n \r\n Page{' '}\r\n \r\n {pageIndex + 1} of {pageOptions.length}\r\n {' '}\r\n \r\n {' '}\r\n
\r\n
\r\n )\r\n}\r\n\r\nexport default EditTable\r\n","import React, { useContext, useEffect, useState } from 'react'\r\nimport EditForm from './EditForm'\r\nimport EditTable from './EditTable'\r\nimport { Link, withRouter } from 'react-router-dom'\r\nimport { FaPlus } from 'react-icons/fa'\r\nimport { CurrentDashboardContext } from '../../../wrappers/CurrentDashboardContext'\r\nimport AdminLayout from '../../../elem/admin/AdminLayout'\r\n\r\nconst EditDashboard = withRouter(({ history }) => {\r\n const { widgetGroups, currentDashboard, setNewObject } = useContext(\r\n CurrentDashboardContext\r\n )\r\n const [edited, setEdited] = useState(null)\r\n\r\n useEffect(() => {\r\n if (history && edited) {\r\n history.replace(`/admin/dashboard/${edited}`)\r\n setEdited(false)\r\n }\r\n }, [edited, history])\r\n\r\n const closeForm = (name) => {\r\n setEdited(name)\r\n setNewObject(name)\r\n }\r\n\r\n if (!currentDashboard) {\r\n return null\r\n }\r\n\r\n const pages = widgetGroups.map((page)=> ({...page, Widgets: page.Widgets.length}))\r\n\r\n return (\r\n \r\n
\r\n \r\n
\r\n \r\n \r\n
\r\n
\r\n \r\n
\r\n )\r\n})\r\n\r\nexport default EditDashboard\r\n","import React from 'react'\r\nimport ReactSelect from 'react-select'\r\nimport { Controller } from 'react-hook-form'\r\nimport { FaInfoCircle } from 'react-icons/fa'\r\nimport { DefaultTooltip as Tooltip } from '../Tooltip'\r\n\r\nexport default ({\r\n options,\r\n onChange,\r\n control,\r\n initialValue,\r\n name,\r\n disabled,\r\n label,\r\n helper,\r\n rules,\r\n errors,\r\n className,\r\n placeholder,\r\n value,\r\n}) => {\r\n return (\r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n
\r\n {helper && (\r\n
\r\n \r\n
\r\n )}\r\n
\r\n \r\n }\r\n name={name}\r\n control={control}\r\n defaultValue={\r\n initialValue ? initialValue : null\r\n }\r\n key={`my_unique_select_key__${JSON.stringify(\r\n value\r\n )}`}\r\n value={value}\r\n rules={rules}\r\n onChange={onChange}\r\n />\r\n \r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n {errors && errors[name] ? errors[name].message : null}\r\n
\r\n
\r\n
\r\n )\r\n}\r\n","import React from 'react'\r\nimport { FaInfoCircle } from 'react-icons/fa'\r\nimport { DefaultTooltip as Tooltip } from '../Tooltip'\r\n\r\nconst Checkbox = ({\r\n name,\r\n label,\r\n onChange,\r\n helper,\r\n example,\r\n defaultChecked,\r\n disabled,\r\n register,\r\n className,\r\n}) => {\r\n return (\r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n
\r\n \r\n
\r\n {helper && (\r\n
${example ? `
${example}` : ''}`}\r\n data-for={name}\r\n className=\"icon is-size-7 is-left\"\r\n >\r\n \r\n \r\n \r\n )}\r\n
\r\n
\r\n
\r\n )\r\n}\r\n\r\nexport default Checkbox\r\n","import React from 'react'\r\nimport { FaInfoCircle } from 'react-icons/fa'\r\nimport { DefaultTooltip as Tooltip } from '../Tooltip'\r\n\r\nexport default ({\r\n label,\r\n name,\r\n register,\r\n registerParams,\r\n onChange,\r\n helper,\r\n example,\r\n value,\r\n active,\r\n errors,\r\n defaultValue,\r\n className,\r\n}) => {\r\n const text = !value ? defaultValue : value\r\n\r\n return (\r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n
\r\n {active === false ? (\r\n
{text}
\r\n ) : (\r\n
\r\n )}\r\n
\r\n
\r\n
\r\n\r\n
\r\n {errors && errors[name] ? errors[name].message : null}\r\n
\r\n
\r\n )\r\n}","import React, { useState, useEffect, useContext } from 'react'\r\nimport toast from '../../../elem/Toast'\r\nimport { useForm } from 'react-hook-form'\r\nimport Select from '../../../elem/form/Select'\r\nimport Input from '../../../elem/form/TextInput'\r\nimport Checkbox from '../../../elem/form/Checkbox'\r\nimport NumberInput from '../../../elem/form/NumberInput'\r\nimport withConfig from '../../../wrappers/withConfig'\r\nimport { APIRequestContext } from '../../../wrappers/APIRequestContext'\r\n\r\nconst CreatePageForm = ({ config, closeForm, dashboardId, pageIds }) => {\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const { register, handleSubmit, control, errors, unregister } = useForm()\r\n const {API_URL} = config\r\n const [id, setId] = useState(null)\r\n const [parentId, setParentId] = useState(null)\r\n const [visible, setVisible] = useState(false)\r\n const hiddenClass = visible ? null : 'hidden'\r\n const options = pageIds.map((x) => ({ label: x.name, value: x.id }))\r\n \r\n if (id) {\r\n closeForm(id)\r\n }\r\n \r\n useEffect(() => {\r\n if(!visible) {\r\n unregister([\"ParentPageId\", \"SharedKey\", \"DetailKey\"]) \r\n }\r\n }, [visible, unregister])\r\n \r\n const POST = (page) => {\r\n return {\r\n method: 'POST',\r\n mode: 'cors',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Access-Control-Allow-Origin': '*',\r\n 'Access-Control-Allow-Headers':\r\n 'Access-Control-Allow-Origin, X-Requested-With, Content-Type, Accept',\r\n },\r\n body: JSON.stringify(page),\r\n }\r\n }\r\n \r\n const createPage = (page) => {\r\n \r\n authenticatedFetch(`${API_URL}/admin/page/create`, POST(page))\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return response.json()\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .then((response) => {\r\n setId(response)\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Create Page:' +\r\n (e.message ? e.message : 'Page create failed'),\r\n })\r\n })\r\n }\r\n\r\n const handleSelectChange = (event) => {\r\n setParentId(event[0].value)\r\n return event\r\n }\r\n \r\n\r\n const trimData = (data) => {\r\n Object.keys(data).map((key) => typeof data[key] === \"string\" ? data[key] = data[key].replace(/\\s\\s+/g, ' ').trim() : data[key])\r\n return data\r\n }\r\n\r\n const onSubmit = (data) => {\r\n !visible ? delete data.ParentPageId && delete data.SharedKey && delete data.DetailKey: data.ParentPageId = data.ParentPageId.value\r\n createPage({ ...trimData(data), DashboardId: dashboardId, Seq: Number(data.Seq), ParentPageId: parentId })\r\n }\r\n\r\n return (\r\n \r\n )\r\n}\r\n\r\nexport default withConfig(CreatePageForm)\r\n","import React, { useState, useContext } from 'react'\r\nimport CreatePageForm from './CreatePageForm'\r\nimport { Redirect } from 'react-router-dom'\r\nimport AdminLayout from '../../../elem/admin/AdminLayout'\r\nimport {CurrentDashboardContext} from '../../../wrappers/CurrentDashboardContext'\r\n\r\nconst CreatePage = () => {\r\n const { widgetGroups, currentDashboard, setNewObject } = useContext(CurrentDashboardContext)\r\n const [pageId, setPageId] = useState(null)\r\n\r\n if (!currentDashboard) {\r\n return null\r\n }\r\n const pages = widgetGroups\r\n .filter(\r\n (widget) =>\r\n widgetGroups.indexOf(widget.PageId) === widgetGroups.lastIndexOf(widget.PageId)\r\n )\r\n .map((widget) => ({\r\n name: widget.PageName,\r\n id: widget.PageId,\r\n }))\r\n \r\n const closeForm = (response) => {\r\n setPageId(response)\r\n }\r\n \r\n if (pageId) {\r\n setNewObject(pageId)\r\n return (\r\n \r\n )\r\n }\r\n return (\r\n \r\n \r\n \r\n )\r\n}\r\n\r\nexport default CreatePage\r\n","import React, { useState, useEffect, useContext } from 'react'\r\nimport toast from '../../../elem/Toast'\r\nimport { useForm } from 'react-hook-form'\r\nimport { FaEdit, FaCheckCircle, FaTimesCircle } from 'react-icons/fa'\r\nimport Input from '../../../elem/form/TextInput'\r\nimport Checkbox from '../../../elem/form/Checkbox'\r\nimport Select from '../../../elem/form/Select'\r\nimport NumberInput from '../../../elem/form/NumberInput'\r\nimport withConfig from '../../../wrappers/withConfig'\r\nimport { APIRequestContext } from '../../../wrappers/APIRequestContext'\r\n\r\nconst EditPageForm = ({ config, page, pages, closeForm }) => {\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const { register, handleSubmit, control, errors, unregister } = useForm()\r\n const [editing, setEditing] = useState(false)\r\n const [state, setState] = useState(page)\r\n const options = pages.map((x) => ({ label: x.name, value: x.id }))\r\n const defaultSelect = options.filter(\r\n (option) => option.value === state.ParentPageId\r\n )[0]\r\n const [parentId, setParentId] = useState(null)\r\n const [visible, setVisible] = useState(page.ParentPageId)\r\n const hiddenClass = visible ? null : 'hidden'\r\n const { API_URL } = config\r\n\r\n useEffect(() => {\r\n if (!visible) {\r\n unregister(['ParentPageId', 'SharedKey', 'DetailKey'])\r\n }\r\n }, [visible, unregister])\r\n\r\n const PUT = (page) => {\r\n return {\r\n method: 'PUT',\r\n mode: 'cors',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Access-Control-Allow-Origin': '*',\r\n 'Access-Control-Allow-Headers':\r\n 'Access-Control-Allow-Origin, X-Requested-With, Content-Type, Accept',\r\n },\r\n body: JSON.stringify(page),\r\n }\r\n }\r\n\r\n const editPage = (editedPage) => {\r\n authenticatedFetch(\r\n `${API_URL}/admin/page/edit/${editedPage.PageId}`,\r\n PUT(editedPage)\r\n )\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Edit Dashboard:' +\r\n (e.message ? e.message : 'Dashboard edit failed'),\r\n })\r\n })\r\n .then(() => {\r\n setEditing(false)\r\n setState(editedPage)\r\n closeForm(editedPage)\r\n })\r\n }\r\n\r\n const handleChange = (event) => {\r\n setState({\r\n ...state,\r\n [event.target.name]: event.target.value,\r\n })\r\n }\r\n\r\n const handleSelectChange = (event) => {\r\n setParentId(event[0].value)\r\n return event\r\n }\r\n const handleClick = () => {\r\n setState(page)\r\n setEditing(false)\r\n }\r\n\r\n const trimData = (data) => {\r\n Object.keys(data).map((key) =>\r\n typeof data[key] === 'string'\r\n ? (data[key] = data[key].replace(/\\s\\s+/g, ' ').trim())\r\n : data[key]\r\n )\r\n return data\r\n }\r\n\r\n const onSubmit = (data) => {\r\n !visible\r\n ? delete data.ParentPageId && delete data.SharedKey && (data.DetailKey = \"\")\r\n : (data.ParentPageId = parentId)\r\n\r\n editPage({\r\n ...trimData(data),\r\n PageId: page.PageId,\r\n Seq: Number(data.Seq),\r\n })\r\n }\r\n\r\n let formClass = editing ? 'button is-info is-small' : 'hidden'\r\n let textClass = editing ? 'hidden' : 'button is-info is-small'\r\n\r\n return (\r\n \r\n )\r\n}\r\n\r\nexport default withConfig(EditPageForm)\r\n","import React, { useMemo } from 'react'\r\nimport { useTable, useSortBy, usePagination } from 'react-table'\r\nimport { Link } from 'react-router-dom'\r\nimport { FaEdit, FaTrashAlt, FaAngleUp, FaAngleDown } from 'react-icons/fa'\r\n\r\n\r\nconst EditPageTable = ({ widgets }) => {\r\n\r\n const ManageCell = ({ value, pageId, name, dashboardName }) => {\r\n return (\r\n <>\r\n \r\n \r\n \r\n \r\n \r\n \r\n >\r\n )\r\n }\r\n const createColumns = () => [\r\n { Header: 'Widget Id', accessor: 'WidgetId' },\r\n { Header: 'Widget Name', accessor: 'WidgetName' },\r\n { Header: 'Widget Type', accessor: 'WidgetType' },\r\n {\r\n Header: 'Manage',\r\n accessor: 'WidgetId',\r\n id: '5',\r\n Cell: ({ cell, row }) => (\r\n \r\n ),\r\n },\r\n ]\r\n\r\n const data = widgets\r\n const columns = useMemo(() => createColumns(), [])\r\n const tableData = useMemo(() => data, [data])\r\n\r\n const {\r\n getTableProps,\r\n getTableBodyProps,\r\n headerGroups,\r\n prepareRow,\r\n page,\r\n canPreviousPage,\r\n canNextPage,\r\n pageOptions,\r\n nextPage,\r\n previousPage,\r\n state: { pageIndex },\r\n } = useTable(\r\n {\r\n columns,\r\n data: tableData,\r\n initialState: { pageIndex: 0, pageSize: 5 },\r\n },\r\n useSortBy,\r\n usePagination\r\n )\r\n\r\n return (\r\n \r\n
Page Widgets
\r\n
\r\n \r\n {headerGroups.map((headerGroup) => (\r\n \r\n {headerGroup.headers.map((column) => (\r\n \r\n {column.render('Header')}\r\n \r\n {column.isSorted\r\n ? column.isSortedDesc\r\n ? \r\n : \r\n : ''}\r\n \r\n | \r\n ))}\r\n
\r\n ))}\r\n \r\n \r\n {page.map((row, i) => {\r\n prepareRow(row)\r\n return (\r\n \r\n {row.cells.map((cell) => {\r\n return (\r\n \r\n {cell.render('Cell')}\r\n | \r\n )\r\n })}\r\n
\r\n )\r\n })}\r\n \r\n
\r\n
\r\n {' '}\r\n \r\n Page{' '}\r\n \r\n {pageIndex + 1} of {pageOptions.length}\r\n {' '}\r\n \r\n {' '}\r\n
\r\n
\r\n )\r\n}\r\n\r\nexport default EditPageTable\r\n","import React, { useContext, useState } from 'react'\r\nimport EditPageForm from './EditPageForm'\r\nimport EditPageTable from './EditPageTable'\r\nimport { Link } from 'react-router-dom'\r\nimport { CurrentDashboardContext } from '../../../wrappers/CurrentDashboardContext'\r\nimport AdminLayout from '../../../elem/admin/AdminLayout'\r\nimport { FaPlus } from 'react-icons/fa'\r\n\r\nconst EditPage = () => {\r\n const {\r\n widgetGroups,\r\n currentWidgetGroup,\r\n currentDashboard,\r\n pageId,\r\n setNewObject,\r\n } = useContext(CurrentDashboardContext)\r\n const id = Number(pageId)\r\n const [edited, setEdited] = useState(false)\r\n\r\n if (!currentDashboard || !pageId || !Object.keys(currentWidgetGroup).length || Number(pageId) !== currentWidgetGroup.PageId) {\r\n return null\r\n }\r\n\r\n const page = currentWidgetGroup\r\n const pages = widgetGroups\r\n .filter(\r\n (widget) =>\r\n widgetGroups.indexOf(widget.PageId) ===\r\n widgetGroups.lastIndexOf(widget.PageId)\r\n )\r\n .map((widget) => ({ name: widget.PageName, id: widget.PageId }))\r\n .filter((widget) => widget.id !== id)\r\n\r\n const closeForm = (edit) => {\r\n setEdited(edit)\r\n }\r\n\r\n if (edited) {\r\n setNewObject(edited)\r\n }\r\n\r\n return (\r\n \r\n
\r\n \r\n
\r\n \r\n \r\n
\r\n
\r\n
\r\n
\r\n \r\n
\r\n )\r\n}\r\n\r\nexport default EditPage\r\n","import React from 'react'\r\nimport { Helmet , HelmetProvider } from 'react-helmet-async'\r\nimport withConfig from '../wrappers/withConfig'\r\n\r\nconst Header = ({ config }) => {\r\n const { APP_TITLE } = config\r\n return (\r\n \r\n \r\n {APP_TITLE}\r\n \r\n \r\n )\r\n}\r\n\r\nexport default withConfig(Header)","import React, { useContext } from 'react'\r\nimport { Link } from 'react-router-dom'\r\n\r\nimport { CurrentDashboardContext } from '../../wrappers/CurrentDashboardContext'\r\nimport { IconTooltip as Tooltip } from '../../elem/Tooltip'\r\n\r\nexport default () => {\r\n const { currentWidgetGroup, widgetGroups } = useContext(CurrentDashboardContext)\r\n return (\r\n \r\n {widgetGroups.filter(target => !target.ParentPageId).map((target, idx) => {\r\n const isActive = target.Location === currentWidgetGroup.Location || target.PageId === currentWidgetGroup.ParentPageId\r\n const tooltipId = `nav-${target.PageId}`\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n )\r\n })}\r\n
\r\n )\r\n}\r\n","import React, { useContext } from 'react'\r\nimport { Link } from 'react-router-dom'\r\nimport { FaArrowLeft } from 'react-icons/fa'\r\n\r\nimport { CurrentDashboardContext } from '../../wrappers/CurrentDashboardContext'\r\nimport { IconTooltip as Tooltip } from '../../elem/Tooltip'\r\n\r\nexport default () => {\r\n const { currentWidgetGroup, widgetGroups } = useContext(CurrentDashboardContext)\r\n const backTarget = currentWidgetGroup.ParentPageId ? widgetGroups.find(x => x.PageId === currentWidgetGroup.ParentPageId) : {Location: '', PageName: 'Dashboard List', DashboardName: ''}\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n )\r\n}\r\n","import React from 'react'\r\n\r\nimport Links from './Links'\r\nimport BackButton from './BackButton'\r\n\r\nexport default () => (\r\n \r\n)\r\n","import React, { useContext } from 'react'\r\nimport { DateContext } from '../../wrappers/DateContext'\r\nimport { FaCalendar, FaTimes } from 'react-icons/fa'\r\n\r\nconst DateDisplayComponent = ({ dateRangeLabel, date }) => {\r\n if (dateRangeLabel !== 'Custom') {\r\n return {dateRangeLabel}
\r\n } else {\r\n return (\r\n <>\r\n {date.minDate ? (\r\n \r\n
From:
\r\n {date.minDate}\r\n
\r\n ) : null}\r\n {date.maxDate ? (\r\n \r\n
To:
\r\n {date.maxDate}\r\n
\r\n ) : null}\r\n >\r\n )\r\n }\r\n}\r\nexport default () => {\r\n const {\r\n date,\r\n selectorVisible,\r\n toggleSelectorVisible,\r\n dateRangeLabel,\r\n } = useContext(DateContext)\r\n\r\n return (\r\n toggleSelectorVisible(!selectorVisible)}\r\n >\r\n
\r\n {selectorVisible ? : }\r\n
\r\n
\r\n
\r\n )\r\n}\r\n","import React, { useContext } from 'react'\r\nimport { FaGripHorizontal, FaSave, FaSyncAlt, FaTimes } from 'react-icons/fa'\r\nimport { CurrentDashboardContext } from '../wrappers/CurrentDashboardContext'\r\n\r\nexport default () => {\r\n const {\r\n editingLayout,\r\n saveLayoutState,\r\n startLayoutEdit,\r\n cancelLayoutEdit,\r\n resetLayoutState\r\n } = useContext(CurrentDashboardContext)\r\n\r\n return (\r\n \r\n
{\r\n editingLayout ? saveLayoutState() : startLayoutEdit()\r\n }}\r\n >\r\n {editingLayout ?
:
}\r\n
\r\n {`${editingLayout ? 'Save' : 'Edit'} Layout`}\r\n
\r\n {/*
*/}\r\n
\r\n {editingLayout && (\r\n <>\r\n
{\r\n resetLayoutState()\r\n }}\r\n >\r\n
\r\n
\r\n {`Reset Layout`}\r\n
\r\n {/*
*/}\r\n
\r\n
{\r\n cancelLayoutEdit()\r\n }}\r\n >\r\n
\r\n
\r\n {`Cancel`}\r\n
\r\n {/*
*/}\r\n
\r\n >\r\n )}\r\n
\r\n )\r\n}\r\n","import React, { useContext, useState } from 'react'\r\nimport { Link } from 'react-router-dom'\r\nimport { CurrentDashboardContext } from '../wrappers/CurrentDashboardContext'\r\n\r\nexport default ({ name }) => {\r\n const { currentWidgetGroup, widgetGroups } = useContext(\r\n CurrentDashboardContext\r\n )\r\n\r\n const [isActive, setIsActive] = useState(false)\r\n\r\n\r\n return (\r\n \r\n
\r\n
setIsActive(!isActive)}\r\n >\r\n {name}\r\n \r\n \r\n \r\n
\r\n
\r\n \r\n
\r\n )\r\n}\r\n","import React, { useContext } from 'react'\r\nimport { CurrentDashboardContext } from '../wrappers/CurrentDashboardContext'\r\nimport DateBarComponent from './date/BarComponent'\r\nimport LayoutEditorToggle from './LayoutEditorToggle'\r\nimport { Helmet, HelmetProvider } from 'react-helmet-async'\r\nimport TopBarDropdown from './TopBarDropdown'\r\nimport withConfig from '../wrappers/withConfig'\r\nimport { UserContext } from '../wrappers/UserContext'\r\nimport { hasAccessToAdmin } from '../../utils/user/permissions'\r\nimport AdminLink from './landing-page/AdminLink'\r\nimport LogoutButton from './user/LogoutButton'\r\nimport ManageUsersButton from './user/ManageUsersButton'\r\n\r\nexport default withConfig(({ config }) => {\r\n const {\r\n currentWidgetGroup: { PageName },\r\n dashboardName,\r\n pageLoading,\r\n } = useContext(CurrentDashboardContext)\r\n const { user, roles } = useContext(UserContext)\r\n const { IS_PUBLIC } = config\r\n\r\n return (\r\n \r\n
\r\n \r\n \r\n {dashboardName} {!IS_PUBLIC ? `Director ` : ''}Dashboard\r\n \r\n \r\n \r\n
\r\n
\r\n
{dashboardName}
\r\n {!pageLoading &&
- {PageName}
}\r\n
\r\n \r\n
\r\n
\r\n
\r\n
\r\n
\r\n {user && (\r\n
\r\n {hasAccessToAdmin(roles) ?
: null}\r\n {hasAccessToAdmin(roles) ?
: null}\r\n
\r\n
\r\n )}\r\n
\r\n
\r\n
\r\n )\r\n})\r\n","import React from 'react'\r\n\r\nimport { dateOptionsColumnOne, dateOptionsColumnTwo } from '../../../utils/constants/date/dateOptions'\r\n\r\nexport default ({\r\n setMinDate,\r\n setMaxDate,\r\n dateRangeLabel,\r\n setDateRangeLabel,\r\n}) => {\r\n const onClick = (option) => {\r\n const { minDate, maxDate, label } = option\r\n setMinDate(minDate)\r\n setMaxDate(maxDate)\r\n setDateRangeLabel(label)\r\n }\r\n return (\r\n \r\n
\r\n
\r\n {dateOptionsColumnOne.map((option, idx) => (\r\n
onClick(option)}\r\n >\r\n
\r\n {option.label}\r\n
\r\n
\r\n {option.dateRangeString}\r\n
\r\n
\r\n ))}\r\n
\r\n
\r\n {dateOptionsColumnTwo.map((option, idx) => (\r\n
onClick(option)}\r\n >\r\n
\r\n {option.label}\r\n
\r\n
\r\n {option.dateRangeString}\r\n
\r\n
\r\n ))}\r\n
\r\n
\r\n
\r\n )\r\n}\r\n","import React, { useContext, useEffect, useState } from 'react'\r\nimport DatePicker from 'react-datepicker'\r\n\r\nimport { DateContext } from '../../wrappers/DateContext'\r\n\r\nimport DateOptions from './DateOptions'\r\nimport { dateToString, stringToDate } from '../../../utils/dateFormatting'\r\n\r\n\r\nconst DatePickerComponent = ({ setDate, date }) => {\r\n const [width, setWidth] = useState(window.innerWidth)\r\n\r\n const listener = () => {\r\n setWidth(window.innerWidth)\r\n }\r\n\r\n window.addEventListener('resize', listener);\r\n\r\n return (\r\n {\r\n const formattedDate = dateToString(value)\r\n setDate(formattedDate, true)\r\n return value\r\n }}\r\n dateFormat=\"MM/dd/yyyy\"\r\n showYearDropdown\r\n showMonthDropdown\r\n yearDropdownItemNumber={150}\r\n scrollableYearDropdown\r\n scrollableMonthDropdown\r\n autoComplete=\"off\"\r\n inline={width > 769 ? true : false}\r\n popperModifiers={{\r\n offset: {\r\n enabled: true,\r\n },\r\n preventOverflow: {\r\n enabled: true,\r\n escapeWithReference: false,\r\n boundariesElement: \"viewport\"\r\n }\r\n }}\r\n />\r\n )\r\n}\r\n\r\nexport default () => {\r\n const {\r\n selectorVisible,\r\n toggleSelectorVisible,\r\n date,\r\n setDate,\r\n dateRangeLabel,\r\n setDateRangeLabel,\r\n } = useContext(DateContext)\r\n\r\n const [selectedDateRangeLabel, setSelectedDateRangeLabel] = useState(\r\n dateRangeLabel\r\n )\r\n const [selectedDate, setSelectedDate] = useState({\r\n minDate: date.minDate,\r\n maxDate: date.maxDate,\r\n })\r\n\r\n\r\n useEffect(() => {\r\n setSelectedDateRangeLabel(dateRangeLabel)\r\n }, [dateRangeLabel])\r\n\r\n useEffect(() => {\r\n setSelectedDate({minDate: date.minDate, maxDate: date.maxDate})\r\n }, [date])\r\n\r\n const updateDateSelection = (field, value, custom) => {\r\n if (custom) {\r\n setSelectedDateRangeLabel('Custom')\r\n }\r\n setSelectedDate((state) => ({\r\n ...state,\r\n [field]: value,\r\n }))\r\n }\r\n\r\n const applyDateSelection = () => {\r\n setDate(selectedDate)\r\n setDateRangeLabel(selectedDateRangeLabel)\r\n toggleSelectorVisible(false)\r\n }\r\n\r\n return (\r\n \r\n
\r\n
\r\n
\r\n
\r\n \r\n \r\n
\r\n
\r\n
\r\n )\r\n}\r\n","import React, { createContext, useState, useEffect, useContext } from 'react'\r\n\r\nimport toast from '../elem/Toast'\r\nimport withConfig from './withConfig'\r\nimport { DateContext } from './DateContext'\r\nimport { CurrentDashboardContext } from './CurrentDashboardContext'\r\nimport { FaChartPie, FaChartBar, FaHashtag, FaRegMap, FaCube, FaClipboardList, FaChartLine, FaTable, FaMap, FaComment} from 'react-icons/fa'\r\nimport { MdTrendingUp } from 'react-icons/md'\r\nimport { APIRequestContext } from './APIRequestContext'\r\n\r\nconst WidgetDataContext = createContext(null)\r\n\r\nconst WidgetDataContextProvider = ({ config, widget, children }) => {\r\n const {\r\n WidgetId: widgetId,\r\n WidgetName: widgetName,\r\n WidgetType: widgetType,\r\n GroupBy\r\n } = widget\r\n\r\n const datectx = useContext(DateContext)\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const { detailKey, currentWidgetGroup } = useContext(CurrentDashboardContext)\r\n const [widgetData, setWidgetData] = useState(null)\r\n const [loading, setLoading] = useState(true)\r\n const [showLegend, setShowLegend] = useState(true)\r\n const [ abortController, setAbortController ] = useState(new AbortController())\r\n const { API_URL } = config // api url configured in public/config..js\r\n \r\n // group by\r\n const groupByOptions = GroupBy ? GroupBy.split('|') : []\r\n const [activeGroupBy, setGroupBy] = useState(groupByOptions.length ? groupByOptions[0] : null)\r\n const dateQueryString = datectx ? datectx.dateQueryString : null\r\n\r\n const fetchWidget = () => {\r\n const abort = new AbortController()\r\n setAbortController(abort)\r\n setLoading(true)\r\n const groupByClause = activeGroupBy\r\n ? `groupBy=${encodeURIComponent(activeGroupBy)}`\r\n : ''\r\n const dateClause = dateQueryString ? `&${dateQueryString}` : ''\r\n const detailKeyClause = detailKey ? `&detailKey=${detailKey}` : ''\r\n authenticatedFetch(`${API_URL}/widget/${widgetId}?${groupByClause}${dateClause}${detailKeyClause}`, {signal: abort.signal})\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return response.json()\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .then((response) => {\r\n setWidgetData(response.data)\r\n setLoading(false)\r\n })\r\n .catch((e) => {\r\n if (e.name !== 'AbortError') {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Widget: ' +\r\n (e.message\r\n ? e.message\r\n : 'Unable to connect to the server. Please try again later.'),\r\n })\r\n setLoading(false)\r\n }\r\n })\r\n }\r\n\r\n // fetch the data associated w/ the widget\r\n useEffect(() => {\r\n if (widgetId) {\r\n if (currentWidgetGroup.DetailKey) {\r\n // if (detailKey) {\r\n abortController.abort()\r\n fetchWidget()\r\n // }\r\n } else {\r\n abortController.abort()\r\n fetchWidget()\r\n }\r\n }\r\n }, [widgetId, API_URL, activeGroupBy, dateQueryString, currentWidgetGroup, detailKey])\r\n\r\n if (!widget) {\r\n return null\r\n }\r\n\r\n //setIcon\r\n const getIconFromWidgetType = (type) => {\r\n switch (type) {\r\n case 'Number':\r\n return \r\n case 'Grid':\r\n return \r\n case 'BarChart':\r\n return \r\n case 'LineChart':\r\n return \r\n case 'ScatterChart':\r\n return \r\n case 'ThemeMap':\r\n return \r\n case 'PieChart':\r\n return \r\n case 'Form':\r\n return \r\n case '2DFeatureMap':\r\n return \r\n case '3DFeatureMap':\r\n return \r\n case 'Text':\r\n return \r\n default:\r\n return 'div'\r\n }\r\n }\r\n\r\n const widgetIcon = getIconFromWidgetType(widgetType)\r\n\r\n return (\r\n \r\n {children}\r\n \r\n )\r\n}\r\n\r\nexport { WidgetDataContext }\r\nexport default withConfig(WidgetDataContextProvider)\r\n","import React from 'react'\r\nimport ReactSelect from 'react-select'\r\nconst selectStyles = {\r\n control: (provided) => ({\r\n ...provided,\r\n minHeight: '1em',\r\n maxHeight: '2em'\r\n }),\r\n container: (provided) => ({\r\n ...provided,\r\n minHeight: '1em',\r\n }),\r\n indicatorsContainer: (provided) => ({\r\n ...provided,\r\n height: '2em',\r\n }),\r\n valueContainer: (provided) => ({\r\n ...provided,\r\n minHeight: '1em',\r\n maxHeight: '2em',\r\n padding: '0px 0px 0px 1px',\r\n width: '2em'\r\n }),\r\n menu: (provided) => ({\r\n ...provided,\r\n zIndex: 3,\r\n }),\r\n clearIndicator: (provided) => ({\r\n ...provided,\r\n padding: '2px',\r\n }),\r\n dropdownIndicator: (provided) => ({\r\n ...provided,\r\n padding: '1px',\r\n width: '1.5em'\r\n }),\r\n}\r\n\r\nexport default ({ options, value, defaultValue, onChange, disabled }) => (\r\n \r\n)\r\n","import React, { useContext } from 'react'\r\n\r\nimport { WidgetDataContext } from '../../../wrappers/WidgetDataContext'\r\nimport Select from '../../select/Select'\r\n\r\nexport default () => {\r\n const { widgetData, activeGroupBy, setGroupBy } = useContext(\r\n WidgetDataContext\r\n )\r\n const groupBy = widgetData ? widgetData.GroupBy : null\r\n if (!groupBy || !groupBy.length > 1) {\r\n return null\r\n }\r\n // split the groupBy field (eg Form Type|Form Status) into separate values\r\n // (eg ['Form Type', 'Form Status'])\r\n const options = groupBy.split('|').map((x) => ({ label: x, value: x }))\r\n \r\n // if there is only one option, do not show the dropdown\r\n if (options.length === 1) {\r\n return null\r\n }\r\n \r\n const currentValue = !!activeGroupBy\r\n ? options.find((option) => option.value === activeGroupBy)\r\n : options[0]\r\n return (\r\n \r\n {/*
By
*/}\r\n
\r\n
\r\n
\r\n )\r\n}\r\n","import React, { useContext } from 'react'\r\nimport { FaExpandArrowsAlt, FaCompressArrowsAlt } from 'react-icons/fa'\r\n\r\nimport { DefaultTooltip as Tooltip } from '../../Tooltip'\r\nimport { WidgetDataContext } from '../../../wrappers/WidgetDataContext'\r\nimport { CurrentDashboardContext } from '../../../wrappers/CurrentDashboardContext'\r\n\r\nexport default () => {\r\n const { widgetData: widget, setLoading } = useContext(WidgetDataContext)\r\n const { expandedWidgetId, setExpandedWidgetId } = useContext(\r\n CurrentDashboardContext\r\n )\r\n\r\n const setExpandedWidget = (widgetId) => {\r\n setLoading(true)\r\n setExpandedWidgetId(widgetId)\r\n setTimeout(() => {\r\n setLoading(false)\r\n }, 200)\r\n }\r\n\r\n if (!widget) {\r\n return null\r\n }\r\n\r\n const expanded = expandedWidgetId === widget.WidgetId\r\n const tooltipId = `expand-${widget.WidgetId}`\r\n return (\r\n \r\n {expanded ? (\r\n
setExpandedWidget(null)}\r\n style={{zIndex: 10 }}\r\n >\r\n \r\n
\r\n ) : (\r\n
setExpandedWidget(widget.WidgetId)}\r\n >\r\n \r\n
\r\n )}\r\n
\r\n
\r\n )\r\n}\r\n","import React, { useContext, useState } from 'react'\r\nimport { WidgetDataContext } from '../../../wrappers/WidgetDataContext'\r\n\r\nexport default () => {\r\n const { widgetData: widget, showLegend, setShowLegend } = useContext(\r\n WidgetDataContext\r\n )\r\n const [show, setShow] = useState(false)\r\n\r\n if (!widget) {\r\n return null\r\n }\r\n\r\n if (widget && widget.WidgetType.match(/Grid|Form|Number|Text/)) {\r\n return null\r\n }\r\n\r\n const handleClick = () => {\r\n setShow(!show)\r\n show ? setShowLegend('all') : setShowLegend('none')\r\n }\r\n\r\n return (\r\n \r\n
\r\n {!showLegend ? (\r\n
setShowLegend(true)}\r\n >\r\n Show Legend\r\n
\r\n ) : (\r\n
setShowLegend(false)}\r\n >\r\n Hide Legend\r\n
\r\n )}\r\n
\r\n {widget.WidgetType !== 'ThemeMap' ? (\r\n
\r\n {showLegend ? (\r\n
\r\n {show ? 'Select All' : 'Deselect All'}\r\n
\r\n ) : null}\r\n
\r\n ) : null}\r\n
\r\n )\r\n}\r\n","export default [\r\n '2DFeatureMap',\r\n '3DFeatureMap',\r\n 'BarChart',\r\n 'Form',\r\n 'Grid',\r\n 'LineChart',\r\n 'ScatterChart',\r\n 'PieChart',\r\n 'ThemeMap'\r\n]","import ReactHtmlParser from 'react-html-parser'\r\nconst linkToHref = item => {\r\n const transformed = Object.keys(item).reduce((acc, curr) => {\r\n const value = item[curr]\r\n if (typeof value === 'string' && value.includes('')) {\r\n // parse links and set to the csv syntax for hyperlinks\r\n try {\r\n const parsed = ReactHtmlParser(value)\r\n const csvString = `=HYPERLINK(\"${parsed[0].props.href}\",\"${parsed[0].props.children[0]}\")`\r\n return {\r\n ...acc,\r\n [curr]: csvString\r\n }\r\n } catch (e) {\r\n // when failing to parse the link, juts default to the og value\r\n return {\r\n ...acc,\r\n [curr]: value\r\n }\r\n }\r\n } else {\r\n // let all values that are not strings with tags pass through uneffected\r\n return {\r\n ...acc,\r\n [curr]: value\r\n }\r\n }\r\n }, {})\r\n return transformed \r\n}\r\nconst transforms = [linkToHref]\r\nexport default transforms","import React, { useCallback, useContext, useState } from 'react'\r\nimport { IconContext } from 'react-icons'\r\nimport { FaDownload, FaSpinner } from 'react-icons/fa'\r\nimport { Parser } from 'json2csv'\r\nimport { saveAs } from 'file-saver'\r\n\r\nimport { WidgetDataContext } from '../../../wrappers/WidgetDataContext'\r\nimport { DefaultTooltip as Tooltip } from '../../Tooltip'\r\nimport exportableWidgetTypes from '../../../../utils/constants/widgets/exportableWidgetTypes'\r\nimport csvTransforms from '../../../../utils/constants/widgets/exportTransforms'\r\nimport { DateContext } from '../../../wrappers/DateContext'\r\nimport dayjs from 'dayjs'\r\nimport withConfig from '../../../wrappers/withConfig'\r\nimport { CurrentDashboardContext } from '../../../wrappers/CurrentDashboardContext'\r\nimport { APIRequestContext } from '../../../wrappers/APIRequestContext'\r\n\r\nconst constructDownloadName = (widget, date, exportPrefix, page) => {\r\n let applyDateFilter = page ? page.ApplyDateFilter : true\r\n let downloadName = exportPrefix\r\n ? `${exportPrefix}_${widget.WidgetName}`\r\n : `${widget.PageName}_${widget.WidgetName}`\r\n if (applyDateFilter) {\r\n if (!date.minDate) {\r\n const maxDateString = dayjs(date.maxDate).format('YYYYMMDD').toString()\r\n downloadName += `_Ending_${maxDateString}`\r\n } else {\r\n const minDateString = dayjs(date.minDate).format('YYYYMMDD').toString()\r\n const maxDateString = dayjs(date.maxDate).format('YYYYMMDD').toString()\r\n \r\n downloadName += `_${minDateString}-${maxDateString}`\r\n }\r\n }\r\n const dateString = dayjs(Date.now()).format('YYYYMMDD')\r\n downloadName += `_Generated_${dateString}`\r\n return downloadName\r\n}\r\n\r\nexport default withConfig(({ config }) => {\r\n const { widgetData: widget } = useContext(WidgetDataContext)\r\n const { detailKey, currentWidgetGroup } = useContext(CurrentDashboardContext)\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const { API_URL } = config\r\n const { date } = useContext(DateContext)\r\n const [exporting, setExporting] = useState(false)\r\n\r\n const fetchExportPrefix = useCallback(async () => {\r\n try {\r\n var url = widget.DetailKey\r\n ? `${API_URL}/widget/exportPrefix/${widget.WidgetId}?id=${detailKey}`\r\n : `${API_URL}/widget/exportPrefix/${widget.WidgetId}`\r\n return await authenticatedFetch(url)\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return await response.text()\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .then((response) => {\r\n return response\r\n })\r\n } catch (e) {\r\n return null\r\n }\r\n }, [widget, detailKey, API_URL])\r\n\r\n const downloadData = useCallback(async () => {\r\n if (!exporting) {\r\n setExporting(true)\r\n var exportPrefix = await fetchExportPrefix()\r\n try {\r\n const parser = new Parser({\r\n transforms: csvTransforms\r\n })\r\n // convert the data to .csv\r\n let csv = parser.parse(widget.Data)\r\n\r\n // update the Value label field, if exists\r\n if (widget.ValueLabel) {\r\n csv = csv.replace('\"Value\"', `\"${widget.ValueLabel}\"`)\r\n }\r\n const downloadName = `${constructDownloadName(\r\n widget,\r\n date,\r\n exportPrefix,\r\n currentWidgetGroup\r\n )}.csv`\r\n saveAs(new Blob([csv], { type: 'text/csv' }), downloadName)\r\n } catch (e) {\r\n console.log('csv export error:', e)\r\n }\r\n setExporting(false)\r\n }\r\n }, [widget, date, exporting, fetchExportPrefix, currentWidgetGroup])\r\n\r\n // if there is no widget or no data, do not render this button\r\n if (!(widget && widget.Data && widget.Data.length)) {\r\n return null\r\n }\r\n\r\n // if the widget is not a supported exportable type,\r\n // then do not render this button\r\n if (!exportableWidgetTypes.includes(widget.WidgetType)) {\r\n return null\r\n }\r\n\r\n const tooltipId = `download-${widget.WidgetId}`\r\n return (\r\n \r\n {!exporting ? (\r\n
downloadData()}\r\n >\r\n \r\n
\r\n ) : (\r\n
\r\n \r\n \r\n \r\n
\r\n )}\r\n
\r\n
\r\n )\r\n})\r\n","import React from 'react'\r\n\r\nexport default () => (\r\n \r\n
\r\n No Data\r\n
\r\n
\r\n)","const allowsOverflow = widget => {\r\n const widgetType = widget && widget.WidgetType ? widget.WidgetType : null\r\n switch (widgetType) {\r\n case \"BarChart\":\r\n return false\r\n case \"LineChart\":\r\n return false\r\n case \"ScatterChart\":\r\n return false\r\n case \"PieChart\":\r\n return false\r\n default:\r\n return true\r\n }\r\n}\r\n\r\nexport { allowsOverflow }","import React, { useContext } from 'react'\r\nimport { WidgetDataContext } from '../../wrappers/WidgetDataContext'\r\nimport LoadingSpinner from '../LoadingSpinner'\r\nimport GroupBySelector from './shared/GroupBySelector'\r\nimport MaximizeButton from './shared/MaximizeButton'\r\nimport LegendButton from './shared/LegendButton'\r\nimport ExportButton from './shared/ExportButton'\r\nimport NoDataComponent from './NoDataComponent'\r\nimport { allowsOverflow } from '../../../utils/widgets/contentStyle'\r\n\r\nexport default ({ children }) => {\r\n const {\r\n widgetName: name,\r\n widgetIcon: icon,\r\n loading,\r\n widgetData: widget,\r\n } = useContext(WidgetDataContext)\r\n const widgetHasData = widget && widget.Data && widget.Data.length\r\n return (\r\n \r\n
\r\n
\r\n
\r\n
{icon}
\r\n
{name}
\r\n
\r\n
\r\n \r\n \r\n
\r\n
\r\n
\r\n \r\n \r\n
\r\n
\r\n
\r\n {loading ? (\r\n \r\n ) : widgetHasData ? (\r\n children\r\n ) : (\r\n \r\n )}\r\n
\r\n
\r\n )\r\n}\r\n","const compactFormat = new Intl.NumberFormat('en-US', { notation: \"compact\", compactDisplay: \"short\" })\r\nconst longFormat = new Intl.NumberFormat('en-US', { maximumFractionDigits: 2})\r\n\r\nconst formatNumber = (value, type) => {\r\n if (type === 'compact') {\r\n return compactFormat.format(value)\r\n } else {\r\n return longFormat.format(value)\r\n }\r\n}\r\n\r\nexport { formatNumber }","import React, { useContext } from 'react'\r\nimport { WidgetDataContext } from '../../../wrappers/WidgetDataContext'\r\nimport NoDataComponent from '../NoDataComponent'\r\nimport { formatNumber } from '../../../../utils/numberFormatting'\r\n\r\nconst getValue = data => {\r\n if (data && data && data.length && data[0].Value) {\r\n return formatNumber(data[0].Value)\r\n } else {\r\n return \r\n }\r\n}\r\n\r\nexport default () => {\r\n const { widgetData: widget } = useContext(WidgetDataContext)\r\n const data = widget ? widget.Data : null\r\n const value = getValue(data)\r\n return (\r\n \r\n
\r\n
\r\n {value}\r\n
\r\n
\r\n
\r\n )\r\n}\r\n","import React from 'react'\r\nimport ReactHtmlParser, { convertNodeToElement } from 'react-html-parser'\r\nimport { Link } from 'react-router-dom'\r\nimport dayjs from 'dayjs'\r\n\r\nimport { formatNumber } from './numberFormatting'\r\n\r\nconst transform = (node, index) => {\r\n if (node.name === 'a') {\r\n if (\r\n node.attribs.href.includes('http') ||\r\n node.attribs.href.includes('www') || node.attribs.href.includes('javascript')\r\n ) {\r\n return convertNodeToElement(node, index, transform)\r\n } else {\r\n return {convertNodeToElement(node, index, transform)}\r\n }\r\n } else {\r\n return convertNodeToElement(node, index, transform)\r\n }\r\n}\r\n\r\nconst formatValue = (value) => {\r\n if (typeof value !== 'undefined' && value !== null) {\r\n if (isNaN(value)) {\r\n if (dayjs(value).isValid()) {\r\n return dayjs(value).format('YYYY/MM/DD').toString()\r\n } else {\r\n return ReactHtmlParser(value, {transform})\r\n }\r\n } else {\r\n return formatNumber(value)\r\n }\r\n } else {\r\n return 'NULL'\r\n }\r\n}\r\n\r\nexport default formatValue\r\n","import React from 'react'\r\n\r\nimport formatValue from '../../../../utils/valueFormatting'\r\n\r\nexport default ({value, type}) => (\r\n {type === 'string' ? value : formatValue(value)}
\r\n)","export default [\r\n 'ID',\r\n 'Name',\r\n 'API 14',\r\n 'OperatorKey',\r\n 'Operator Number',\r\n 'API No',\r\n 'Well Name',\r\n 'Incident',\r\n 'Postal Code',\r\n 'Lease',\r\n \"Well Type\",\r\n \"Producing Geological Formation\",\r\n \"Surface Managing Entity\",\r\n \"Subsurface Managing Entity\",\r\n \"Tribe\",\r\n \"Pre-Plugging Methane Emissions (grams/hr)\",\r\n \"Post-Plugging Methane Emissions (grams/hr)\",\r\n \"Methane Measurement Method\",\r\n \"Habitat Restored (acres)\",\r\n \"Restoration Endpoint\",\r\n \"Surface Water Contamination Indicators\",\r\n \"Surface Water Remediation Method\",\r\n \"Groundwater Contamination Indicators\",\r\n \"Groundwater Remediation Method\",\r\n \"Community Impact\",\r\n \"Community Impact Indicators\",\r\n \"Witness Name & Certification\",\r\n \"Result\",\r\n \"Place Value\",\r\n \"Package Name\",\r\n \"Plugged Year-Month\",\r\n 'Field',\r\n \"Date Restoration Complete\",\r\n \"Surface Water Remediation Completion Date\",\r\n \"Groundwater Remediation Completion Date\",\r\n \"Witness Date\",\r\n \"Tribal\"\r\n]","import React, {\r\n useContext,\r\n useMemo,\r\n useCallback\r\n} from 'react'\r\nimport { useTable, useSortBy, useFlexLayout, useFilters } from 'react-table'\r\nimport AutoSizer from 'react-virtualized-auto-sizer'\r\nimport { TableVirtuoso } from 'react-virtuoso'\r\nimport { WidgetDataContext } from '../../../wrappers/WidgetDataContext'\r\nimport Cell from './Cell'\r\nimport { CurrentDashboardContext } from '../../../wrappers/CurrentDashboardContext'\r\nimport { FaChevronUp, FaChevronDown } from 'react-icons/fa'\r\nimport stringFields from '../../../../utils/constants/widgets/stringFields'\r\n\r\n// Define a default UI for filtering\r\nfunction DefaultColumnFilter({\r\n column: { filterValue, preFilteredRows, setFilter },\r\n }) {\r\n const count = preFilteredRows.length\r\n \r\n return (\r\n {\r\n setFilter(e.target.value || undefined) // Set undefined to remove the filter entirely\r\n }}\r\n className=\"input is-small\"\r\n placeholder={`Search ${count} records...`}\r\n />\r\n )\r\n }\r\n\r\nconst parseColumns = (data, valueLabel, sharedKey) => {\r\n const exampleRow = data[0] // get the first row of the data to extract cols from\r\n const columns = Object.keys(exampleRow)\r\n .filter((col) => col !== 'Value') // remove the value column, we'll add next w/ proper label\r\n .filter((col) => (sharedKey ? col !== sharedKey : true)) // if there is a shared key, we don't want it to display in the grid\r\n .map((col) => ({\r\n Header: () => ,\r\n accessor: col,\r\n Cell: ({ cell }) => (\r\n | \r\n ),\r\n Filter: DefaultColumnFilter\r\n \r\n })) // map column name => {Header: , accessor: } format\r\n\r\n // all values must be in the 'Value' field of the returned data\r\n if (Object.keys(exampleRow).find((col) => col === 'Value')) {\r\n const valueColumn = {\r\n Header: () => (\r\n \r\n ),\r\n accessor: 'Value',\r\n Cell: ({ cell }) => | ,\r\n Filter: DefaultColumnFilter\r\n }\r\n return [...columns, valueColumn]\r\n } else {\r\n return columns\r\n }\r\n}\r\n\r\n\r\nexport default () => {\r\n const { sharedPageKey, sharedPageId, setSharedPageId } = useContext(\r\n CurrentDashboardContext\r\n )\r\n const { widgetData: widget } = useContext(WidgetDataContext)\r\n const data = widget ? widget.Data : null\r\n const detailKey = widget ? widget.DetailKey : null\r\n const columns = useMemo(\r\n () => parseColumns(data, widget.ValueLabel, widget.SharedKey),\r\n [data, widget.ValueLabel, widget.SharedKey]\r\n )\r\n\r\n const tableData = useMemo(() => data, [data])\r\n const defaultColumn = React.useMemo(\r\n () => ({\r\n minWidth: 30, // minWidth is only used as a limit for resizing\r\n width: 100, // width is used for both the flex-basis and flex-grow\r\n maxWidth: 300, // maxWidth is only used as a limit for resizing\r\n }),\r\n []\r\n )\r\n\r\n const {\r\n getTableProps,\r\n getTableBodyProps,\r\n headerGroups,\r\n rows,\r\n prepareRow,\r\n } = useTable(\r\n { columns, data: tableData, defaultColumn },\r\n useFilters,\r\n useSortBy,\r\n useFlexLayout\r\n )\r\n\r\n\r\n const onClick = useCallback(\r\n (row) =>\r\n sharedPageKey &&\r\n detailKey !== sharedPageKey &&\r\n setSharedPageId(row.original[sharedPageKey]),\r\n [sharedPageKey, setSharedPageId, detailKey]\r\n )\r\n\r\n return (\r\n \r\n \r\n {({ height }) => {\r\n return (\r\n\r\n ,\r\n TableBody: React.forwardRef(({ style, ...props }, ref) => ),\r\n TableRow: (props) => {\r\n const index = props['data-index']\r\n const row = rows[index]\r\n return (\r\n onClick(row)}\r\n className={`tr ${\r\n sharedPageKey &&\r\n sharedPageId === row.original[sharedPageKey]\r\n ? 'active'\r\n : ''\r\n } ${sharedPageKey ? 'pointer' : ''}`}\r\n />\r\n )}\r\n }}\r\n fixedHeaderContent={() => {\r\n return headerGroups.map((headerGroup) => (\r\n \r\n {headerGroup.headers.map((column) => (\r\n \r\n \r\n \r\n \r\n {column.render('Header')}\r\n \r\n {column.isSorted ? (\r\n column.isSortedDesc ? (\r\n \r\n ) : (\r\n \r\n )\r\n ) : (\r\n ''\r\n )}\r\n \r\n \r\n \r\n {/* {column.canFilter ? column.render('Filter') : null} */}\r\n \r\n | \r\n ))}\r\n \r\n ))\r\n }}\r\n itemContent={(index) => {\r\n const row = rows[index]\r\n prepareRow(row)\r\n return row.cells.map((cell) => {cell.render('Cell')} | )\r\n }}\r\n />\r\n )\r\n }}\r\n \r\n \r\n )\r\n}\r\n","const getHiddenColumns = (widget) => {\r\n const options = widget.WidgetOptions\r\n if (options && options !== '') {\r\n try {\r\n const config = JSON.parse(options)\r\n if (config.hiddenColumns) {\r\n return config.hiddenColumns\r\n }\r\n } catch {}\r\n return\r\n }\r\n return []\r\n}\r\n\r\nexport default getHiddenColumns","// palette obtained from ColorBrewer: https://colorbrewer2.org/#type=qualitative&scheme=Paired&n=12\r\n// and mixed with material design palette https://www.materialpalette.com/colors\r\nconst qualitative = [\r\n '#1f78b4', // blue\r\n '#33a02c', // green\r\n '#fdbf6f', // creme\r\n '#a6cee3', // light blue\r\n '#ff7f00', // orange\r\n '#b2df8a', // light green\r\n '#b15928', // light brown\r\n '#cab2d6', // lavender\r\n '#6a3d9a', // purple\r\n '#3f51b5', // indigo 500\r\n '#009688', // teal 500\r\n '#ab47bc', // purple 400\r\n '#f44336', // red 500\r\n '#90a4ae', // blue grey 300\r\n '#673ab7', // deep purple 500\r\n '#00bcd4', // cyan 500\r\n '#e91e63', // pink 500\r\n '#aeea00', // lime green A700\r\n '#ffc107', // amber 500\r\n '#ff5722', // deep orange 500\r\n //additional colors added\r\n '#880e4f', //dark purple\r\n '#f48fb1', // light pink\r\n '#1b5e20', //dark green\r\n '#ffab40', // med orange\r\n '#b71c1c', //dark red\r\n]\r\n\r\n// palette obtained from ColorBrewer: https://colorbrewer2.org/#type=sequential&scheme=Reds&n=6\r\nconst heatMap = [\r\n '#fee5d9',\r\n '#fcbba1',\r\n '#fc9272',\r\n '#fb6a4a',\r\n '#de2d26',\r\n '#a50f15',\r\n]\r\nexport default qualitative\r\nexport { qualitative, heatMap }\r\n","import React, { useState, useEffect, createContext, useCallback } from 'react'\r\nimport deepEqual from 'deep-equal'\r\nimport qualitative from '../../utils/colors/chartPalette'\r\n\r\nconst ColorContext = createContext(null)\r\n\r\nconst getNextColor = (idx) => qualitative[idx % qualitative.length]\r\nconst getColorMapArray = (series, colorMap, groupName) =>\r\n series.map((seriesName, idx) => ({\r\n series: seriesName,\r\n color: getNextColor(\r\n colorMap[groupName] ? colorMap[groupName].length + idx : idx\r\n ),\r\n }))\r\n\r\nconst ColorContextProvider = ({ children }) => {\r\n const [colorMap, setColorMap] = useState(null)\r\n const getAssociatedColor = useCallback(\r\n (seriesName, groupKey) => {\r\n if (groupKey && colorMap && colorMap[groupKey]) {\r\n const matchingSeries = colorMap[groupKey].find(x => x.series === seriesName)\r\n if (matchingSeries) {\r\n return matchingSeries ? matchingSeries.color : null\r\n }\r\n } else {\r\n if (colorMap && colorMap[null]) {\r\n const nullSeries = colorMap[null].find(x => x.series === seriesName)\r\n return nullSeries ? nullSeries.color : null\r\n }\r\n }\r\n },\r\n [colorMap]\r\n )\r\n\r\n const updateColorMap = useCallback(\r\n (groupName, groupSeries) => {\r\n if (colorMap) {\r\n let updatedColorMap = colorMap\r\n // find the associated color map that is tied to the groupName we have\r\n const associatedColorMap =\r\n colorMap && colorMap[groupName] ? colorMap[groupName] : null\r\n if (associatedColorMap) {\r\n const associatedSeries = associatedColorMap.map(\r\n (x) => x.series\r\n )\r\n const seriesWithoutColorMaps = groupSeries.filter(\r\n (x) => !associatedSeries.includes(x)\r\n )\r\n updatedColorMap = {\r\n ...colorMap,\r\n [groupName]: [\r\n ...colorMap[groupName],\r\n ...getColorMapArray(\r\n seriesWithoutColorMaps,\r\n colorMap,\r\n groupName\r\n ),\r\n ],\r\n }\r\n } else {\r\n updatedColorMap = {\r\n ...colorMap,\r\n [groupName]: getColorMapArray(\r\n groupSeries,\r\n colorMap,\r\n groupName\r\n ),\r\n }\r\n }\r\n if (!deepEqual(updatedColorMap, colorMap)) {\r\n setColorMap(updatedColorMap)\r\n }\r\n } else {\r\n const updatedColorMap = {\r\n [groupName]: getColorMapArray(groupSeries, {}, groupName),\r\n }\r\n setColorMap(updatedColorMap)\r\n }\r\n },\r\n [colorMap, setColorMap]\r\n )\r\n\r\n return (\r\n \r\n {children}\r\n \r\n )\r\n}\r\n\r\nexport { ColorContext }\r\nexport default ColorContextProvider\r\n","import React, { useContext, useState, useEffect, useCallback, useMemo, useRef } from 'react'\r\nimport {\r\n BarChart,\r\n Bar,\r\n XAxis,\r\n YAxis,\r\n Label,\r\n CartesianGrid,\r\n Tooltip,\r\n Legend,\r\n ResponsiveContainer,\r\n Rectangle,\r\n Brush\r\n} from 'recharts'\r\nimport dayjs from 'dayjs'\r\n\r\nimport { WidgetDataContext } from '../../../wrappers/WidgetDataContext'\r\n\r\nimport { formatNumber } from '../../../../utils/numberFormatting'\r\nimport { dateToString } from '../../../../utils/dateFormatting'\r\nimport getHiddenColumns from '../../../../utils/widgets/getHiddenColumns'\r\nimport { ColorContext } from '../../../wrappers/ColorContext'\r\n\r\n\r\nconst MAX_TICKS = 150\r\n\r\nconst CustomTooltip = ({ payload }) => {\r\n const label = payload?.[0]?.payload.Date\r\n const total = payload.map((d) => d['value']).reduce((a, b) => a + b, 0)\r\n\r\n const values = payload.map((item) => ({\r\n value: item.value,\r\n color: item.fill,\r\n name: item.dataKey,\r\n percent: formatNumber((item.value / total) * 100),\r\n }))\r\n return (\r\n \r\n 10 ? 'is-size-7' : 'is-size-6'\r\n }`}\r\n >\r\n {label ? dateToString(label) : 'Null'}:{' '}\r\n {values.map((value, index) => (\r\n - \r\n \r\n {value.name}:\r\n \r\n \r\n {formatNumber(value.value)} ({value.percent}%)\r\n \r\n
\r\n ))}\r\n \r\n \r\n )\r\n}\r\n\r\nconst CustomLegend = ({\r\n payload,\r\n activeIndex,\r\n setActiveIndex,\r\n hiddenState,\r\n updateHiddenState,\r\n}) => {\r\n const highlight = (name, color) => {\r\n if (!hiddenState[name]) {\r\n color !== activeIndex ? setActiveIndex(color) : setActiveIndex(null)\r\n }\r\n }\r\n\r\n const fillColor = (name) => {\r\n const color = !hiddenState[name]\r\n ? payload.filter((item) => item.dataKey === name)[0].color\r\n : 'rgb(204, 204, 204)'\r\n return color\r\n }\r\n\r\n return (\r\n \r\n {payload.map((item, index) => (\r\n - \r\n \r\n highlight(item.value, item.color)}\r\n >\r\n {item.value}\r\n \r\n
\r\n ))}\r\n \r\n )\r\n}\r\n\r\nexport default () => {\r\n const { widgetData: widget, showLegend } = useContext(WidgetDataContext)\r\n const { updateColorMap, colorMap, getAssociatedColor } = useContext(ColorContext)\r\n const data = useMemo(() => widget ? widget.Data : null, [widget])\r\n const [activeIndex, setActiveIndex] = useState(null)\r\n const margin = useMemo(() => ({\r\n top: 20,\r\n right: 30,\r\n left: 10,\r\n bottom: 20,\r\n }), [])\r\n\r\n const hiddenColumns = useMemo(() => getHiddenColumns(widget), [widget])\r\n\r\n const sanitizedData = useMemo(() => data\r\n .filter((d) => !!d.Date) // remove data w/out dates\r\n .map((d) => ({\r\n ...d,\r\n Date: dayjs(d.Date).toDate().getTime(),\r\n })) // transform dates to MM/DD/YYYY format\r\n , [data])\r\n\r\n // get the column name key for grouping, eg in { Date: , Value: , District: }\r\n // groupKey == 'District'\r\n // also filter out hidden columns\r\n const groupKey = useMemo(() => Object.keys(sanitizedData[0])\r\n .filter(x => !hiddenColumns.includes(x))\r\n .find(\r\n (x) => !(x.includes('Date') || x.includes('Value'))\r\n ), [sanitizedData, hiddenColumns])\r\n\r\n // get a list of all unique groups that are present in the data\r\n // and sort alphabetically\r\n const uniqueGroups = useMemo(() => [\r\n ...new Set(sanitizedData.map((x) => x[groupKey])),\r\n ].sort(), [sanitizedData, groupKey])\r\n\r\n // aggregate bar chart data using unique dates. eg\r\n // [{Date: date1, District: 1, Value: 100}, {Date: date1, District: 2, Value: 200}]\r\n // turns into [{Date: date1, \"1\": 100, \"2\": 200}]\r\n const chartData = useMemo(() => [...new Set(sanitizedData.map((d) => d.Date))].map(\r\n (date) => {\r\n const dateEntry = { Date: date }\r\n return sanitizedData\r\n .filter((x) => x.Date === date) // get entries associated w/ the current date\r\n .reduce((acc, curr) => {\r\n const groupName = `${curr[groupKey]}`\r\n const value = curr['Value']\r\n return {\r\n ...acc,\r\n [groupName]: value,\r\n }\r\n }, dateEntry)\r\n }\r\n ), [sanitizedData, groupKey])\r\n\r\n // create state to track hidden prop for each individual bar series\r\n const [hiddenState, setHiddenState] = useState(\r\n uniqueGroups.reduce((acc, curr) => ({ ...acc, [curr]: false }), {})\r\n )\r\n\r\n const updateHiddenState = useCallback((e) => {\r\n setHiddenState({\r\n ...hiddenState,\r\n [e.dataKey]: !hiddenState[e.dataKey],\r\n })\r\n }, [hiddenState, setHiddenState])\r\n\r\n useEffect(() => {\r\n updateColorMap(groupKey, uniqueGroups)\r\n }, [uniqueGroups, groupKey])\r\n\r\n useEffect(() => {\r\n if (showLegend === 'all' && Object.values(hiddenState).includes(true)) {\r\n setHiddenState(\r\n uniqueGroups.reduce(\r\n (acc, curr) => ({ ...acc, [curr]: false }),\r\n {}\r\n )\r\n )\r\n }\r\n if (\r\n showLegend === 'none' &&\r\n Object.values(hiddenState).includes(false)\r\n ) {\r\n setHiddenState(\r\n uniqueGroups.reduce(\r\n (acc, curr) => ({ ...acc, [curr]: true }),\r\n {}\r\n )\r\n )\r\n }\r\n }, [showLegend, uniqueGroups])\r\n\r\n const containerRef = useRef({current: {current: {offsetWidth: null}}})\r\n\r\n const [containerWidth, setContainerWidth] = useState(0)\r\n\r\n useEffect(() => {\r\n if (containerRef && containerRef.current && containerRef.current.current) {\r\n const guessWidth = containerRef.current.current.offsetWidth - margin.left - margin.right - 40\r\n setContainerWidth(guessWidth)\r\n }\r\n }, [margin.left, margin.right, containerRef.current.current])\r\n\r\n // create state to track bar width for x axis padding\r\n const padding = useMemo(() => {\r\n const minDate = dayjs(Math.min(...chartData.map(x => x.Date)))\r\n const maxDate = dayjs(Math.max(...chartData.map(x => x.Date)))\r\n const numMonths = maxDate.diff(minDate, \"month\") + 1\r\n const p = (containerWidth / numMonths ) / 2\r\n return {\r\n left: p,\r\n right: p\r\n }\r\n }, [chartData, containerWidth])\r\n\r\n // tie each groupname to a groupid, if exists. otherwise,\r\n // default to \"shared\"\r\n const stacks = useMemo(() => uniqueGroups.reduce((acc, groupName) => {\r\n const exampleRow = data.find(x => x[groupKey] === groupName)\r\n let stackId = 'shared' // default stack id\r\n\r\n // get the stack id of the group in the query, if it exists\r\n if (exampleRow && typeof exampleRow.GroupID !== 'undefined') {\r\n stackId = exampleRow.GroupID\r\n }\r\n return {\r\n ...acc,\r\n [groupName]: stackId\r\n }\r\n }, {}), [uniqueGroups, data, groupKey])\r\n \r\n const RevealBarProps = useCallback((props) => {\r\n return props.fill === activeIndex ? (\r\n \r\n ) : (\r\n \r\n )\r\n }, [activeIndex])\r\n\r\n const colors = useMemo(\r\n () =>\r\n uniqueGroups.reduce((acc, curr, idx) => {\r\n return {\r\n ...acc,\r\n [curr]: getAssociatedColor(curr, groupKey),\r\n }\r\n }, {}),\r\n [uniqueGroups, groupKey, colorMap]\r\n )\r\n\r\n // generate a bar component for each group\r\n const Bars = useMemo(() => uniqueGroups.map((groupName, idx) => (\r\n \r\n )), [hiddenState, activeIndex, uniqueGroups, colors, stacks])\r\n\r\n return (\r\n \r\n \r\n \r\n dateToString(unixTime)}\r\n padding={padding}\r\n >\r\n \r\n \r\n formatNumber(value, 'compact')}\r\n >\r\n \r\n \r\n \r\n }\r\n />\r\n \r\n }\r\n verticalAlign=\"top\"\r\n wrapperStyle={{\r\n display: showLegend ? '' : 'none',\r\n top: 1,\r\n left: 0,\r\n overflowX: 'hidden',\r\n overflowY: 'scroll',\r\n alignItems: 'center',\r\n width: '100%',\r\n }}\r\n />\r\n {Bars}\r\n {chartData && chartData.length > MAX_TICKS && dateToString(unixTime)}/> }\r\n \r\n \r\n )\r\n}\r\n","import React, { useContext, useEffect, useState, useMemo } from 'react'\r\nimport {\r\n LineChart,\r\n Line,\r\n XAxis,\r\n YAxis,\r\n Label,\r\n CartesianGrid,\r\n Tooltip,\r\n Legend,\r\n ResponsiveContainer,\r\n ReferenceLine,\r\n Brush\r\n} from 'recharts'\r\nimport dayjs from 'dayjs'\r\n\r\nimport { WidgetDataContext } from '../../../wrappers/WidgetDataContext'\r\n\r\nimport { formatNumber } from '../../../../utils/numberFormatting'\r\nimport { dateToString } from '../../../../utils/dateFormatting'\r\nimport getHiddenColumns from '../../../../utils/widgets/getHiddenColumns'\r\nimport { ColorContext } from '../../../wrappers/ColorContext'\r\n\r\nconst MAX_TICKS = 150\r\n\r\nconst CustomLegend = ({ legendPayload, bold, setBold, updateHiddenState }) => {\r\n return (\r\n \r\n {legendPayload.map((item, index) => (\r\n - \r\n \r\n \r\n bold && bold === item.value\r\n ? setBold(null)\r\n : setBold(item.value)\r\n }\r\n >\r\n {item.value}\r\n \r\n
\r\n ))}\r\n \r\n )\r\n}\r\n\r\nconst getSeriesFromData = (data, hiddenColumns) => {\r\n // if there is not a 'Value' column, then the series\r\n // values are stored in a column for each series name\r\n const seriesAreDistinctColumns =\r\n typeof Object.keys(data[0]).find((x) => x.includes('Value')) ===\r\n 'undefined'\r\n\r\n if (seriesAreDistinctColumns) {\r\n // get the line chart series names, eg in { Date: , Series A: , Series B: }\r\n // => groupKeys == ['Series A', 'Series B']\r\n \r\n // filter out Date and any hiddenColumns\r\n const groupKeys = data.reduce(\r\n (acc, curr) => [\r\n ...acc,\r\n ...Object.keys(curr).filter((x) => !x.includes('Date')).filter(x => !hiddenColumns.includes(x)),\r\n ],\r\n []\r\n )\r\n\r\n // get a list of all unique groups that are present in the data\r\n // => ['Series A', 'Series B']\r\n const uniqueGroups = [...new Set([...groupKeys])].sort()\r\n\r\n // return data + group names\r\n return [data, uniqueGroups, null]\r\n } else {\r\n // otherwise, the group key is just the column that is not Value or Date\r\n // eg data[0] = {'Value': 0, 'Date': '12/12/12', 'Form Type': 'Wow'}\r\n // => groupKey = 'Form Type'\r\n const groupKey = Object.keys(data[0]).find(\r\n (x) => !(x.includes('Date') || x.includes('Value')) && !(hiddenColumns.includes(x))\r\n )\r\n\r\n // get the set of all unique values in the column specified by groupKey\r\n const uniqueGroups = [...new Set(data.map((x) => x[groupKey]))].sort()\r\n\r\n // aggregate line chart data using unique dates. eg\r\n // [{Date: date1, District: 1, Value: 100}, {Date: date1, District: 2, Value: 200}]\r\n // turns into [{Date: date1, \"1\": 100, \"2\": 200}]\r\n const chartData = [...new Set(data.map((d) => d.Date))].sort().map((date) => {\r\n const dateEntry = { Date: date }\r\n return data\r\n .filter((x) => x.Date === date) // get entries associated w/ the current date\r\n .reduce((acc, curr) => {\r\n const groupName = curr[groupKey]\r\n const value = curr['Value']\r\n return {\r\n ...acc,\r\n [groupName]: value,\r\n }\r\n }, dateEntry)\r\n }).sort((a, b) => a.Date - b.Date)\r\n\r\n return [chartData, uniqueGroups, groupKey]\r\n }\r\n}\r\n\r\nconst getReferenceLineData = options => {\r\n if (options) {\r\n try {\r\n const config = JSON.parse(options)\r\n const referenceData = config.reference ? config.reference : null\r\n return referenceData\r\n } catch (e) {\r\n }\r\n }\r\n}\r\nexport default () => {\r\n const { widgetData: widget, showLegend } = useContext(WidgetDataContext)\r\n const { colorMap, updateColorMap, getAssociatedColor } = useContext(ColorContext)\r\n const data = useMemo(() => widget ? widget.Data : null, [widget])\r\n const options = useMemo(() => widget ? widget.WidgetOptions : null, [widget])\r\n const [legend, setLegend] = useState([])\r\n\r\n const referenceData = useMemo(() => getReferenceLineData(options), [options])\r\n const preparedData = useMemo(() => data\r\n .filter((d) => !!d.Date) // remove data w/out dates\r\n .map((d) => ({\r\n ...d,\r\n Date: dayjs(d.Date).toDate().getTime(),\r\n })) // transform dates to MM/DD/YYYY format\r\n , [data])\r\n\r\n const hiddenColumns = useMemo(() => getHiddenColumns(widget), [widget])\r\n const [chartData, uniqueGroups, groupKey] = useMemo(() => getSeriesFromData(preparedData, hiddenColumns), [preparedData, hiddenColumns])\r\n\r\n // create state to track hidden prop for each individual line\r\n const [hiddenState, setHiddenState] = useState(\r\n uniqueGroups.reduce((acc, curr) => ({ ...acc, [curr]: false }), {})\r\n )\r\n const [bold, setBold] = useState(null)\r\n\r\n useEffect(() => {\r\n updateColorMap(groupKey, uniqueGroups)\r\n }, [uniqueGroups, groupKey])\r\n\r\n \r\n useEffect(() => {\r\n setLegend(\r\n uniqueGroups.map((groupName, idx) => ({\r\n color: !hiddenState[groupName]\r\n ? getAssociatedColor(groupName, groupKey)\r\n : 'rgb(204, 204, 204)',\r\n type: 'line',\r\n dataKey: groupName,\r\n value: groupName,\r\n hide: hiddenState[groupName] ? true : false,\r\n })\r\n ))\r\n }, [hiddenState, colorMap])\r\n\r\n useEffect(() => {\r\n if (showLegend === 'all' && Object.values(hiddenState).includes(true)) {\r\n setHiddenState(\r\n uniqueGroups.reduce(\r\n (acc, curr) => ({ ...acc, [curr]: false }),\r\n {}\r\n )\r\n )\r\n }\r\n if (\r\n showLegend === 'none' &&\r\n Object.values(hiddenState).includes(false)\r\n ) {\r\n setHiddenState(\r\n uniqueGroups.reduce(\r\n (acc, curr) => ({ ...acc, [curr]: true }),\r\n {}\r\n )\r\n )\r\n }\r\n }, [showLegend])\r\n\r\n const colors = useMemo(\r\n () =>\r\n uniqueGroups.reduce((acc, curr, idx) => {\r\n return {\r\n ...acc,\r\n [curr]: getAssociatedColor(curr, groupKey),\r\n }\r\n }, {}),\r\n [uniqueGroups, groupKey, colorMap]\r\n )\r\n\r\n const updateHiddenState = (e) => {\r\n setHiddenState((previousState) => ({\r\n ...previousState,\r\n [e.dataKey]: !previousState[e.dataKey],\r\n }))\r\n }\r\n\r\n // const delay = 500\r\n // let click = 0\r\n\r\n // const handleClicks = (e) => {\r\n // console.log(e, \"event\")\r\n // click > 2 ? (click = 0) : click++\r\n // setTimeout(() => {\r\n // if (click === 1) {\r\n // updateHiddenState(e)\r\n // click = 0\r\n // }\r\n // if (click === 2) {\r\n // bold && bold === e.dataKey ? setBold(null) : setBold(e.dataKey)\r\n // click = 0\r\n // }\r\n // }, delay)\r\n // }\r\n\r\n const dataMax = useMemo(() => Math.max(\r\n ...chartData.map((entry) =>\r\n Math.max(\r\n ...Object.keys(entry).map((key) =>\r\n key !== 'Date' && !hiddenState[key] ? entry[key] : 0\r\n )\r\n )\r\n )\r\n ), [chartData, hiddenState])\r\n\r\n const dataMin = useMemo(() => Math.min(\r\n ...chartData.map((entry) =>\r\n Math.min(\r\n ...Object.keys(entry).map((key) =>\r\n key !== 'Date' && !hiddenState[key] ? entry[key] : Infinity\r\n )\r\n )\r\n )\r\n ), [chartData, hiddenState])\r\n\r\n // generate a line component for each\r\n // individual line\r\n const Lines = useMemo(() => uniqueGroups.map((groupName, idx) => (\r\n \r\n )), [uniqueGroups, hiddenState, colors, bold])\r\n\r\n const References = useMemo(() => uniqueGroups.map((groupName, idx) => {\r\n const hasReference = referenceData ? !!referenceData[groupName] : false\r\n if (!hasReference) {\r\n return null\r\n }\r\n return (\r\n \r\n )\r\n }\r\n ), [uniqueGroups, referenceData, hiddenState, colors, bold])\r\n\r\n return (\r\n \r\n \r\n \r\n }\r\n verticalAlign=\"top\"\r\n wrapperStyle={{\r\n display: showLegend ? Object.keys(legend).length ? 'block' : 'none' : 'none',\r\n top: 1,\r\n left: 0,\r\n overflowX: 'hidden',\r\n overflowY: 'scroll',\r\n alignItems: 'center',\r\n width: '100%',\r\n }}\r\n />\r\n \r\n dateToString(unixTime)}\r\n >\r\n \r\n \r\n formatNumber(value, 'compact')}\r\n domain={[dataMin, dataMax]}\r\n allowDataOverflow={true}\r\n >\r\n \r\n \r\n formatNumber(value)}\r\n labelFormatter={(label) => dateToString(label)}\r\n isAnimationActive={false}\r\n wrapperStyle={\r\n Object.values(hiddenState).filter((x) => x === false)\r\n .length > 10\r\n ? { fontSize: '.75rem' }\r\n : { fontSize: '1rem' }\r\n }\r\n itemStyle={{ padding: '0px' }}\r\n />\r\n {Lines}\r\n {References}\r\n {chartData && chartData.length > MAX_TICKS && dateToString(unixTime)}/> }\r\n \r\n \r\n )\r\n}\r\n","import React, { useState, useEffect, useRef } from 'react'\r\n\r\nconst getTooltipPositionProps = (pixel, mapWidth, bottomPixel) => {\r\n // horizontal edge detection\r\n const tooltipWidth = 200\r\n const pixelX = pixel[0]\r\n const tooltipRightPxValue = pixelX + tooltipWidth\r\n const exceedsRightEdge = tooltipRightPxValue > mapWidth\r\n const horizontalProps = exceedsRightEdge\r\n ? { right: mapWidth - pixel[0] + 'px' }\r\n : { left: pixel[0] + 'px' }\r\n\r\n // vertical edge detection\r\n const tooltipHeight = 100\r\n const tooltipYTopOffset = 50\r\n const tooltipYBottomOffset = tooltipHeight - tooltipYTopOffset\r\n const pixelY = pixel[1]\r\n \r\n // top edge\r\n const topDelta = pixelY - tooltipYTopOffset\r\n const exceedsTopEdge = topDelta < 0\r\n\r\n // bottom edge\r\n const bottomDelta = pixelY - 10 + tooltipYBottomOffset - bottomPixel\r\n const exceedsBottomEdge = bottomDelta > 0\r\n \r\n const verticalProps = exceedsTopEdge\r\n ? { top: pixel[1] - (tooltipYTopOffset + topDelta) + 10 + 'px' }\r\n : exceedsBottomEdge ? \r\n { top: pixel[1] - tooltipYTopOffset - bottomDelta } \r\n : { top: pixel[1] - tooltipYTopOffset + 'px' }\r\n\r\n const styleProps = {\r\n ...horizontalProps,\r\n ...verticalProps,\r\n }\r\n return styleProps\r\n}\r\n\r\nexport default ({ left, top, right, bottom, feature, tooltipData }) => {\r\n const tooltipRef = useRef(null)\r\n const featureRef = useRef(feature)\r\n // display logic \r\n const [display, setDisplay] = useState('none')\r\n\r\n // when a new feature is highlighted,\r\n // set display block\r\n useEffect(() => {\r\n if (feature) {\r\n setDisplay('block')\r\n featureRef.current = feature\r\n }\r\n else {\r\n setDisplay('none')\r\n featureRef.current = null\r\n }\r\n }, [feature])\r\n\r\n // when the tooltip loads,\r\n // setup mouseenter and mouseleave\r\n // functions to ensure mouseover\r\n // prevents tooltip from hiding\r\n useEffect(() => {\r\n if (tooltipRef.current) {\r\n tooltipRef.current.onmouseenter = () => setDisplay('block')\r\n tooltipRef.current.onmouseleave = () => { \r\n if (!featureRef.current) {\r\n setDisplay('none')\r\n }}\r\n }\r\n }, [featureRef])\r\n\r\n if (!tooltipData || !tooltipData.length) {\r\n return null\r\n }\r\n\r\n return (\r\n \r\n \r\n {tooltipData.filter(x => x.field !== \"PKey\").map((d, idx) => (\r\n \r\n \r\n {`${d.field} :`}\r\n \r\n \r\n {`${d.value}`}\r\n \r\n \r\n ))}\r\n \r\n \r\n )\r\n}\r\n\r\nexport { getTooltipPositionProps }","import React from 'react'\r\nimport ReactDOM from 'react-dom'\r\nimport VectorLayer from 'ol/layer/Vector'\r\nimport Vector from 'ol/source/Vector'\r\nimport { Select } from 'ol/interaction'\r\nimport { ZoomToExtent } from 'ol/control'\r\nimport Polygon from 'ol/geom/Polygon'\r\n\r\nimport { FaGlobe } from 'react-icons/fa'\r\nimport { IconTooltip } from '../../Tooltip'\r\nimport { getTooltipPositionProps } from './Tooltip'\r\n\r\nconst createLabel = (element, name, tooltip) => {\r\n const div = document.createElement('div')\r\n div.setAttribute('class', 'zoomToExtentDiv')\r\n if (name && tooltip) {\r\n div.setAttribute('data-for', name)\r\n div.setAttribute('data-tip', tooltip)\r\n }\r\n ReactDOM.render(element, div)\r\n return div\r\n}\r\n\r\nconst getLayers = () => {\r\n return {\r\n polygon: new VectorLayer({\r\n source: new Vector(),\r\n title: 'Polygon',\r\n visible: false,\r\n }),\r\n }\r\n}\r\n\r\nconst zoomControls = (extent) => {\r\n return [\r\n new ZoomToExtent({\r\n extent,\r\n label: createLabel(\r\n <>\r\n \r\n \r\n >,\r\n 'zoom-to-extent',\r\n 'Zoom to Full Extent'\r\n ),\r\n tipLabel: '',\r\n className: 'ol-zoom-extent zoomToExtentWrapper',\r\n })\r\n ]\r\n}\r\n\r\nconst getSelectControl = () => {\r\n const control = new Select()\r\n\r\n const on = (source, sharedPageKey, setSharedPageId, layer) => {\r\n const mouseDownKey = control.on('select', function(e) {\r\n var tolerance = 10\r\n // if the layer is not visible, pass this selection over\r\n if (!layer.getVisible()) {\r\n return\r\n }\r\n \r\n if (e.selected.length) {\r\n const map = e.mapBrowserEvent.map\r\n const coordinates = e.selected[0].getGeometry().flatCoordinates\r\n const targetPixel = map.getPixelFromCoordinate(coordinates)\r\n\r\n const pixel1 = [\r\n targetPixel[0] - tolerance / 2,\r\n targetPixel[1] - tolerance / 2,\r\n ]\r\n const pixel2 = [\r\n targetPixel[0] - tolerance / 2,\r\n targetPixel[1] + tolerance / 2,\r\n ]\r\n const pixel3 = [\r\n targetPixel[0] + tolerance / 2,\r\n targetPixel[1] + tolerance / 2,\r\n ]\r\n const pixel4 = [\r\n targetPixel[0] + tolerance / 2,\r\n targetPixel[1] - tolerance / 2,\r\n ]\r\n\r\n const point1 = map.getCoordinateFromPixel(pixel1)\r\n const point2 = map.getCoordinateFromPixel(pixel2)\r\n const point3 = map.getCoordinateFromPixel(pixel3)\r\n const point4 = map.getCoordinateFromPixel(pixel4)\r\n\r\n const polygon = new Polygon([[point1, point2, point3, point4]])\r\n const newFilterData = source\r\n .getFeatures()\r\n .filter(feature => \r\n polygon.intersectsExtent(\r\n feature.getGeometry().getExtent()\r\n )\r\n )\r\n\r\n if (newFilterData && newFilterData.length && sharedPageKey) {\r\n const sharedPageId = newFilterData[0].get(sharedPageKey)\r\n if (sharedPageId) {\r\n setSharedPageId(sharedPageId)\r\n }\r\n e.target.getFeatures().clear()\r\n }\r\n }\r\n })\r\n const keys = [mouseDownKey]\r\n return keys\r\n }\r\n\r\n return {\r\n on,\r\n control\r\n }\r\n}\r\n\r\n\r\nconst displayPopup = async (e, map, setTooltipState) => {\r\n const pixel = map.getEventPixel(e.originalEvent)\r\n const mapWidth = e.originalEvent.currentTarget.scrollWidth\r\n const bottomPixel = e.originalEvent.currentTarget.clientHeight \r\n const styleProps = getTooltipPositionProps(pixel, mapWidth, bottomPixel)\r\n\r\n // unhighlight the previous feature\r\n const previousFeature = map.get('currentFeature')\r\n if (previousFeature) {\r\n previousFeature.set('highlighted', 0)\r\n }\r\n\r\n const feature = map.forEachFeatureAtPixel(pixel, function(feature, layer) {\r\n return feature\r\n })\r\n\r\n if (feature) {\r\n // highlight the new feature\r\n feature.set('highlighted', 1)\r\n map.set('currentFeature', feature)\r\n setTooltipState({\r\n ...styleProps,\r\n feature\r\n })\r\n } else {\r\n setTooltipState(prevState => ({\r\n ...prevState,\r\n feature: null,\r\n }))\r\n }\r\n}\r\nexport { getLayers, zoomControls, displayPopup, getSelectControl }\r\n","import { Fill, Stroke, Style } from 'ol/style'\r\nimport { heatMap } from '../../../../../utils/colors/chartPalette'\r\n\r\nconst getDataIntervalFromRange = (range, numBins) => {\r\n const [dataMin, dataMax] = range\r\n const diff = dataMax - dataMin\r\n const interval = diff / numBins\r\n return interval\r\n}\r\n\r\nconst getHeatMapColorKeyFromRange = (value, range) => {\r\n // if value is 0,\r\n // return undefined to give the transparent\r\n // color in themeMapStyleFunction\r\n if (value === 0) {\r\n return undefined\r\n }\r\n const [dataMin] = range\r\n const interval = getDataIntervalFromRange(range, heatMap.length)\r\n const colorKey = heatMap\r\n .map((_, idx) => dataMin + (idx + 1) * interval)\r\n .findIndex(x => x + 1> value)\r\n return colorKey\r\n}\r\n\r\nconst getLegendDataFromRange = range => {\r\n const [dataMin] = range\r\n const interval = getDataIntervalFromRange(range, heatMap.length)\r\n if (interval === NaN || interval === 0 ) {\r\n return [{\r\n color: heatMap[0],\r\n dataMin: 0,\r\n dataMax: 0\r\n }]\r\n }\r\n \r\n return heatMap.map((_, idx) => {\r\n return {\r\n color: heatMap[idx],\r\n dataMin: Math.ceil(dataMin + (idx * interval)),\r\n dataMax: Math.ceil(dataMin + ((idx + 1) * interval))\r\n }\r\n })\r\n}\r\n\r\nconst heatMapStyles = heatMap.map((_, idx) => idx).reduce((acc, current) => {\r\n const color = heatMap[current]\r\n return {\r\n ...acc,\r\n [current]: new Style({\r\n fill: new Fill({\r\n color,\r\n }),\r\n stroke: new Stroke({\r\n color: 'black',\r\n width: 1\r\n }),\r\n })\r\n }\r\n}, {})\r\n\r\nconst defaultCountyStyle = new Style({\r\n fill: new Fill({\r\n color: 'rgba(255, 255, 255, 0)'\r\n }),\r\n stroke: new Stroke({\r\n color: 'black',\r\n width: 1\r\n })\r\n})\r\n\r\nconst themeMapStyleFunction = (feature) => {\r\n const colorKey = feature.get('ColorKey')\r\n if (typeof colorKey !== 'undefined') {\r\n return heatMapStyles[feature.get('ColorKey')]\r\n } else {\r\n return defaultCountyStyle\r\n }\r\n}\r\n\r\nexport { themeMapStyleFunction, getHeatMapColorKeyFromRange, getLegendDataFromRange }","import React, {useContext} from 'react'\r\nimport { getLegendDataFromRange } from './styleFunctions'\r\nimport { formatNumber } from '../../../../../utils/numberFormatting'\r\nimport { WidgetDataContext } from '../../../../wrappers/WidgetDataContext'\r\n\r\n\r\nexport default ({dataRange}) => {\r\n const { showLegend } = useContext(WidgetDataContext)\r\n const legendData = getLegendDataFromRange(dataRange)\r\n return (\r\n \r\n {legendData.map((legendItem, idx) => (\r\n \r\n \r\n \r\n \r\n \r\n {`${formatNumber(legendItem.dataMin, 'compact')} - ${formatNumber(legendItem.dataMax, 'compact')}`}\r\n \r\n \r\n ))}\r\n \r\n )\r\n}\r\n","import React, { createContext, useState, useEffect, useContext } from 'react'\r\nimport GeoJSON from 'ol/format/GeoJSON'\r\n\r\nimport toast from '../../../Toast'\r\nimport { WidgetDataContext } from '../../../../wrappers/WidgetDataContext'\r\nimport { CurrentDashboardContext } from '../../../../wrappers/CurrentDashboardContext'\r\nimport { APIRequestContext } from '../../../../wrappers/APIRequestContext'\r\nimport withConfig from '../../../../wrappers/withConfig'\r\n\r\nconst FeatureContext = createContext(null)\r\n\r\nconst getFeatureFilepathAssociatedWithGroupBy = (widget, activeGroupBy) => {\r\n const groupByOptions = widget.GroupBy.split('|')\r\n const filenameOptions = widget.FeatureFiles.split('|')\r\n const activeIndex = groupByOptions.findIndex(\r\n (groupBy) => groupBy === activeGroupBy\r\n )\r\n const activeFilename = filenameOptions[activeIndex]\r\n return activeFilename\r\n}\r\n\r\nconst FeatureContextProvider = ({ config, children }) => {\r\n // get the map widget data\r\n const { widgetData: widget, activeGroupBy } = useContext(WidgetDataContext)\r\n const { dashboardName } = useContext(CurrentDashboardContext)\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const { MAP: mapConfig } = config\r\n const { SOURCE_PROJECTION } = mapConfig[dashboardName]\r\n const targetProjection = 'EPSG:4326'\r\n const sourceProjection = SOURCE_PROJECTION ? SOURCE_PROJECTION : targetProjection\r\n const [features, setFeatures] = useState([])\r\n\r\n useEffect(() => {\r\n // get the file name we are going to fetch from\r\n // the /public directory\r\n const featureFilename = getFeatureFilepathAssociatedWithGroupBy(\r\n widget,\r\n activeGroupBy\r\n )\r\n\r\n // fetch the file contents\r\n authenticatedFetch(`/${featureFilename}`)\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return response.json()\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .then((response) => {\r\n const f = new GeoJSON({\r\n dataProjection: sourceProjection,\r\n featureProjection: targetProjection\r\n }).readFeatures(response)\r\n setFeatures(f)\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message: `Map Feature File: No file found at ${featureFilename}`,\r\n })\r\n })\r\n }, [activeGroupBy, widget])\r\n\r\n return (\r\n \r\n {children}\r\n \r\n )\r\n}\r\n\r\nexport { FeatureContext }\r\nexport default withConfig(FeatureContextProvider)\r\n","import React, { useEffect, useRef, useContext, useState } from 'react'\r\nimport Map from 'ol/Map'\r\nimport { defaults as defaultInteractions } from 'ol/interaction'\r\nimport View from 'ol/View'\r\nimport TileLayer from 'ol/layer/Tile'\r\nimport OSM from 'ol/source/OSM'\r\nimport VectorSource from 'ol/source/Vector'\r\nimport VectorLayer from 'ol/layer/Vector'\r\nimport { unByKey } from 'ol/Observable'\r\n\r\nimport withConfig from '../../../../wrappers/withConfig'\r\nimport { WidgetDataContext } from '../../../../wrappers/WidgetDataContext'\r\n\r\nimport {\r\n displayPopup, zoomControls,\r\n} from '../mapConfig'\r\nimport {\r\n themeMapStyleFunction,\r\n getHeatMapColorKeyFromRange,\r\n} from './styleFunctions'\r\nimport MapTooltip from '../Tooltip'\r\nimport MapLegend from './MapLegend'\r\nimport { FeatureContext } from './FeatureContext'\r\nimport { CurrentDashboardContext } from '../../../../wrappers/CurrentDashboardContext'\r\nimport { formatNumber } from '../../../../../utils/numberFormatting'\r\nimport { ZoomToExtent } from 'ol/control'\r\n\r\nconst getTooltipDataFromFeature = (tooltipState, widget, activeGroupBy) => {\r\n if (tooltipState) {\r\n const { feature } = tooltipState\r\n return [{\r\n field: activeGroupBy,\r\n value: feature && feature.get('FeatureName')\r\n }, {\r\n field: widget ? widget.ValueLabel : null,\r\n value: feature && formatNumber(feature.get('Value'))\r\n }]\r\n }\r\n return []\r\n}\r\n\r\nconst ThemeMap = ({ config }) => {\r\n // load context\r\n const {\r\n loading,\r\n widgetData: widget,\r\n activeGroupBy,\r\n } = useContext(WidgetDataContext)\r\n const { dashboardName, expandedWidgetId } = useContext(CurrentDashboardContext)\r\n const { features } = useContext(FeatureContext)\r\n\r\n // get map config associated w/ the dashboard name\r\n const { MAP: mapConfig } = config\r\n const { INITIAL_ZOOM_LEVEL, CENTER_LAT_LNG } = mapConfig[dashboardName]\r\n\r\n\r\n // create internal state + refs\r\n const mapRef = useRef(null)\r\n const [map, setMap] = useState(null)\r\n const [popupKey, setPopupKey] = useState(null)\r\n const [tooltipState, setTooltipState] = useState(null)\r\n const [dataRange, setDataRange] = useState([])\r\n\r\n // on load, set up the map\r\n useEffect(() => {\r\n const map = new Map({\r\n target: mapRef.current,\r\n // controls: zoomControls(ZOOM_TO_EXTENT),\r\n controls: zoomControls(null),\r\n interactions: defaultInteractions({ mouseWheelZoom: false }),\r\n layers: [\r\n new TileLayer({\r\n source: new OSM(),\r\n }),\r\n ],\r\n view: new View({\r\n projection: 'EPSG:4326',\r\n center: CENTER_LAT_LNG,\r\n zoom: INITIAL_ZOOM_LEVEL,\r\n }),\r\n })\r\n\r\n // set initial extent to that of the view\r\n const initialExtent = map.getView().calculateExtent()\r\n map.getControls().getArray().find(x => x instanceof ZoomToExtent).extent = initialExtent\r\n\r\n setMap(map)\r\n }, [ CENTER_LAT_LNG, INITIAL_ZOOM_LEVEL, /*ZOOM_TO_EXTENT*/])\r\n\r\n // update size on load finish + expanded change\r\n useEffect(() => {\r\n if (map && mapRef) {\r\n map.setSize(mapRef.current.clientWidth, mapRef.current.clientHeight)\r\n map.updateSize()\r\n }\r\n }, [loading, map, mapRef, expandedWidgetId])\r\n\r\n // draw the counties vector layer,\r\n // and style according to the data\r\n // for each county\r\n useEffect(() => {\r\n if (widget && widget.Data && map && activeGroupBy && features) {\r\n const data = widget.Data.filter((x) => !!x[activeGroupBy]) // get data + filter out null counties\r\n const values = data.map((x) => x.Value)\r\n const range = [Math.min(...values), Math.max(...values)]\r\n setDataRange(range) // set data range for use by the map legend\r\n features.forEach((feature) => {\r\n const featureName = feature.get('FeatureName')\r\n const featureData = data.find(\r\n (d) =>\r\n `${d[activeGroupBy]}`.toLowerCase() === `${featureName}`.toLowerCase()\r\n || Number(d[activeGroupBy]) === Number(featureName)\r\n )\r\n if (featureData) {\r\n feature.set('Value', featureData.Value)\r\n } else {\r\n feature.set('Value', 0)\r\n }\r\n const colorKey = getHeatMapColorKeyFromRange(\r\n feature.get('Value'),\r\n range\r\n )\r\n feature.set('ColorKey', colorKey)\r\n })\r\n const vectorSource = new VectorSource({\r\n features: features,\r\n })\r\n const vectorLayer = new VectorLayer({\r\n source: vectorSource,\r\n style: themeMapStyleFunction,\r\n })\r\n map.addLayer(vectorLayer)\r\n }\r\n }, [widget, map, activeGroupBy, features])\r\n\r\n // add tooltip event listeners when map loads\r\n useEffect(() => {\r\n if (map) {\r\n if (!popupKey) {\r\n const key = map.on('pointermove', (e) =>\r\n displayPopup(e, map, setTooltipState)\r\n )\r\n setPopupKey(key)\r\n }\r\n } else {\r\n if (popupKey) {\r\n unByKey(popupKey)\r\n setPopupKey(null)\r\n }\r\n }\r\n }, [map, popupKey])\r\n\r\n if (loading) {\r\n return null\r\n }\r\n\r\n return (\r\n \r\n )\r\n}\r\n\r\nexport default withConfig(ThemeMap)","import React from 'react'\r\n\r\nimport Map from './Map'\r\nimport FeatureContextProvider from './FeatureContext'\r\n\r\nexport default () => {\r\n return (\r\n <>\r\n \r\n \r\n \r\n >\r\n )\r\n}","import { Style, Fill, Stroke, Circle} from 'ol/style'\r\n\r\nconst defaultStyle = {\r\n fillColor: 'rgba(0, 0, 255, 0.1)',\r\n strokeColor: 'rgba(0, 0, 255, 0.5)',\r\n strokeWidth: 2\r\n}\r\n\r\nconst getLegendDataFromLayers = layers => {\r\n return layers.map(layer => {\r\n const layerFeatures = layer.getSource().getFeatures()\r\n const geometryType = (layerFeatures && layerFeatures.length) ? layerFeatures[0].getGeometry().getType() : null\r\n const style = JSON.parse(layer.get('style'))\r\n const layerName = layer.get('name')\r\n return {\r\n layerName,\r\n geometryType,\r\n fillColor: style.fillColor ? style.fillColor : null,\r\n strokeColor: style.strokeColor ? style.strokeColor : null,\r\n visible: true\r\n }\r\n })\r\n}\r\n\r\nconst getFeatureValue = (feature, fieldName) => feature.get(fieldName) ? feature.get(fieldName) : defaultStyle[fieldName]\r\n\r\nconst selectedStyle = new Style({\r\n image: new Circle({\r\n radius: 6,\r\n fill: new Fill({\r\n color: '#ADD8E6'\r\n }),\r\n stroke: new Stroke({\r\n color: 'white'\r\n })\r\n }),\r\n zIndex: 4\r\n})\r\n\r\nconst renderStyles = {}\r\n\r\nconst getOrCreateRenderStyles = (feature, layerName, renderStyles) => {\r\n if (typeof renderStyles[layerName] === 'undefined') {\r\n const featureType = feature.getGeometry().getType()\r\n const fillColor = getFeatureValue(feature, 'fillColor')\r\n const strokeColor = getFeatureValue(feature, 'strokeColor')\r\n const strokeWidth = getFeatureValue(feature, 'strokeWidth')\r\n switch (featureType) {\r\n case \"Polygon\":\r\n renderStyles[layerName] = new Style({\r\n fill: new Fill({\r\n color: fillColor\r\n }),\r\n stroke: new Stroke({\r\n color: strokeColor,\r\n width: strokeWidth\r\n })\r\n })\r\n break\r\n case \"Point\":\r\n renderStyles[layerName] = new Style({\r\n image: new Circle({\r\n radius: 3,\r\n fill: new Fill({\r\n color: fillColor\r\n }),\r\n stroke: new Stroke({\r\n color: strokeColor, \r\n width: strokeWidth\r\n })\r\n })\r\n })\r\n break\r\n default:\r\n renderStyles[layerName] = undefined\r\n }\r\n }\r\n return renderStyles[layerName]\r\n \r\n}\r\n\r\nconst featureMapStyleFunction = (layerName) => (feature) => {\r\n const selected = feature.get('selected')\r\n\r\n if (selected) {\r\n return selectedStyle\r\n }\r\n const renderStyle = getOrCreateRenderStyles(feature, layerName, renderStyles)\r\n return renderStyle\r\n}\r\n\r\nexport { featureMapStyleFunction, getLegendDataFromLayers }","import WKT from 'ol/format/WKT'\r\nimport { Vector as VectorLayer} from 'ol/layer'\r\nimport {Vector as VectorSource} from 'ol/source'\r\nimport { getArea } from 'ol/extent'\r\n\r\nimport { featureMapStyleFunction } from './styleFunctions'\r\n\r\nconst format = new WKT()\r\n\r\nconst getMinimumLayerExtent = layers => {\r\n let fit = null\r\n layers.map(layer => {\r\n const layerExtent = layer.getSource().getExtent()\r\n if (fit) {\r\n // // if the layerExtent is bigger than the\r\n // // existing fit, make it the new fit target\r\n // if (getArea(fit) < getArea(layerExtent)) {\r\n // fit = layerExtent\r\n // }\r\n\r\n // more sophisticated version: compare all elements of the fit and take the max / min of each\r\n const minMaxExtent = {\r\n minLat: layerExtent[0] < fit[0] ? layerExtent[0] : fit[0],\r\n minLon: layerExtent[1] < fit[1] ? layerExtent[1] : fit[1],\r\n maxLat: layerExtent[2] > fit[2] ? layerExtent[2] : fit[2],\r\n maxLon: layerExtent[3] > fit[3] ? layerExtent[3] : fit[3]\r\n }\r\n fit = [minMaxExtent.minLat, minMaxExtent.minLon, minMaxExtent.maxLat, minMaxExtent.maxLon]\r\n } else {\r\n fit = layerExtent\r\n }\r\n return null\r\n })\r\n return fit\r\n}\r\n\r\nconst getLayers = (data, options, sharedKey) => {\r\n // map the data received for a 2d feature map\r\n // in the form:\r\n // [{\"Key\": , LayerName: , Data: , WKT: }, ...]\r\n // and options in the form\r\n // [{layerName: , style: }, ...]\r\n // and transform into openmap layers that align with the data\r\n const { layers: styleOptions} = JSON.parse(options)\r\n\r\n const layerNames = [...new Set(data.map(layer => layer.LayerName))]\r\n const layers = layerNames.map(layerName => {\r\n const { style } = styleOptions.find(x => x.layerName === layerName)\r\n const associatedFeatures = data.filter(x => x.LayerName === layerName)\r\n const features = associatedFeatures.map(f => {\r\n // create feature from wkt\r\n const feature = format.readFeature(f.WKT)\r\n\r\n // add the sharedKey field for page interactions\r\n if (sharedKey) {\r\n feature.set(sharedKey, f[sharedKey])\r\n }\r\n\r\n // tie associated data to the wkt feature\r\n const featureData = JSON.parse(f.Data)\r\n feature.set('data', featureData)\r\n\r\n // tie style to feature\r\n Object.keys(style).map(s => {\r\n return feature.set(s, style[s])\r\n })\r\n\r\n return feature\r\n })\r\n const layer = new VectorLayer({\r\n source: new VectorSource({\r\n features\r\n }),\r\n style: featureMapStyleFunction(layerName),\r\n zIndex: style.zIndex ? style.zIndex : null\r\n })\r\n layer.set('name', layerName)\r\n layer.set('style', JSON.stringify(style))\r\n return layer\r\n })\r\n return layers\r\n}\r\n\r\nexport { getMinimumLayerExtent }\r\nexport default getLayers","import React, { createContext, useContext, useEffect, useState } from 'react'\r\nimport { WidgetDataContext } from '../../../wrappers/WidgetDataContext'\r\n\r\nconst MapContext = createContext(null)\r\n\r\nconst MapContextProvider = ({ data, legendFunction, children }) => {\r\n const [legendState, setLegendState] = useState([])\r\n const {showLegend} = useContext(WidgetDataContext)\r\n\r\n useEffect(() => setLegendState(legendFunction(data)), [data, legendFunction])\r\n\r\n // when the legend is clicked, toggle visibility for the layer\r\n // associated with the legend item\r\n const toggleLayerVisibility = (layerName) => {\r\n const updatedLegendState = legendState.map((legendItem) => ({\r\n ...legendItem,\r\n visible:\r\n legendItem.layerName === layerName\r\n ? !legendItem.visible\r\n : legendItem.visible,\r\n }))\r\n setLegendState(updatedLegendState)\r\n }\r\n\r\n useEffect(()=> {\r\n if(showLegend === \"all\"){\r\n setLegendState(legendState.map((item) => ({...item, visible: true})))\r\n }\r\n if(showLegend === \"none\"){\r\n setLegendState(legendState.map((item) => ({...item, visible: false})))\r\n }\r\n \r\n }, [showLegend])\r\n \r\n return (\r\n \r\n {children}\r\n \r\n )\r\n}\r\n\r\nexport { MapContext }\r\nexport default MapContextProvider\r\n","import React, { useContext } from 'react'\r\nimport { MapContext } from '../MapContext'\r\nimport { WidgetDataContext } from '../../../../wrappers/WidgetDataContext'\r\n\r\nexport default () => {\r\n const { showLegend } = useContext(WidgetDataContext)\r\n const { legendState: legendData, toggleLayerVisibility } = useContext(\r\n MapContext\r\n )\r\n return (\r\n \r\n {legendData.map((legendItem, idx) => {\r\n const visible = legendItem.visible\r\n const backgroundColor = visible ? legendItem.fillColor : 'white'\r\n return (\r\n \r\n toggleLayerVisibility(legendItem.layerName)\r\n }\r\n >\r\n \r\n \r\n \r\n \r\n {legendItem.layerName}\r\n \r\n \r\n )\r\n })}\r\n \r\n )\r\n}\r\n","import React, { useContext, useRef, useState, useEffect } from 'react'\r\nimport Map from 'ol/Map'\r\n// import { defaults as defaultInteractions } from 'ol/interaction'\r\nimport View from 'ol/View'\r\nimport TileLayer from 'ol/layer/Tile'\r\nimport OSM from 'ol/source/OSM'\r\nimport { unByKey } from 'ol/Observable'\r\nimport { ZoomToExtent } from 'ol/control'\r\n\r\nimport getLayers, { getMinimumLayerExtent } from './getLayers'\r\n\r\nimport withConfig from '../../../../wrappers/withConfig'\r\nimport { WidgetDataContext } from '../../../../wrappers/WidgetDataContext'\r\nimport { CurrentDashboardContext } from '../../../../wrappers/CurrentDashboardContext'\r\nimport MapContextProvider, { MapContext } from '../MapContext'\r\nimport { displayPopup, getSelectControl, zoomControls } from '../mapConfig'\r\nimport MapTooltip from '../Tooltip'\r\nimport MapLegend from './MapLegend'\r\nimport { getLegendDataFromLayers } from './styleFunctions'\r\n\r\nconst getTooltipDataFromFeature = (tooltipState) => {\r\n if (tooltipState) {\r\n const { feature } = tooltipState\r\n const data = feature ? feature.get('data') : []\r\n if (data) {\r\n return Object.keys(data).map(d => ({\r\n field: d,\r\n value: data[d]\r\n }))\r\n }\r\n }\r\n return []\r\n}\r\n\r\nconst FeatureMap = withConfig(({ config, loading, layers }) => {\r\n const { dashboardName, expandedWidgetId, sharedPageKey, sharedPageId, setSharedPageId } = useContext(CurrentDashboardContext)\r\n const { legendState } = useContext(MapContext)\r\n\r\n // get map config associated w/ the dashboard name\r\n const { MAP: mapConfig } = config\r\n const { INITIAL_ZOOM_LEVEL, CENTER_LAT_LNG } = mapConfig[dashboardName]\r\n \r\n\r\n // create internal state + refs\r\n const mapRef = useRef(null)\r\n const mapHeightRef = useRef(null)\r\n const [map, setMap] = useState(null)\r\n const [popupKey, setPopupKey] = useState(null)\r\n const [eventKeys, setEventKeys] = useState([])\r\n const [tooltipState, setTooltipState] = useState(null)\r\n const selectControl = getSelectControl()\r\n\r\n // on load, set up the map\r\n useEffect(() => {\r\n const map = new Map({\r\n target: mapRef.current,\r\n controls: zoomControls(null),\r\n // interactions: defaultInteractions({ mouseWheelZoom: false }),\r\n layers: [\r\n new TileLayer({\r\n source: new OSM(),\r\n }),\r\n ],\r\n view: new View({\r\n projection: 'EPSG:4326',\r\n center: CENTER_LAT_LNG,\r\n zoom: INITIAL_ZOOM_LEVEL\r\n })\r\n })\r\n\r\n // set initial extent to that of the view\r\n const initialExtent = map.getView().calculateExtent()\r\n map.getControls().getArray().find(x => x instanceof ZoomToExtent).extent = initialExtent\r\n setMap(map)\r\n }, [ \r\n CENTER_LAT_LNG, \r\n INITIAL_ZOOM_LEVEL\r\n ])\r\n\r\n useEffect(() => {\r\n if (map && mapRef) {\r\n map.setSize(mapRef.current.clientWidth, mapRef.current.clientHeight)\r\n map.updateSize()\r\n }\r\n }, [loading, map, mapRef, expandedWidgetId])\r\n\r\n // add tooltip event listeners when map loads\r\n useEffect(() => {\r\n if (map) {\r\n if (!popupKey) {\r\n const key = map.on('pointermove', (e) =>\r\n displayPopup(e, map, setTooltipState)\r\n )\r\n setPopupKey(key)\r\n }\r\n } else {\r\n if (popupKey) {\r\n unByKey(popupKey)\r\n setPopupKey(null)\r\n }\r\n }\r\n }, [map, popupKey])\r\n \r\n // on layers update, add layers to map\r\n // and set the extent to the largest of the layers\r\n useEffect(() => {\r\n if (layers.length && map) {\r\n // remove existing select interactions\r\n map.removeInteraction(selectControl.control)\r\n if (eventKeys.length) {\r\n eventKeys.forEach(eventKey => unByKey(eventKey))\r\n }\r\n\r\n // add select interactions to each layer\r\n map.addInteraction(selectControl.control)\r\n const keys = layers.reduce((acc, layer) => {\r\n const source = layer.getSource()\r\n const eventKey = selectControl.on(source, sharedPageKey, setSharedPageId, layer)\r\n return [...acc, ...eventKey]\r\n }, [])\r\n setEventKeys(keys)\r\n \r\n // add layers\r\n layers.map(layer => {\r\n map.addLayer(layer)\r\n return null\r\n })\r\n // set view to minimum extent that\r\n // fits all the data\r\n const fit = getMinimumLayerExtent(layers)\r\n map.getView().fit(fit, { size: map.getSize(), padding: [10, 10, 10, 10]})\r\n // set the zoom to extent toggle to the right size\r\n map.getControls().getArray().find(x => x instanceof ZoomToExtent ).extent = map.getView().calculateExtent()\r\n }\r\n }, [map, layers, setSharedPageId, sharedPageKey])\r\n\r\n // zoom to new feature when selected from table\r\n useEffect(() => {\r\n // remove all previously selected features\r\n layers.map(layer => layer.getSource().getFeatures().forEach(feature => feature.set('selected', 0)))\r\n if (sharedPageKey && sharedPageId) {\r\n\r\n // add new selection to feature layer\r\n layers.map(layer => layer.getSource().getFeatures().forEach(feature => {\r\n const featureSharedId = feature.get(sharedPageKey)\r\n\r\n if (featureSharedId === sharedPageId) {\r\n // set the selected value to true\r\n feature.set('selected', 1)\r\n \r\n // thl 2021.12.12 - remove \"zoom to feature\" on click\r\n // // and zoom to fit the map around the selected feature\r\n // const featurePoint = feature.getGeometry()\r\n // map.getView().fit(featurePoint, {padding: [10, 10, 10, 10], duration: 500, maxZoom: 15})\r\n }\r\n return null\r\n }))\r\n map.render() \r\n }\r\n }, [ map, layers, sharedPageKey, sharedPageId])\r\n\r\n \r\n useEffect(() => {\r\n if (map && legendState.length) {\r\n // set visibility when legendState changes\r\n map.getLayers().getArray()\r\n .filter(x => !!x.get('name')) // filter out layers without names\r\n .forEach(x => {\r\n x.setVisible(legendState.find(y => y.layerName === x.get('name')).visible)\r\n })\r\n }\r\n }, [legendState, map])\r\n\r\n // when the legend comes in, recalculate the extent\r\n useEffect(() => {\r\n if (map && layers && mapRef) {\r\n if (mapHeightRef.current !== mapRef.current?.clientHeight) {\r\n mapHeightRef.current = mapRef.current.clientHeight\r\n map.setSize(mapRef.current.clientWidth, mapRef.current.clientHeight)\r\n map.updateSize()\r\n // set view to minimum extent that\r\n // fits all the data\r\n const fit = getMinimumLayerExtent(layers)\r\n map.getView().fit(fit, { size: map.getSize(), padding: [10, 10, 10, 10]})\r\n // set the zoom to extent toggle to the right size\r\n map.getControls().getArray().find(x => x instanceof ZoomToExtent ).extent = map.getView().calculateExtent()\r\n }\r\n }\r\n }, [map, layers, legendState, mapRef.current?.clientHeight])\r\n\r\n if (loading) {\r\n return null\r\n }\r\n\r\n return (\r\n \r\n )\r\n})\r\n\r\nconst FeatureMapWrapper = () => {\r\n const {\r\n loading,\r\n widgetData: widget,\r\n } = useContext(WidgetDataContext)\r\n const [layers, setLayers] = useState([])\r\n\r\n useEffect(() => {\r\n if (widget && widget.Data) {\r\n const {Data: data, WidgetOptions: options, SharedKey: sharedKey} = widget\r\n setLayers(getLayers(data, options, sharedKey))\r\n }\r\n }, [widget])\r\n \r\n return (\r\n \r\n \r\n \r\n )\r\n}\r\n\r\nexport default FeatureMapWrapper","\r\nimport { DoubleSide } from \"three\"\r\n\r\nconst meshProps = {\r\n rotation: [-Math.PI / 2, 0, 0],\r\n castShadow: false\r\n}\r\n\r\nconst meshBasicMaterialProps = {\r\n side: DoubleSide, \r\n attach: \"material\", \r\n transparent: true,\r\n depthTest: false\r\n}\r\n\r\nconst orbitControlsProps = {\r\n // enableDamping: true,\r\n // dampingFactor: 0.05,\r\n rotateSpeed: 0.35,\r\n screenSpacePanning: true,\r\n maxPolarAngle: Math.PI / 2\r\n}\r\n\r\nconst scale = { x: 1250, y: 1250, z: 1 / 500 }\r\n\r\nexport { scale, meshProps, meshBasicMaterialProps, orbitControlsProps }","import React, { useRef, useMemo, useContext, useCallback } from 'react'\r\nimport { useFrame, useUpdate } from 'react-three-fiber'\r\nimport { Vector3, Color, Shape, Vector2, ArrowHelper, Vector4 } from 'three'\r\nimport { SceneContext } from './Scene'\r\nimport { generateMeshFromPointCloud } from './utils'\r\nimport { meshBasicMaterialProps, meshProps, scale } from '../../../../../utils/widgets/map/3d/config'\r\n\r\nconst Mesh = ({feature, centerPoint, style}) => {\r\n const shouldInteract = useRef(true)\r\n\r\n useFrame((state) => {\r\n const isControlled = state.scene.userData?.isControlled\r\n shouldInteract.current = !isControlled\r\n })\r\n\r\n const {\r\n tooltipContext: { updateTooltipState },\r\n legendState,\r\n } = useContext(SceneContext)\r\n const { geometry, styleData: featureStyles } = feature\r\n const { coordinates } = geometry\r\n\r\n const styleObj = useMemo(() => ({\r\n ...featureStyles,\r\n ...style,\r\n }), [featureStyles, style])\r\n\r\n const vertices = useMemo(() => {\r\n return generateMeshFromPointCloud(coordinates[0], centerPoint, scale)\r\n }, [coordinates, centerPoint])\r\n\r\n const visible = useMemo(() => legendState.find(\r\n (legendItem) => legendItem.layerName === feature.layerName\r\n ).visible, [legendState, feature])\r\n\r\n const geometryRef = useUpdate(geometry => {\r\n geometry.setFromPoints(vertices)\r\n }, [visible])\r\n\r\n const Meshes = useMemo(() =>\r\n {\r\n if (shouldInteract.current) {\r\n const data = e.eventObject.userData\r\n const event = e.nativeEvent\r\n const {\r\n offsetX,\r\n offsetY,\r\n target,\r\n } = event\r\n updateTooltipState({\r\n data,\r\n target: {\r\n offsetX,\r\n offsetY,\r\n scrollWidth: target.scrollWidth,\r\n clientHeight:\r\n target.clientHeight,\r\n },\r\n })\r\n }\r\n }}\r\n onPointerLeave={(e) => {\r\n if (shouldInteract.current) {\r\n e.eventObject.material.color = new Color(\r\n styleObj.color ? styleObj.color : 'red'\r\n )\r\n if (visible) {\r\n updateTooltipState(null)\r\n }\r\n }\r\n }}\r\n >\r\n \r\n \r\n \r\n , [shouldInteract, styleObj, updateTooltipState, feature])\r\n\r\n return (\r\n visible && (\r\n Meshes\r\n )\r\n )\r\n}\r\n\r\nconst Polygon = ({ feature, centerPoint, style }) => {\r\n const { legendState } = useContext(SceneContext)\r\n const geometryRef = useRef()\r\n const { geometry } = feature\r\n const { coordinates } = geometry\r\n\r\n const shape = useMemo(() => {\r\n const vectors = coordinates\r\n ? coordinates[0].reduce((acc, pt) => {\r\n if (pt.length > 1) {\r\n return [\r\n ...acc,\r\n new Vector2(\r\n (pt[0] - centerPoint.x) * scale.x,\r\n (pt[1] - centerPoint.y) * scale.y\r\n ),\r\n ]\r\n }\r\n return acc\r\n }, [])\r\n : []\r\n return new Shape(vectors)\r\n }, [coordinates, centerPoint])\r\n\r\n if (!(feature.data && feature.data.Top && feature.data.Bottom)) {\r\n return null\r\n }\r\n\r\n // calculate height as the bottom - top\r\n const height = - Math.abs((feature.data.Top - feature.data.Bottom)) * scale.z\r\n\r\n // scale top\r\n const top = feature.data.Top * scale.z\r\n \r\n const visible = legendState.find(\r\n (legendItem) => legendItem.layerName === feature.layerName\r\n ).visible\r\n\r\n return (\r\n visible && (\r\n \r\n \r\n \r\n \r\n )\r\n )\r\n}\r\n\r\nconst Line = ({ feature, centerPoint, style: layerStyle, camera }) => {\r\n const shouldInteract = useRef(true)\r\n useFrame((state) => {\r\n const isControlled = state.scene.userData?.isControlled\r\n shouldInteract.current = !isControlled\r\n })\r\n const {\r\n tooltipContext: { updateTooltipState },\r\n sharedPageKey,\r\n sharedPageId,\r\n setSharedPageId,\r\n legendState,\r\n } = useContext(SceneContext)\r\n const { geometry, styleData: featureStyle } = feature\r\n const { coordinates } = geometry\r\n\r\n const style = useMemo(() => ({ ...layerStyle, ...featureStyle }), [layerStyle, featureStyle])\r\n\r\n const sections = useMemo(() => {\r\n let lastZValue = 0\r\n const vectors = coordinates.map((pt, idx) => {\r\n if (pt.length > 2) {\r\n return new Vector3(\r\n (pt[0] - centerPoint.x) * scale.x,\r\n (pt[1] - centerPoint.y) * scale.y,\r\n (pt[2] - centerPoint.z) * scale.z\r\n )\r\n } else {\r\n // TODO: fix on sql side\r\n // if 2 pt wellbore, and there is no last z coordinate,\r\n // get the z value of the last pt\r\n if (idx > 0 && coordinates[idx - 1].length > 2) {\r\n lastZValue = coordinates[idx - 1][2] - centerPoint.z\r\n }\r\n return new Vector3(\r\n (pt[0] - centerPoint.x) * scale.x,\r\n (pt[1] - centerPoint.y) * scale.y,\r\n lastZValue * scale.z\r\n )\r\n }\r\n })\r\n const lineSegments = vectors.reduce((acc, curr, idx) => {\r\n if (idx <= vectors.length - 2) {\r\n const v1 = vectors[idx]\r\n const v2 = vectors[idx + 1]\r\n return [\r\n ...acc,\r\n {\r\n v1,\r\n v2,\r\n },\r\n ]\r\n } else {\r\n return acc\r\n }\r\n }, [])\r\n\r\n const cylinders = lineSegments.map(({ v1, v2 }) => {\r\n const direction = new Vector3().subVectors(v2, v1)\r\n const arrow = new ArrowHelper(direction.clone().normalize(), v1)\r\n const rotation = arrow.rotation.clone()\r\n const position = new Vector3().addVectors(\r\n v1,\r\n direction.clone().multiplyScalar(0.5)\r\n )\r\n return {\r\n direction,\r\n rotation,\r\n position,\r\n }\r\n })\r\n return cylinders\r\n }, [coordinates, centerPoint])\r\n\r\n const getColor = useCallback((f, s) => {\r\n const c = new Color(s.color ? s.color : 'red')\r\n if (sharedPageKey && sharedPageId) {\r\n if (sharedPageId === f[sharedPageKey])\r\n return new Color('#ADD8E6')\r\n }\r\n return c\r\n }, [sharedPageKey, sharedPageId])\r\n\r\n const lineWidth = useMemo(() => style.width ? style.width * 0.02 : 0.1, [style])\r\n\r\n const material = useMemo(() => {\r\n return (\r\n \r\n )}, [getColor, feature, style])\r\n\r\n const visible = useMemo(() => legendState.find(\r\n (legendItem) => legendItem.layerName === feature.layerName\r\n ).visible, [legendState, feature])\r\n\r\n const meshes = useMemo(() => {\r\n return sections.map(\r\n (\r\n { direction, rotation, quaternion, position },\r\n idx\r\n ) => (\r\n {\r\n if (shouldInteract.current) {\r\n const data = e.eventObject.userData\r\n const event = e.nativeEvent\r\n const {\r\n offsetX,\r\n offsetY,\r\n target,\r\n } = event\r\n e.eventObject.material.color = new Color(\r\n 'black'\r\n )\r\n updateTooltipState({\r\n data,\r\n target: {\r\n offsetX,\r\n offsetY,\r\n scrollWidth: target.scrollWidth,\r\n clientHeight:\r\n target.clientHeight,\r\n },\r\n })\r\n }\r\n }}\r\n onPointerLeave={(e) => {\r\n if (shouldInteract.current) {\r\n e.eventObject.material.color = new Color(\r\n style.color ? style.color : 'red'\r\n )\r\n if (visible) {\r\n updateTooltipState(null)\r\n }\r\n }\r\n }}\r\n onClick={() => {\r\n if (sharedPageKey) {\r\n setSharedPageId(feature[sharedPageKey])\r\n }\r\n }}\r\n rotation={[rotation.x, rotation.y, rotation.z]}\r\n quaternion={quaternion}\r\n position={position}\r\n name={feature.layerName}\r\n >\r\n \r\n {material}\r\n \r\n )\r\n )\r\n }\r\n , [material, sharedPageKey, setSharedPageId, shouldInteract, lineWidth, sections, feature, style.color])\r\n\r\n const meshGroup = useMemo(() => \r\n \r\n {meshes}\r\n \r\n , [meshes])\r\n\r\n return (\r\n <>\r\n {material && sections && visible && (\r\n meshGroup\r\n )}\r\n >\r\n )\r\n}\r\n\r\nexport { Polygon, Line, Mesh }\r\n","import React from 'react'\r\nimport wellknown from 'wellknown'\r\nimport { Vector3 } from 'three'\r\nimport delaunay from 'delaunay-fast'\r\n\r\nimport colors from '../../../../../utils/colors/chartPalette'\r\nimport { Line, Polygon, Mesh } from './Shapes'\r\n\r\nconst getShapeComponent = layerType => {\r\n switch (layerType) {\r\n case 'LineString':\r\n return Line\r\n case 'Polygon':\r\n return Polygon\r\n case 'Mesh':\r\n return Mesh\r\n default:\r\n return \r\n }\r\n}\r\n\r\nconst prepareData = (data, options, sharedPageKey) => {\r\n // parse the wkt returned w/ each 3d feature\r\n const transformedData = data.filter(d => !!d.WKT).map(d => {\r\n let additionalData = {}\r\n try {\r\n additionalData = JSON.parse(d.Data)\r\n } catch (e) {\r\n console.log('unable to parse feature data from point:', d)\r\n }\r\n return {...d, geometry: wellknown.parse(d.WKT), data: additionalData}\r\n })\r\n\r\n // point data needs to be constructed into a polygon \r\n const pointData = transformedData.filter(x => x.geometry.type === 'Point')\r\n const allPointLayerNames = pointData.map(x => {\r\n const layerOptions = options.find(option => option.layerName === x.LayerName)\r\n const layerNameField = layerOptions ? layerOptions.nameField : null\r\n return x.data[layerNameField]\r\n })\r\n const pointLayerNames = [...new Set(allPointLayerNames)]\r\n\r\n const pointsToMesh = pointLayerNames.reduce((acc, layerName, idx) => {\r\n const associatedData = pointData.filter(x => {\r\n const layerOptions = options.find(option => option.layerName === x.LayerName)\r\n const layerNameField = layerOptions ? layerOptions.nameField : null\r\n return x.data[layerNameField] === layerName\r\n })\r\n const geometryData = {\r\n type: 'Mesh',\r\n coordinates: [associatedData.map(x => x.geometry.coordinates)]\r\n }\r\n const exampleRow = associatedData[0]\r\n const layerOptions = options.find(option => option.layerName === exampleRow.LayerName)\r\n const layerNameField = layerOptions ? layerOptions.nameField : null\r\n return [\r\n ...acc,\r\n {\r\n LayerName: exampleRow.LayerName,\r\n geometry: geometryData,\r\n data: {\r\n Type: exampleRow.LayerName,\r\n [layerNameField]: layerName,\r\n style: {\r\n color: colors[idx % colors.length] \r\n }\r\n },\r\n [sharedPageKey]: exampleRow[sharedPageKey],\r\n }\r\n ]\r\n }, [])\r\n\r\n const parsedData = [\r\n ...transformedData.filter(x => x.geometry.type !== 'Point'),\r\n ...pointsToMesh\r\n ]\r\n\r\n // get the unique layernames as a set from the data\r\n const layerNames = [...new Set(parsedData.map(x => x.LayerName))]\r\n\r\n // collect metadata + associated feature set for each layer\r\n const layerData = layerNames.map((layerName, idx) => {\r\n const associatedData = parsedData.filter(x => x.LayerName === layerName)\r\n const associatedStyle = options.find(x => x.layerName === layerName)\r\n const exampleRow = associatedData[0]\r\n return {\r\n layerName: exampleRow.LayerName,\r\n layerType: exampleRow.geometry.type,\r\n layerIdx: idx,\r\n data: associatedData,\r\n [sharedPageKey]: sharedPageKey ? exampleRow[sharedPageKey] : null,\r\n style: associatedStyle ? associatedStyle.style : null\r\n }\r\n })\r\n return layerData\r\n}\r\n\r\nconst getFeatures = (camera, gl, layerData, setTooltipState) => {\r\n // obtain the camera positioning:\r\n // this is the vector representing the top of the UIC permit well and extended a bit\r\n let centerPoint = new Vector3(0, 0, 0)\r\n if (layerData.find(x => x.layerType === 'LineString')) {\r\n const allCoords = layerData\r\n .filter(x => x.layerType === 'LineString')\r\n .reduce((acc, d) => {\r\n return [...acc, ...d.data.map(pt => {\r\n return pt.geometry.coordinates[0]\r\n })]\r\n }, [])\r\n const totalLineStrings = allCoords.length\r\n const centerCoords = allCoords.reduce((acc, curr) => {\r\n return [\r\n acc[0] + (curr[0] / totalLineStrings),\r\n acc[1] + (curr[1] / totalLineStrings),\r\n acc[2] + (curr[2] ? curr[2] / totalLineStrings : 0)\r\n ]\r\n }, [0, 0, 0])\r\n centerPoint = new Vector3(...centerCoords)\r\n const lookAt = camera.position.applyAxisAngle(new Vector3(1, 0, 0), -Math.PI / 2).multiplyScalar(1.5)\r\n camera.position.set(lookAt.x, lookAt.y, lookAt.z)\r\n }\r\n\r\n // tie the layerData to three.js components\r\n const features = layerData.reduce((acc, layer) => {\r\n \r\n // get the component based on wkt layer type (eg polygon, linestring, point)\r\n const ShapeComponent = getShapeComponent(layer.layerType)\r\n\r\n if (typeof ShapeComponent === 'undefined') {\r\n return acc\r\n } else {\r\n const layerFeatures = layer.data.map((feature, idx) => {\r\n\r\n // parse the feature.Data field\r\n // into json, if it exists\r\n let parsedData = {}\r\n let tooltipData = {}\r\n let styleData = {}\r\n if (feature.data) {\r\n try {\r\n parsedData = feature.data\r\n // remove the style object from the parsed data, if it exists\r\n tooltipData = Object.keys(parsedData).reduce((acc, curr) => {\r\n if (curr !== \"style\") {\r\n return { ...acc, [curr]: parsedData[curr]}\r\n }\r\n return acc\r\n }, {})\r\n\r\n // extract the style object if it exists\r\n styleData = parsedData.style ? parsedData.style : {}\r\n } catch (e) {\r\n console.log('unable to parse feature data from feature:', feature)\r\n }\r\n }\r\n\r\n // add attributes to the feature\r\n feature.tooltipData = tooltipData\r\n feature.styleData = styleData\r\n feature.layerName = layer.layerName\r\n feature.layerType = layer.layerType\r\n\r\n // return a react component that matches wkt type,\r\n // with props\r\n return React.createElement(ShapeComponent, {\r\n ...layer,\r\n feature,\r\n key: `${layer.layerName}-${layer.layerType}-${idx}`,\r\n centerPoint,\r\n camera,\r\n gl,\r\n setTooltipState\r\n })\r\n })\r\n return [...acc, ...layerFeatures ]\r\n }\r\n }, [])\r\n return features\r\n}\r\n\r\n\r\nconst getLegendDataFromLayers = (layerData) => {\r\n return layerData.map(layer => {\r\n const { layerName, style } = layer\r\n return {\r\n layerName,\r\n fillColor: style && style.color ? style.color : 'rgb(55,55,55)',\r\n strokeColor: style && style.color ? style.color : 'rgb(55,55,55)',\r\n visible: true\r\n }\r\n })\r\n}\r\n\r\nconst generateMeshFromPointCloud = (coordinates, centerPoint, scale) => {\r\n const scaledCoords = coordinates.map(pt => ([\r\n (pt[0] - centerPoint.x) * scale.x,\r\n (pt[1] - centerPoint.y) * scale.y,\r\n (pt[2] - centerPoint.z) * scale.z\r\n ]))\r\n\r\n // lop off the 3d aspect of the point\r\n const planeRepresentation = scaledCoords.map(pt => ([\r\n pt[0], pt[1]\r\n ]))\r\n\r\n\r\n // convert the plane representation to delaunay triangle \r\n // representation\r\n const cells = delaunay.triangulate(planeRepresentation)\r\n\r\n // create triangles from the cells\r\n const numTriangles = cells.length ? cells.length / 3 : 0\r\n \r\n let triangles = []\r\n for (let i = 0; i < numTriangles; i++) {\r\n triangles.push(new Vector3(...scaledCoords[cells[(i*3)]]))\r\n triangles.push(new Vector3(...scaledCoords[cells[(i*3) + 1]]))\r\n triangles.push(new Vector3(...scaledCoords[cells[(i*3) + 2]]))\r\n }\r\n\r\n return triangles\r\n}\r\n\r\nexport { getFeatures, getShapeComponent, prepareData, getLegendDataFromLayers, generateMeshFromPointCloud }","import React, { useState, createContext } from 'react'\r\nimport { getTooltipPositionProps } from '../Tooltip'\r\n\r\nconst TooltipContext = createContext(null)\r\n\r\nconst TooltipContextProvider = ({children}) => {\r\n const [tooltipState, setTooltipState] = useState(null)\r\n\r\n const updateTooltipState = (tooltipData) => {\r\n if (tooltipData) {\r\n const { data, target } = tooltipData\r\n const pixel = [target.offsetX, target.offsetY]\r\n const styleProps = getTooltipPositionProps(pixel, target.scrollWidth, target.clientHeight)\r\n\r\n setTooltipState({\r\n data,\r\n feature: {},\r\n ...styleProps\r\n })\r\n } else {\r\n setTooltipState(prevState => ({\r\n ...prevState,\r\n data: {}\r\n }))\r\n }\r\n }\r\n\r\n return (\r\n \r\n {children}\r\n \r\n )\r\n}\r\n\r\nexport { TooltipContext }\r\nexport default TooltipContextProvider","import React, { useEffect, useState, useContext, createContext, useRef, useMemo } from 'react'\r\nimport { Canvas, extend, useThree, useFrame } from 'react-three-fiber'\r\nimport { Color } from 'three'\r\nimport { MapControls, OrbitControls } from 'three/examples/jsm/controls/OrbitControls'\r\nimport { ConvexGeometry } from 'three/examples/jsm/geometries/ConvexGeometry'\r\nimport { TrackballControls } from 'three/examples/jsm/controls/TrackballControls'\r\n\r\nimport { getFeatures } from './utils'\r\nimport { TooltipContext } from './TooltipContext'\r\nimport { CurrentDashboardContext } from '../../../../wrappers/CurrentDashboardContext'\r\nimport { MapContext } from '../MapContext'\r\nimport { orbitControlsProps } from '../../../../../utils/widgets/map/3d/config'\r\n\r\nconst SceneContext = createContext(null)\r\n\r\nextend({\r\n MapControls,\r\n TrackballControls,\r\n OrbitControls,\r\n ConvexGeometry,\r\n})\r\n\r\nconst Controls = ({ camera, gl }) => {\r\n const isControlled = useRef(false)\r\n const controlsRef = useRef(null)\r\n const lightRef = useRef(null)\r\n const { compassRef, tooltipContext: {updateTooltipState} } = useContext(SceneContext)\r\n\r\n useFrame((state) => {\r\n // update isControlled flag on the scene - determines whether interactions should happen\r\n // (such as tooltip display / hide) when the controls are \r\n // updating the camera position\r\n state.scene.userData.isControlled = isControlled.current\r\n controlsRef.current.update()\r\n lightRef.current.position.set(...state.camera.position.clone().multiplyScalar(2).toArray())\r\n \r\n // apply transformation to compass based on azimuthal angle of camera\r\n // compassRef.current.style.transform = 'rotateX(' + controlsRef.current.getPolarAngle() + 'rad) rotateZ(' + controlsRef.current.getAzimuthalAngle() + 'rad)'\r\n compassRef.current.style.transform = 'rotate(' + controlsRef.current.getAzimuthalAngle() + 'rad)'\r\n })\r\n\r\n useEffect(() => {\r\n if (controlsRef.current) {\r\n controlsRef.current.addEventListener('start', () => {\r\n isControlled.current = true\r\n updateTooltipState(null)\r\n })\r\n controlsRef.current.addEventListener('end', () => {\r\n isControlled.current = false\r\n })\r\n }\r\n }, [controlsRef, isControlled])\r\n\r\n return (\r\n <>\r\n \r\n \r\n >\r\n )\r\n}\r\n\r\nconst Renderer = ({ data }) => {\r\n const { camera, gl } = useThree()\r\n const { expandedWidgetId } = useContext(SceneContext)\r\n const [features, setFeatures] = useState([])\r\n \r\n // set the features based on layer data\r\n useEffect(() => {\r\n const features = getFeatures(camera, gl, data)\r\n setFeatures(features)\r\n }, [camera, gl, data])\r\n\r\n // manually fire a resize event for when the\r\n // canvas size changes: see https://github.com/react-spring/react-three-fiber/issues/350\r\n // for tracking of this issue\r\n useEffect(() => {\r\n setTimeout(() => window.dispatchEvent(new Event('resize')), 200)\r\n }, [expandedWidgetId])\r\n\r\n const objects = useMemo(() => (\r\n <>\r\n \r\n {features}\r\n \r\n \r\n >\r\n ), [features, camera, gl])\r\n\r\n return objects\r\n}\r\n\r\nexport default ({ data, compassRef }) => {\r\n const tooltipContext = useContext(TooltipContext) \r\n const { expandedWidgetId, sharedPageId, sharedPageKey, setSharedPageId } = useContext(CurrentDashboardContext)\r\n const { legendState } = useContext(MapContext)\r\n\r\n return (\r\n \r\n\r\n )\r\n}\r\nexport { SceneContext }\r\n","import React, { useContext } from 'react'\r\n\r\nimport { MapContext } from '../MapContext'\r\nimport { WidgetDataContext } from '../../../../wrappers/WidgetDataContext'\r\n\r\nexport default () => {\r\n const { showLegend } = useContext(WidgetDataContext)\r\n const { legendState, toggleLayerVisibility } = useContext(MapContext)\r\n\r\n return (\r\n \r\n {legendState.map((legendItem, idx) => {\r\n const backgroundColor = legendItem.visible\r\n ? legendItem.fillColor\r\n : 'white'\r\n const border = legendItem.visible\r\n ? legendItem.strokeColor\r\n ? `solid 1px ${legendItem.strokeColor}`\r\n : null\r\n : 'solid 1px black'\r\n const borderRadius = legendItem.visible\r\n ? legendItem.geometryType === 'LineString'\r\n ? '10px'\r\n : null\r\n : '0px'\r\n\r\n return (\r\n \r\n toggleLayerVisibility(legendItem.layerName)\r\n }\r\n >\r\n \r\n \r\n \r\n \r\n {legendItem.layerName}\r\n \r\n \r\n )\r\n })}\r\n \r\n )\r\n}\r\n","import React, { useContext, useState, useRef, useEffect } from 'react'\r\n\r\nimport { WidgetDataContext } from '../../../../wrappers/WidgetDataContext'\r\nimport Scene from './Scene'\r\nimport { prepareData } from './utils'\r\nimport MapLegend from './MapLegend'\r\nimport MapTooltip from '../Tooltip'\r\nimport TooltipContextProvider, { TooltipContext } from './TooltipContext'\r\nimport { CurrentDashboardContext } from '../../../../wrappers/CurrentDashboardContext'\r\nimport MapContextProvider from '../MapContext'\r\nimport { getLegendDataFromLayers } from './utils'\r\n\r\nconst getTooltipDataFromFeature = (tooltipState) => {\r\n if (tooltipState) {\r\n const { data } = tooltipState\r\n return Object.keys(data).map(fieldName => (\r\n {\r\n field: fieldName,\r\n value: data[fieldName]\r\n }\r\n ))\r\n }\r\n return []\r\n}\r\n\r\nexport default () => {\r\n const { sharedPageKey } = useContext(CurrentDashboardContext)\r\n const { loading, widgetData: widget } = useContext(WidgetDataContext)\r\n const mapRef = useRef(null)\r\n const compassRef = useRef(null)\r\n const [layerData, setLayerData] = useState([])\r\n\r\n useEffect(() => {\r\n if (widget && widget.Data) {\r\n // obtain layer data + stringified options object from\r\n // the widget\r\n const { Data: data, WidgetOptions: stringifiedOptions } = widget\r\n\r\n // parse the options object and get the \"layers\" field\r\n let parsedOptions = stringifiedOptions\r\n ? JSON.parse(stringifiedOptions)\r\n : { layers: [] }\r\n const { layers: options } = parsedOptions\r\n\r\n // prepare data - turns into shape\r\n // [{layerName: , layerType: , data: , style: }, ...]\r\n const layerData = prepareData(data, options, sharedPageKey)\r\n setLayerData(layerData)\r\n }\r\n }, [widget, sharedPageKey])\r\n\r\n if (loading) {\r\n return null\r\n }\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n  \r\n \r\n \r\n \r\n \r\n \r\n {({ tooltipState }) => (\r\n \r\n )}\r\n \r\n \r\n \r\n \r\n \r\n )\r\n}\r\n","import React, {\r\n useContext,\r\n useEffect,\r\n useState,\r\n useMemo,\r\n useCallback,\r\n} from 'react'\r\nimport {\r\n PieChart,\r\n Pie,\r\n Legend,\r\n Cell,\r\n Sector,\r\n Tooltip,\r\n ResponsiveContainer,\r\n} from 'recharts'\r\n\r\nimport { WidgetDataContext } from '../../../wrappers/WidgetDataContext'\r\n\r\nimport { formatNumber } from '../../../../utils/numberFormatting'\r\nimport { ColorContext } from '../../../wrappers/ColorContext'\r\n\r\nconst createInitialState = (uniqueGroups) =>\r\n uniqueGroups.reduce(\r\n (acc, curr) => ({\r\n ...acc,\r\n [curr]: { hidden: false, highlight: false },\r\n }),\r\n {}\r\n )\r\n\r\nconst resetStateField = (displayState, field, state) =>\r\n Object.keys(displayState).reduce(\r\n (acc, curr) => ({\r\n ...acc,\r\n [curr]: { ...displayState[curr], [field]: state },\r\n }),\r\n {}\r\n )\r\n\r\nconst renderActiveShape = (props) => {\r\n const {\r\n cx,\r\n cy,\r\n innerRadius,\r\n outerRadius,\r\n startAngle,\r\n endAngle,\r\n fill,\r\n payload,\r\n } = props\r\n return (\r\n \r\n \r\n {payload.name}\r\n \r\n \r\n \r\n \r\n )\r\n}\r\n\r\nconst CustomTooltip = ({ payload, data, groupKey }) => {\r\n const label = payload?.[0]?.payload[groupKey]\r\n const value = payload?.[0]?.payload['Value']\r\n const total = data.map((d) => d['Value']).reduce((a, b) => a + b, 0)\r\n const percent = formatNumber((value / total) * 100)\r\n return (\r\n \r\n \r\n {label ? label : 'Null'}:{' '}\r\n {formatNumber(payload?.[0]?.payload?.Value)} ({percent}%)\r\n \r\n \r\n )\r\n}\r\n\r\nconst CustomLegend = ({\r\n legendPayload,\r\n payload,\r\n activeLegendIndex,\r\n toggleDisplayState,\r\n}) => {\r\n const getLegendValue = useCallback(\r\n (name) => {\r\n const value = payload.find((x) => x.value === name)?.payload\r\n ?.percent\r\n if (value) {\r\n return formatNumber(value * 100)\r\n } else {\r\n return null\r\n }\r\n },\r\n [payload, legendPayload]\r\n )\r\n\r\n return (\r\n \r\n {legendPayload.map((item, index) => (\r\n - \r\n \r\n \r\n toggleDisplayState(item, 'highlight', true)\r\n }\r\n >\r\n {item.value}\r\n {activeLegendIndex === index\r\n ? `(${getLegendValue(item.value)}%)`\r\n : null}\r\n \r\n
\r\n ))}\r\n \r\n )\r\n}\r\n\r\nconst Chart = ({ data, showLegend }) => {\r\n const { colorMap, updateColorMap, getAssociatedColor } = useContext(ColorContext)\r\n // get the column name key for grouping, eg in { Value: , District: }\r\n // groupKey == 'District'\r\n const groupKey = useMemo(\r\n () => Object.keys(data[0]).find((x) => !x.includes('Value')),\r\n [data]\r\n )\r\n\r\n // get a list of all unique groups that are present in the data\r\n // and sort alphabetically\r\n const uniqueGroups = useMemo(\r\n () =>\r\n [...new Set(data.map((x) => x[groupKey]))]\r\n .sort()\r\n .map((x) => (x === null ? (x = '') : x)),\r\n [data, groupKey]\r\n )\r\n\r\n const [displayState, setDisplayState] = useState(\r\n createInitialState(uniqueGroups)\r\n )\r\n\r\n const toggleDisplayState = useCallback(\r\n (e, field, unique) => {\r\n // if only one can be selected at a time,\r\n // then we want to update state so that\r\n // all other values are false\r\n if (unique && !displayState[e.value][field]) {\r\n const newDisplayState = resetStateField(\r\n displayState,\r\n field,\r\n false\r\n )\r\n setDisplayState({\r\n ...newDisplayState,\r\n [e.value]: {\r\n ...newDisplayState[e.value],\r\n [field]: true,\r\n },\r\n })\r\n } else {\r\n // if we are hiding a pie slice,\r\n // and it is the currently highlighted slice,\r\n // we want to remove the highlight state on that slice\r\n if (field === 'hidden' && displayState[e.value].highlight) {\r\n setDisplayState({\r\n ...displayState,\r\n [e.value]: {\r\n highlight: false,\r\n [field]: !displayState[e.value][field],\r\n },\r\n })\r\n } else {\r\n // otherwise, we simply toggle the field\r\n setDisplayState({\r\n ...displayState,\r\n [e.value]: {\r\n ...displayState[e.value],\r\n [field]: !displayState[e.value][field],\r\n },\r\n })\r\n }\r\n }\r\n },\r\n [displayState, setDisplayState]\r\n )\r\n\r\n const onShowLegendChange = useCallback(\r\n (showLegend) => {\r\n if (\r\n showLegend === 'all' &&\r\n Object.keys(displayState).some(\r\n (x) => displayState[x].hidden === true\r\n )\r\n ) {\r\n setDisplayState(resetStateField(displayState, 'hidden', false))\r\n }\r\n if (\r\n showLegend === 'none' &&\r\n Object.keys(displayState).some(\r\n (x) => displayState[x].hidden === false\r\n )\r\n ) {\r\n const resetHiddenField = resetStateField(\r\n displayState,\r\n 'hidden',\r\n true\r\n )\r\n const newDisplayState = resetStateField(\r\n resetHiddenField,\r\n 'highlight',\r\n false\r\n )\r\n setDisplayState(newDisplayState)\r\n }\r\n },\r\n [setDisplayState, displayState]\r\n )\r\n\r\n useEffect(() => {\r\n updateColorMap(groupKey, uniqueGroups)\r\n }, [uniqueGroups, groupKey])\r\n\r\n useEffect(() => {\r\n onShowLegendChange(showLegend)\r\n }, [showLegend])\r\n\r\n const colors = useMemo(\r\n () =>\r\n uniqueGroups.reduce((acc, curr, idx) => {\r\n return {\r\n ...acc,\r\n [curr]: getAssociatedColor(curr, groupKey),\r\n }\r\n }, {}),\r\n [uniqueGroups, groupKey, colorMap]\r\n )\r\n\r\n const displayGroups = useMemo(\r\n () =>\r\n uniqueGroups\r\n .filter((groupName) => !displayState[groupName].hidden)\r\n .sort(),\r\n [uniqueGroups, displayState]\r\n )\r\n\r\n const cells = useMemo(\r\n () =>\r\n displayGroups.map((groupName, idx) => {\r\n return (\r\n | \r\n )\r\n }),\r\n [displayGroups, colors]\r\n )\r\n\r\n // legend includes all groups, so the active index for the\r\n // legend is the uniquegroups idx which is highlighted\r\n const activeLegendIndex = useMemo(\r\n () => uniqueGroups.findIndex((x) => displayState[x].highlight === true && !displayState[x].hidden === true),\r\n [displayState, uniqueGroups]\r\n )\r\n\r\n // pie chart does not include all groups, so the active index for the\r\n // legend is the displayGroups idx which is highlighted\r\n const activeIndex = useMemo(\r\n () =>\r\n displayGroups.findIndex((x) => displayState[x].highlight === true),\r\n [displayState, displayGroups]\r\n )\r\n\r\n const chartData = useMemo(\r\n () =>\r\n data\r\n .filter((d) => !displayState[d[groupKey]].hidden)\r\n .sort((a, b) =>\r\n typeof a[groupKey] === 'string' &&\r\n typeof b[groupKey] === 'string'\r\n ? a[groupKey]\r\n .toLowerCase()\r\n .localeCompare(b[groupKey].toLowerCase())\r\n : a[groupKey] - b[groupKey]\r\n ),\r\n [displayState, groupKey, data]\r\n )\r\n\r\n const legendPayload = useMemo(\r\n () =>\r\n uniqueGroups.map((groupName) => ({\r\n color: !displayState[groupName].hidden\r\n ? colors[groupName]\r\n : 'rgb(204, 204, 204)',\r\n type: 'rect',\r\n value: groupName,\r\n id: groupName,\r\n })),\r\n [displayState, colors, uniqueGroups]\r\n )\r\n\r\n return (\r\n \r\n \r\n \r\n {cells}\r\n \r\n \r\n }\r\n />\r\n \r\n }\r\n verticalAlign=\"top\"\r\n formatter={(a, b) => {\r\n return b.value ? b.value : 'Null'\r\n }}\r\n data={data}\r\n wrapperStyle={{\r\n display: showLegend ? '' : 'none',\r\n top: 1,\r\n left: 0,\r\n overflowX: 'hidden',\r\n overflowY: 'scroll',\r\n alignItems: 'center',\r\n width: '100%',\r\n }}\r\n />\r\n \r\n \r\n )\r\n}\r\n\r\nexport default () => {\r\n const { widgetData: widget, showLegend } = useContext(WidgetDataContext)\r\n const data = widget ? widget.Data : null\r\n //added to prevent errors from groupKeys with null values\r\n\r\n const noNullData = data.map((x) => {\r\n Object.keys(x).map((y) => (x[y] === null ? (x[y] = 'Null') : x[y]))\r\n return x\r\n })\r\n return \r\n}\r\n","import React, { useContext } from 'react'\r\n\r\nimport { WidgetDataContext } from '../../../wrappers/WidgetDataContext'\r\n\r\nimport formatValue from '../../../../utils/valueFormatting'\r\nimport stringFields from '../../../../utils/constants/widgets/stringFields'\r\n\r\nexport default () => {\r\n const { widgetData: widget } = useContext(WidgetDataContext)\r\n\r\n // data is the first row of the widget.Data field\r\n const data = (widget && widget.Data && widget.Data.length) ? widget.Data[0] : null\r\n\r\n // map the data ({: , : , ...})\r\n // into an array of field, value pairs\r\n const fieldData = Object.keys(data).map(field => ({field, value: data[field]}))\r\n\r\n // arrange the field data and display\r\n return (\r\n \r\n \r\n \r\n {fieldData.map((f, idx) => (\r\n \r\n \r\n \r\n {stringFields.includes(f.field) ? f.value : formatValue(f.value)} \r\n \r\n \r\n ))}\r\n \r\n \r\n \r\n )\r\n}","import React from 'react'\r\nimport { dateToString } from '../../../../utils/dateFormatting'\r\n\r\nexport default ({ active, payload, label }) => {\r\n if (active) {\r\n const formattedLabel = dateToString(label)\r\n return (\r\n \r\n {formattedLabel} \r\n \r\n {payload && payload.map((x, idx) =>\r\n x.name !== 'NoTooltipItem' ? (\r\n - \r\n \r\n {x.name}\r\n \r\n : \r\n \r\n {Math.round(x.value * 100) / 100}\r\n \r\n
\r\n ) : null\r\n )}\r\n \r\n \r\n )\r\n }\r\n return null\r\n}\r\n","import React, { useContext, useEffect, useState, useMemo } from 'react'\r\nimport {\r\n XAxis,\r\n Line,\r\n YAxis,\r\n Label,\r\n CartesianGrid,\r\n Tooltip,\r\n Legend,\r\n ResponsiveContainer,\r\n Scatter,\r\n ComposedChart\r\n} from 'recharts'\r\nimport dayjs from 'dayjs'\r\n\r\nimport { WidgetDataContext } from '../../../wrappers/WidgetDataContext'\r\nimport CustomTooltip from './CustomTooltip'\r\nimport { formatNumber } from '../../../../utils/numberFormatting'\r\nimport { dateToString } from '../../../../utils/dateFormatting'\r\nimport getHiddenColumns from '../../../../utils/widgets/getHiddenColumns'\r\nimport { ColorContext } from '../../../wrappers/ColorContext'\r\n\r\nconst CustomLegend = ({ legendPayload, bold, setBold, updateHiddenState }) => {\r\n return (\r\n \r\n {legendPayload.map((item, index) => (\r\n - \r\n \r\n \r\n bold && bold === item.value\r\n ? setBold(null)\r\n : setBold(item.value)\r\n }\r\n >\r\n {item.value}\r\n \r\n
\r\n ))}\r\n \r\n )\r\n}\r\n\r\nconst getSeriesFromData = (data, hiddenColumns) => {\r\n // if there is not a 'Value' column, then the series\r\n // values are stored in a column for each series name\r\n const seriesAreDistinctColumns =\r\n typeof Object.keys(data[0]).find((x) => x.includes('Value')) ===\r\n 'undefined'\r\n\r\n if (seriesAreDistinctColumns) {\r\n // get the line chart series names, eg in { Date: , Series A: , Series B: }\r\n // => groupKeys == ['Series A', 'Series B']\r\n\r\n // filter out Date and any hiddenColumns\r\n const groupKeys = data.reduce(\r\n (acc, curr) => [\r\n ...acc,\r\n ...Object.keys(curr).filter((x) => !x.includes('Date')).filter(x => !hiddenColumns.includes(x)),\r\n ],\r\n []\r\n )\r\n\r\n // get a list of all unique groups that are present in the data\r\n // => ['Series A', 'Series B']\r\n const uniqueGroups = [...new Set([...groupKeys])].sort()\r\n\r\n // return data + group names\r\n return [data, uniqueGroups, null]\r\n } else {\r\n // otherwise, the group key is just the column that is not Value or Date\r\n // eg data[0] = {'Value': 0, 'Date': '12/12/12', 'Form Type': 'Wow'}\r\n // => groupKey = 'Form Type'\r\n const groupKey = Object.keys(data[0]).find(\r\n (x) => !(x.includes('Date') || x.includes('Value')) && !(hiddenColumns.includes(x))\r\n )\r\n\r\n // get the set of all unique values in the column specified by groupKey\r\n const uniqueGroups = [...new Set(data.map((x) => x[groupKey]))].sort()\r\n\r\n // aggregate line chart data using unique dates. eg\r\n // [{Date: date1, District: 1, Value: 100}, {Date: date1, District: 2, Value: 200}]\r\n // turns into [{Date: date1, \"1\": 100, \"2\": 200}]\r\n const chartData = [...new Set(data.map((d) => d.Date))].map((date) => {\r\n const dateEntry = { Date: date }\r\n return data\r\n .filter((x) => x.Date === date) // get entries associated w/ the current date\r\n .reduce((acc, curr) => {\r\n const groupName = curr[groupKey]\r\n const value = curr['Value']\r\n return {\r\n ...acc,\r\n [groupName]: value,\r\n }\r\n }, dateEntry)\r\n })\r\n\r\n return [chartData, uniqueGroups, groupKey]\r\n }\r\n}\r\n\r\n\r\nexport default () => {\r\n const { widgetData: widget, showLegend } = useContext(WidgetDataContext)\r\n const { colorMap, updateColorMap, getAssociatedColor } = useContext(ColorContext)\r\n const data = widget ? widget.Data : null\r\n const [legend, setLegend] = useState([])\r\n\r\n const preparedData = data\r\n .filter((d) => !!d.Date) // remove data w/out dates\r\n .map((d) => ({\r\n ...d,\r\n Date: dayjs(d.Date).toDate().getTime(),\r\n })) // transform dates to MM/DD/YYYY format\r\n\r\n const hiddenColumns = getHiddenColumns(widget)\r\n const [chartData, uniqueGroups, groupKey] = getSeriesFromData(preparedData, hiddenColumns)\r\n\r\n // create state to track hidden prop for each individual line\r\n const [hiddenState, setHiddenState] = useState(\r\n uniqueGroups.reduce((acc, curr) => ({ ...acc, [curr]: false }), {})\r\n )\r\n const [bold, setBold] = useState(null)\r\n\r\n useEffect(() => {\r\n updateColorMap(groupKey, uniqueGroups)\r\n }, [uniqueGroups, groupKey])\r\n\r\n \r\n useEffect(() => {\r\n if (showLegend === 'all' && Object.values(hiddenState).includes(true)) {\r\n setHiddenState(\r\n uniqueGroups.reduce(\r\n (acc, curr) => ({ ...acc, [curr]: false }),\r\n {}\r\n )\r\n )\r\n }\r\n if (\r\n showLegend === 'none' &&\r\n Object.values(hiddenState).includes(false)\r\n ) {\r\n setHiddenState(\r\n uniqueGroups.reduce(\r\n (acc, curr) => ({ ...acc, [curr]: true }),\r\n {}\r\n )\r\n )\r\n }\r\n }, [showLegend])\r\n\r\n useEffect(() => {\r\n setLegend(() =>\r\n uniqueGroups.map((groupName, idx) => ({\r\n color: !hiddenState[groupName]\r\n ? getAssociatedColor(groupName, groupKey)\r\n : 'rgb(204, 204, 204)',\r\n type: 'line',\r\n dataKey: groupName,\r\n value: groupName,\r\n hide: hiddenState[groupName] ? true : false,\r\n }))\r\n )\r\n }, [hiddenState, colorMap])\r\n\r\n const colors = useMemo(\r\n () =>\r\n uniqueGroups.reduce((acc, curr, idx) => {\r\n return {\r\n ...acc,\r\n [curr]: getAssociatedColor(curr, groupKey),\r\n }\r\n }, {}),\r\n [uniqueGroups, groupKey, colorMap]\r\n )\r\n\r\n const updateHiddenState = (e) => {\r\n setHiddenState((previousState) => ({\r\n ...previousState,\r\n [e.dataKey]: !previousState[e.dataKey],\r\n }))\r\n }\r\n\r\n const dataMax = Math.max(\r\n ...chartData.map((entry) =>\r\n Math.max(\r\n ...Object.keys(entry).map((key) =>\r\n key !== 'Date' && !hiddenState[key] ? entry[key] : 0\r\n )\r\n )\r\n )\r\n )\r\n const dataMin = Math.min(\r\n ...chartData.map((entry) =>\r\n Math.min(\r\n ...Object.keys(entry).map((key) =>\r\n key !== 'Date' && !hiddenState[key] ? entry[key] : Infinity\r\n )\r\n )\r\n )\r\n )\r\n\r\n // generate a line component for each\r\n // individual line\r\n const Lines = uniqueGroups.map((groupName, idx) => (\r\n \r\n ))\r\n\r\n\r\n // // generate a line component for each\r\n // // individual line\r\n const Scatters = uniqueGroups.map((groupName, idx) => {\r\n return (\r\n !!x[groupName])}\r\n key={`best-fit-${groupName}`}\r\n dataKey={groupName}\r\n lineType=\"fitting\"\r\n name={'NoTooltipItem'}\r\n line={{\r\n stroke: colors[groupName],\r\n strokeWidth: bold === groupName ? 3 : 1,\r\n }}\r\n fill=\"none\"\r\n hide={hiddenState[groupName]}\r\n />\r\n )\r\n })\r\n\r\n return (\r\n \r\n \r\n \r\n dateToString(unixTime)}\r\n >\r\n \r\n \r\n formatNumber(value, 'compact')}\r\n domain={[dataMin, dataMax]}\r\n allowDataOverflow={true}\r\n >\r\n \r\n \r\n \r\n }\r\n verticalAlign=\"top\"\r\n wrapperStyle={{\r\n display: showLegend ? '' : 'none',\r\n top: 1,\r\n left: 0,\r\n overflowX: 'hidden',\r\n overflowY: 'scroll',\r\n alignItems: 'center',\r\n width: '100%',\r\n }}\r\n />\r\n }\r\n formatter={(value) => formatNumber(value)}\r\n labelFormatter={(label) => dateToString(label)}\r\n isAnimationActive={false}\r\n wrapperStyle={\r\n Object.values(hiddenState).filter((x) => x === false)\r\n .length > 10\r\n ? { fontSize: '.75rem' }\r\n : { fontSize: '1rem' }\r\n }\r\n itemStyle={{ padding: '0px' }}\r\n />\r\n {Lines}\r\n {Scatters}\r\n \r\n \r\n )\r\n}\r\n","import React, { useContext, useEffect, useRef, useMemo } from 'react'\r\n\r\nimport { WidgetDataContext } from '../../../wrappers/WidgetDataContext'\r\nimport NoDataComponent from '../NoDataComponent'\r\n\r\nconst TextWidget = () => {\r\n const { widgetData: widget } = useContext(WidgetDataContext)\r\n const textRef = useRef(null)\r\n const data = widget ? widget.Data : null\r\n const options = widget ? widget.WidgetOptions : null\r\n const style = useMemo(() => {\r\n if (options) {\r\n const json = JSON.parse(options)\r\n if (json) {\r\n return json.style\r\n }\r\n }\r\n return null\r\n }, [options])\r\n\r\n useEffect(() => {\r\n textRef.current.setAttribute('style', style)\r\n }, [style])\r\n\r\n if (!(data && data.length && data[0].Value)) {\r\n return \r\n }\r\n\r\n return (\r\n \r\n \r\n {data[0].Value}\r\n \r\n \r\n )\r\n\r\n}\r\n\r\nexport default TextWidget","import React, { useContext } from 'react'\r\n\r\nimport { WidgetDataContext } from '../../wrappers/WidgetDataContext'\r\n\r\nimport WidgetWrapper from './WidgetWrapper'\r\nimport Number from './number/Number'\r\nimport Grid from './grid/Grid'\r\nimport BarChart from './bar/BarChart'\r\nimport LineChart from './line/LineChart'\r\nimport ThemeMap from './map/theme-map/ThemeMap'\r\nimport FeatureMap from './map/feature-map/FeatureMap'\r\nimport ThreeDimensionalMap from './map/3d-map/ThreeDimensionalMap'\r\nimport PieChart from './pie/PieChart'\r\nimport Form from './form/Form'\r\nimport ScatterChart from './scatter/ScatterChart'\r\nimport TextWidget from './text/Text'\r\n\r\nconst getWidgetComponentFromWidgetType = (widgetType) => {\r\n switch (widgetType) {\r\n case 'Number':\r\n return Number\r\n case 'Grid':\r\n return Grid\r\n case 'BarChart':\r\n return BarChart\r\n case 'LineChart':\r\n return LineChart\r\n case 'ScatterChart':\r\n return ScatterChart\r\n case 'ThemeMap':\r\n return ThemeMap\r\n case 'PieChart':\r\n return PieChart\r\n case 'Form':\r\n return Form\r\n case '2DFeatureMap':\r\n return FeatureMap\r\n case '3DFeatureMap':\r\n return ThreeDimensionalMap\r\n case 'Text':\r\n return TextWidget\r\n default:\r\n return 'div'\r\n }\r\n}\r\n\r\nexport default () => {\r\n const { widgetType } = useContext(WidgetDataContext)\r\n const WidgetComponent = getWidgetComponentFromWidgetType(widgetType)\r\n return (\r\n \r\n {React.createElement(WidgetComponent)}\r\n \r\n )\r\n}\r\n","import React, { useContext } from 'react'\r\nimport { Responsive, WidthProvider } from 'react-grid-layout'\r\n\r\nimport { CurrentDashboardContext } from '../../wrappers/CurrentDashboardContext'\r\nimport WidgetDataContextProvider from '../../wrappers/WidgetDataContext'\r\nimport { cols, breakpoints, rowHeight } from '../../../utils/widgets/getLayouts'\r\nimport Widget from './Widget'\r\nimport { Spinner } from '../LoadingSpinner'\r\n\r\nconst ResponsiveGridLayout = WidthProvider(Responsive)\r\n\r\nexport default () => {\r\n const {\r\n currentWidgetGroup: { Widgets: widgets },\r\n editingLayout,\r\n setLayoutState,\r\n layoutState,\r\n expandedWidgetId,\r\n pageLoading,\r\n } = useContext(CurrentDashboardContext)\r\n\r\n const children = React.useMemo(() => {\r\n if (!widgets) {\r\n return null\r\n }\r\n return widgets.map((widget) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n )\r\n })\r\n }, [expandedWidgetId, widgets, editingLayout])\r\n\r\n if (!widgets || !layoutState || !Object.keys(layoutState).length) {\r\n if (pageLoading) {\r\n return \r\n } else {\r\n return null\r\n }\r\n }\r\n\r\n const onLayoutChange = (layout, allLayouts) => {\r\n setLayoutState(allLayouts)\r\n }\r\n\r\n return (\r\n \r\n \r\n {children}\r\n \r\n \r\n )\r\n}\r\n\r\n","import React from 'react'\r\nimport Widgets from '../../elem/widgets/Widgets'\r\n\r\nexport default () => (\r\n \r\n \r\n \r\n)\r\n","import React, { useContext, useState } from 'react'\r\nimport { Redirect } from 'react-router-dom'\r\nimport toast from '../../elem/Toast'\r\nimport AdminLayout from '../../elem/admin/AdminLayout'\r\nimport { FaCheckCircle, FaTimesCircle } from 'react-icons/fa'\r\nimport withConfig from '../../wrappers/withConfig'\r\nimport { APIRequestContext } from '../../wrappers/APIRequestContext'\r\n\r\n\r\n\r\nconst DeleteItem = ({config, location}) => {\r\n const {deleteProps: item} = location\r\n const itemType = item.itemType\r\n const [redirect, setRedirect] = useState(null)\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const { API_URL } = config\r\n\r\n const DELETE = () => {\r\n return {\r\n method: 'DELETE',\r\n }\r\n }\r\n\r\n const deleteItem = (id) => {\r\n authenticatedFetch(`${API_URL}/admin/${itemType}/delete/${id}`, DELETE())\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n `Delete ${itemType}:` +\r\n (e.message ? e.message : `${itemType} delete failed`),\r\n })\r\n })\r\n .finally(() => {\r\n setRedirect(itemType)\r\n })\r\n }\r\n\r\n if (redirect === 'widget') {\r\n return (\r\n \r\n )\r\n }\r\n \r\n if (redirect === 'page') {\r\n return (\r\n \r\n )\r\n }\r\n\r\n if (redirect === 'dashboard') {\r\n return (\r\n \r\n )\r\n }\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n )\r\n}\r\n\r\nexport default withConfig(DeleteItem)\r\n","import React, {useState, useEffect, useContext} from 'react'\r\nimport Select from '../form/Select'\r\nimport toast from '../Toast'\r\nimport withConfig from '../../wrappers/withConfig'\r\nimport { APIRequestContext } from '../../wrappers/APIRequestContext'\r\n\r\n\r\nconst WidgetTypes = ({\r\n config,\r\n control,\r\n initialValue,\r\n disabled,\r\n value,\r\n onChange,\r\n helper,\r\n errors\r\n\r\n}) => {\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const { API_URL } = config\r\n const [widgetTypes, setWidgetTypes] = useState([])\r\n const widgetOptions = widgetTypes.map((x) => ({ label: x, value: x }))\r\n \r\n useEffect(() => {\r\n authenticatedFetch(`${API_URL}/widget/getWidgetTypes`)\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return response.json()\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .then((response) => {\r\n setWidgetTypes(response)\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Widget Types:' +\r\n (e.message\r\n ? e.message\r\n : 'Unable to connect to the server'),\r\n })\r\n })\r\n }, [API_URL])\r\n \r\n return(\r\n \r\n)\r\n }\r\n\r\nexport default withConfig(WidgetTypes)\r\n","import React, { useState, useEffect, useContext } from 'react'\r\nimport Select from '../form/Select'\r\nimport toast from '../Toast'\r\nimport withConfig from '../../wrappers/withConfig'\r\nimport { APIRequestContext } from '../../wrappers/APIRequestContext'\r\n\r\nconst Databases = ({\r\n config,\r\n control,\r\n initialValue,\r\n disabled,\r\n value,\r\n onChange,\r\n errors,\r\n helper\r\n}) => {\r\n const { API_URL } = config\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const [databases, setDatabases] = useState([])\r\n const databaseOptions = databases.map((x) => ({ label: x, value: x }))\r\n\r\n useEffect(() => {\r\n authenticatedFetch(`${API_URL}/admin/getTargetDatabases`)\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return response.json()\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .then((response) => {\r\n setDatabases(response.data)\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Get Databases:' +\r\n (e.message\r\n ? e.message\r\n : 'Unable to connect to the server'),\r\n })\r\n })\r\n }, [API_URL])\r\n\r\n return (\r\n \r\n )\r\n}\r\n\r\nexport default withConfig(Databases)\r\n","import React from 'react'\r\nimport CreatableSelect from 'react-select/creatable'\r\nimport { Controller } from 'react-hook-form'\r\nimport { FaInfoCircle } from 'react-icons/fa'\r\nimport { DefaultTooltip as Tooltip } from '../Tooltip'\r\n\r\nexport default ({\r\n options,\r\n control,\r\n initialValue,\r\n name,\r\n disabled,\r\n label,\r\n helper,\r\n rules,\r\n example,\r\n onChange,\r\n className,\r\n errors,\r\n value,\r\n}) => {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {helper && (\r\n \r\n \r\n \r\n )}\r\n \r\n \r\n }\r\n name={name}\r\n control={control}\r\n defaultValue={\r\n initialValue ? initialValue : null\r\n }\r\n key={`my_unique_select_key__${JSON.stringify(\r\n value\r\n )}`}\r\n value={value}\r\n rules={rules}\r\n onChange={onChange}\r\n />\r\n \r\n \r\n \r\n \r\n\r\n \r\n {errors && errors[name] ? errors[name].message : null}\r\n \r\n \r\n )\r\n}\r\n","import React, { useMemo } from 'react'\r\nimport { useTable, usePagination } from 'react-table'\r\n\r\nimport NoDataComponent from '../../../elem/widgets/NoDataComponent'\r\n\r\nconst createColumns = (data) => {\r\n const keys = data && data.length ? Object.keys(data[0]) : []\r\n return keys.map((key) => ({ Header: key, accessor: key }))\r\n}\r\n\r\nconst PreviewQueryTable = ({ data }) => {\r\n const removeEmpty = (array) => {\r\n array.forEach((obj) => {\r\n Object.keys(obj).forEach((k) => {\r\n if (typeof obj[k] === 'object') {\r\n obj[k] = ''\r\n }\r\n })\r\n })\r\n return array\r\n }\r\n\r\n const columns = useMemo(() => createColumns(data), [data])\r\n const tableData = useMemo(() => removeEmpty(data), [data])\r\n const {\r\n getTableProps,\r\n getTableBodyProps,\r\n headerGroups,\r\n prepareRow,\r\n page,\r\n canPreviousPage,\r\n canNextPage,\r\n pageOptions,\r\n nextPage,\r\n previousPage,\r\n state: { pageIndex },\r\n } = useTable(\r\n {\r\n columns,\r\n data: tableData,\r\n initialState: { pageIndex: 0, pageSize: 5 },\r\n },\r\n usePagination\r\n )\r\n\r\n if (!(data && data.length)) {\r\n return \r\n }\r\n\r\n return (\r\n \r\n Response Data Preview\r\n \r\n \r\n {headerGroups.map((headerGroup) => (\r\n \r\n {headerGroup.headers.map((column) => (\r\n \r\n {column.render('Header')}\r\n | \r\n ))}\r\n \r\n ))}\r\n \r\n \r\n {page.map((row, i) => {\r\n prepareRow(row)\r\n return (\r\n \r\n {row.cells.map((cell) => {\r\n return (\r\n \r\n {cell.render('Cell')}\r\n | \r\n )\r\n })}\r\n \r\n )\r\n })}\r\n \r\n \r\n \r\n {' '}\r\n \r\n Page{' '}\r\n \r\n {pageIndex + 1} of {pageOptions.length}\r\n {' '}\r\n \r\n {' '}\r\n \r\n \r\n )\r\n}\r\n\r\nexport default PreviewQueryTable\r\n","import React, { useContext, useState } from 'react'\r\nimport toast from '../../../elem/Toast'\r\nimport { useForm } from 'react-hook-form'\r\nimport Select from '../../../elem/form/Select'\r\nimport WidgetTypes from '../../../elem/admin/SelectWidgetTypes'\r\nimport Databases from '../../../elem/admin/SelectDatabases'\r\nimport Input from '../../../elem/form/TextInput'\r\nimport Checkbox from '../../../elem/form/Checkbox'\r\nimport NumberInput from '../../../elem/form/NumberInput'\r\nimport MultiSelect from '../../../elem/form/MultiSelect'\r\nimport PreviewQueryTable from './PreviewQueryTable'\r\nimport { Link } from 'react-router-dom'\r\nimport withConfig from '../../../wrappers/withConfig'\r\nimport { APIRequestContext } from '../../../wrappers/APIRequestContext'\r\n\r\nconst CreateWidgetForm = ({ config, closeForm, back, page, dateFilter }) => {\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const { register, unregister, handleSubmit, control, errors } = useForm({\r\n mode: 'onChange',\r\n })\r\n const { API_URL } = config\r\n const [id, setId] = useState(null)\r\n const [type, setType] = useState('')\r\n const [query, setQuery] = useState(null)\r\n const [string, setString] = useState(null)\r\n const [preview, setPreview] = useState(null)\r\n const functionOptions = [\r\n { value: 'COUNT', label: 'Count (COUNT)' },\r\n { value: 'SUM', label: 'Sum (SUM)' },\r\n { value: 'AVG', label: 'Average (AVG)' },\r\n { value: 'MIN', label: 'Minimum (MIN)' },\r\n { value: 'MAX', label: 'Maximum (MAX)' },\r\n ]\r\n const initialState = {\r\n AggregateFunction: 'COUNT',\r\n AggregateColumn: '*',\r\n ApplyFiltersToBaseQuery: false,\r\n ApplyDateFilter: dateFilter,\r\n DetailKey: '',\r\n DetailKeyValue: '',\r\n GroupBy: '',\r\n NumRows: null,\r\n }\r\n\r\n const [state, setState] = useState(initialState)\r\n\r\n if (id) {\r\n closeForm(id)\r\n }\r\n\r\n const POST = (widget) => {\r\n return {\r\n method: 'POST',\r\n mode: 'cors',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Access-Control-Allow-Origin': '*',\r\n 'Access-Control-Allow-Headers':\r\n 'Access-Control-Allow-Origin, X-Requested-With, Content-Type, Accept',\r\n },\r\n body: JSON.stringify(widget),\r\n }\r\n }\r\n\r\n const generateQueryString = (data) => {\r\n authenticatedFetch(`${API_URL}/widget/generate`, POST(data))\r\n .then(async (response) => {\r\n const queryString = await response.text()\r\n if (response.ok) {\r\n return queryString\r\n } else {\r\n throw new Error(queryString)\r\n }\r\n })\r\n .then((response) => {\r\n setString(response)\r\n setQuery(response)\r\n })\r\n .catch((e) => {\r\n setString(e.message)\r\n })\r\n }\r\n\r\n const previewQuery = (queryString) => {\r\n setPreview(null)\r\n authenticatedFetch(`${API_URL}/widget/preview`, POST(queryString))\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return response.json()\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .then((response) => {\r\n setPreview(response.data)\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Create Widget:' +\r\n (e.message ? e.message : 'Query preview failed'),\r\n })\r\n })\r\n }\r\n\r\n const createWidget = (widget) => {\r\n authenticatedFetch(`${API_URL}/admin/widget/create`, POST(widget))\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return response.json()\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .then((response) => {\r\n setId(response)\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Create Widget:' +\r\n (e.message ? e.message : 'Widget create failed'),\r\n })\r\n })\r\n }\r\n\r\n const handleSelectChange = (event) => {\r\n setState({ ...state, [event[1].name]: event[0].value })\r\n if (event[1].name === 'WidgetType') {\r\n setType(event[0].value)\r\n }\r\n return event\r\n }\r\n\r\n const handleMultiChange = (event) => {\r\n event[0]\r\n ? setState({\r\n ...state,\r\n [event[1].name]: event[0]\r\n .map((x) => x.value)\r\n .toString()\r\n .replaceAll(',', '|'),\r\n })\r\n : delete state[event[1].name]\r\n return event[0]\r\n }\r\n\r\n const formChange = (event) => {\r\n if (event.target.name !== '') {\r\n setState({\r\n ...state,\r\n [event.target.name]: event.target.value,\r\n })\r\n }\r\n if (event.target.type === 'checkbox') {\r\n setState({\r\n ...state,\r\n [event.target.name]: event.target.checked,\r\n })\r\n }\r\n if (event.target.name === 'NumRows') {\r\n setState({\r\n ...state,\r\n NumRows:\r\n event.target.value !== ''\r\n ? Number(event.target.value)\r\n : null,\r\n })\r\n }\r\n }\r\n\r\n const handleClick = () => {\r\n const queryString = {\r\n TargetDatabase: state.TargetDatabase,\r\n QueryString: string,\r\n }\r\n previewQuery(queryString)\r\n }\r\n\r\n const trimData = (data) => {\r\n Object.keys(data).map((key) =>\r\n typeof data[key] === 'string'\r\n ? (data[key] = data[key].replace(/\\s\\s+/g, ' ').trim())\r\n : data[key]\r\n )\r\n return data\r\n }\r\n\r\n const onSubmit = (data) => {\r\n createWidget({ ...trimData(state), PageId: page })\r\n }\r\n\r\n const validateOptions = (value) => {\r\n try {\r\n JSON.parse(value)\r\n return true\r\n } catch {\r\n return false\r\n }\r\n }\r\n\r\n return (\r\n <>\r\n \r\n {preview ? : null}\r\n >\r\n )\r\n}\r\n\r\nexport default withConfig(CreateWidgetForm)\r\n","import React, { useState, useContext } from 'react'\r\nimport CreateWidgetForm from './CreateWidgetForm'\r\nimport { Redirect } from 'react-router-dom'\r\nimport AdminLayout from '../../../elem/admin/AdminLayout'\r\nimport {CurrentDashboardContext} from '../../../wrappers/CurrentDashboardContext'\r\n\r\nconst CreateWidget = () => {\r\n\r\n const [widgetId, setWidgetId] = useState(null)\r\n const { currentWidgetGroup, currentDashboard, pageId, setNewObject } = useContext(CurrentDashboardContext)\r\n const id = Number(pageId)\r\n\r\n if (!currentDashboard || !Object.keys(currentWidgetGroup).length) {\r\n return null\r\n }\r\n \r\n const closeForm = (response) => {\r\n setWidgetId(response)\r\n }\r\n \r\n if (widgetId) {\r\n setNewObject(widgetId)\r\n return (\r\n \r\n )\r\n }\r\n return (\r\n \r\n \r\n \r\n )\r\n}\r\n\r\nexport default CreateWidget","import React, { useContext, useState } from 'react'\r\nimport toast from '../../../elem/Toast'\r\nimport { useForm } from 'react-hook-form'\r\nimport Select from '../../../elem/form/Select'\r\nimport WidgetTypes from '../../../elem/admin/SelectWidgetTypes'\r\nimport Databases from '../../../elem/admin/SelectDatabases'\r\nimport Input from '../../../elem/form/TextInput'\r\nimport Checkbox from '../../../elem/form/Checkbox'\r\nimport NumberInput from '../../../elem/form/NumberInput'\r\nimport MultiSelect from '../../../elem/form/MultiSelect'\r\nimport PreviewQueryTable from './PreviewQueryTable'\r\nimport withConfig from '../../../wrappers/withConfig'\r\n\r\nimport { FaEdit, FaCheckCircle, FaTimesCircle } from 'react-icons/fa'\r\nimport { APIRequestContext } from '../../../wrappers/APIRequestContext'\r\n\r\nconst EditWidgetForm = ({ config, widget, closeForm }) => {\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const { register, handleSubmit, control, errors, unregister } = useForm({\r\n mode: 'onChange',\r\n })\r\n const { API_URL } = config\r\n const [type, setType] = useState(widget.WidgetType)\r\n const [query, setQuery] = useState(null)\r\n const [string, setString] = useState(null)\r\n const [preview, setPreview] = useState(null)\r\n const functionOptions = [\r\n { value: 'COUNT', label: 'Count (COUNT)' },\r\n { value: 'SUM', label: 'Sum (SUM)' },\r\n { value: 'AVG', label: 'Average (AVG)' },\r\n { value: 'MIN', label: 'Minimum (MIN)' },\r\n { value: 'MAX', label: 'Maximum (MAX)' },\r\n ]\r\n\r\n const [editing, setEditing] = useState(false)\r\n\r\n const deleteKeys = (object) => {\r\n Object.keys(object).forEach((key) => {\r\n if (object[key] === null && key !== 'NumRows') {\r\n object[key] = ''\r\n }\r\n })\r\n return object\r\n }\r\n\r\n const [state, setState] = useState(deleteKeys(widget))\r\n\r\n const initialFunction = functionOptions.filter(\r\n (option) => option.value === state.AggregateFunction\r\n )[0]\r\n\r\n let formClass = editing ? 'button is-info is-small' : 'hidden'\r\n let textClass = editing ? 'hidden' : 'button is-info is-small'\r\n\r\n const POST = (widget) => {\r\n return {\r\n method: 'POST',\r\n mode: 'cors',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Access-Control-Allow-Origin': '*',\r\n 'Access-Control-Allow-Headers':\r\n 'Access-Control-Allow-Origin, X-Requested-With, Content-Type, Accept',\r\n },\r\n body: JSON.stringify(widget),\r\n }\r\n }\r\n\r\n const PUT = (page) => {\r\n return {\r\n method: 'PUT',\r\n mode: 'cors',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Access-Control-Allow-Origin': '*',\r\n 'Access-Control-Allow-Headers':\r\n 'Access-Control-Allow-Origin, X-Requested-With, Content-Type, Accept',\r\n },\r\n body: JSON.stringify(page),\r\n }\r\n }\r\n\r\n const generateQueryString = (data) => {\r\n authenticatedFetch(`${API_URL}/widget/generate`, POST(data))\r\n .then(async (response) => {\r\n const responseText = await response.text()\r\n if (response.ok) {\r\n return responseText\r\n } else {\r\n throw new Error(responseText)\r\n }\r\n })\r\n .then((response) => {\r\n setString(response)\r\n setQuery(response)\r\n })\r\n .catch((e) => {\r\n setString(e.message)\r\n })\r\n }\r\n\r\n const previewQuery = (queryString) => {\r\n setPreview(null)\r\n authenticatedFetch(`${API_URL}/widget/preview`, POST(queryString))\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return response.json()\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .then((response) => {\r\n setPreview(response.data)\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Create Widget:' +\r\n (e.message ? e.message : 'Query preview failed'),\r\n })\r\n })\r\n }\r\n\r\n const editWidget = (editedWidget) => {\r\n authenticatedFetch(\r\n `${API_URL}/admin/widget/edit/${widget.WidgetId}`,\r\n PUT(editedWidget)\r\n )\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Edit Dashboard:' +\r\n (e.message ? e.message : 'Dashboard edit failed'),\r\n })\r\n })\r\n .then(() => {\r\n setState(editedWidget)\r\n setEditing(false)\r\n closeForm(editedWidget)\r\n })\r\n }\r\n\r\n const handleSelectChange = (event) => {\r\n setType(event[0].value)\r\n setState({ ...state, [event[1].name]: event[0].value })\r\n return event\r\n }\r\n\r\n const handleMultiChange = (event) => {\r\n event[0]\r\n ? setState({\r\n ...state,\r\n [event[1].name]: event[0]\r\n .map((x) => x.value)\r\n .toString()\r\n .replaceAll(',', '|'),\r\n })\r\n : setState({ ...state, [event[1].name]: null })\r\n return event[0]\r\n }\r\n\r\n const formChange = (event) => {\r\n if (event.target.name !== '') {\r\n setState({\r\n ...state,\r\n [event.target.name]: event.target.value,\r\n })\r\n }\r\n if (event.target.type === 'checkbox') {\r\n setState({\r\n ...state,\r\n [event.target.name]: event.target.checked,\r\n })\r\n }\r\n if (event.target.name === 'NumRows') {\r\n setState({\r\n ...state,\r\n NumRows:\r\n event.target.value !== ''\r\n ? Number(event.target.value)\r\n : null,\r\n })\r\n }\r\n }\r\n\r\n const handleClick = () => {\r\n const queryString = {\r\n TargetDatabase: state.TargetDatabase,\r\n QueryString: string,\r\n }\r\n previewQuery(queryString)\r\n }\r\n\r\n const trimData = (data) => {\r\n Object.keys(data).map((key) =>\r\n typeof data[key] === 'string'\r\n ? (data[key] = data[key].replace(/\\s\\s+/g, ' ').trim())\r\n : data[key]\r\n )\r\n return data\r\n }\r\n\r\n const onSubmit = (data) => {\r\n editWidget(trimData(state))\r\n }\r\n\r\n const cancel = () => {\r\n setState(widget)\r\n setEditing(false)\r\n }\r\n\r\n const validateOptions = (value) => {\r\n try {\r\n JSON.parse(value)\r\n return true\r\n } catch {\r\n return false\r\n }\r\n }\r\n\r\n return (\r\n <>\r\n \r\n {preview ? : null}\r\n >\r\n )\r\n}\r\n\r\nexport default withConfig(EditWidgetForm)\r\n","import React, { useEffect, useState, useContext } from 'react'\r\nimport EditWidgetForm from './EditWidgetForm'\r\nimport toast from '../../../elem/Toast'\r\nimport { Redirect } from 'react-router-dom'\r\nimport { CurrentDashboardContext } from '../../../wrappers/CurrentDashboardContext'\r\nimport AdminLayout from '../../../elem/admin/AdminLayout'\r\nimport withConfig from '../../../wrappers/withConfig'\r\nimport { APIRequestContext } from '../../../wrappers/APIRequestContext'\r\n\r\nconst EditWidget = ({config}) => {\r\n const {API_URL} = config\r\n const [widget, setWidget] = useState(null)\r\n const [edited, setEdited] = useState(false)\r\n const {widgetId, setNewObject} = useContext(CurrentDashboardContext)\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n \r\n useEffect(() => {\r\n authenticatedFetch(`${API_URL}/Widget/${widgetId}?noData`)\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return response.json()\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .then((response) => {\r\n setWidget(response.data)\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Widget Types:' +\r\n (e.message\r\n ? e.message\r\n : 'Unable to connect to the server'),\r\n })\r\n })\r\n }, [API_URL, widgetId])\r\n\r\n if (!widget) {\r\n return null\r\n }\r\n\r\n const closeForm = () => {\r\n setEdited(true)\r\n }\r\n\r\n if(edited){\r\n setNewObject(widget)\r\n return\r\n }\r\n\r\n return (\r\n \r\n )\r\n}\r\n\r\nexport default withConfig(EditWidget)","import React, { useEffect, useState, useContext } from 'react'\r\nimport toast from '../../../elem/Toast'\r\nimport AdminLayout from '../../../elem/admin/AdminLayout'\r\nimport { CurrentDashboardContext } from '../../../wrappers/CurrentDashboardContext'\r\nimport Widgets from '../../../elem/widgets/Widgets'\r\nimport getLayouts from '../../../../utils/widgets/getLayouts'\r\nimport { Redirect } from 'react-router-dom'\r\nimport withConfig from '../../../wrappers/withConfig'\r\nimport { APIRequestContext } from '../../../wrappers/APIRequestContext'\r\n\r\nconst PageLayoutEditor = ({config}) => {\r\n const {API_URL} = config\r\n const {\r\n currentWidgetGroup,\r\n setLayoutState,\r\n layoutState,\r\n setLayoutEditingState,\r\n } = useContext(CurrentDashboardContext)\r\n const { authenticatedFetch } = useContext(APIRequestContext)\r\n const [edited, setEdited] = useState(null)\r\n const { Widgets: widgets } = currentWidgetGroup\r\n const [initialState, setInitialState] = useState(getLayouts(widgets))\r\n\r\n useEffect(() => {\r\n setLayoutEditingState(true)\r\n !Object.keys(layoutState).length\r\n ? setInitialState(getLayouts(currentWidgetGroup.Widgets))\r\n : setInitialState(layoutState)\r\n }, [currentWidgetGroup])\r\n\r\n if (!widgets && !Object.keys(layoutState).length) {\r\n return null\r\n }\r\n\r\n const PUT = (page) => {\r\n return {\r\n method: 'PUT',\r\n mode: 'cors',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Access-Control-Allow-Origin': '*',\r\n 'Access-Control-Allow-Headers':\r\n 'Access-Control-Allow-Origin, X-Requested-With, Content-Type, Accept',\r\n },\r\n body: JSON.stringify(page),\r\n }\r\n }\r\n\r\n const updateLayout = (layouts) => {\r\n const pageLayout = {\r\n PageId: widgets[0].PageId,\r\n Widgets: layouts.map((layout) => ({\r\n Width: layout.w,\r\n Height: layout.h,\r\n X: layout.x,\r\n Y: layout.y,\r\n WidgetId: Number(layout.i.replace(/[^0-9.]+/g, '')),\r\n })),\r\n }\r\n \r\n authenticatedFetch(`${API_URL}/admin/updatePageLayout`, PUT(pageLayout))\r\n .then(async (response) => {\r\n if (response.ok) {\r\n return\r\n } else {\r\n const error = await response.text()\r\n throw new Error(error)\r\n }\r\n })\r\n .then(() => {\r\n setLayoutState(layoutState)\r\n setInitialState(layoutState)\r\n setEdited(true)\r\n })\r\n .catch((e) => {\r\n toast({\r\n level: 'error',\r\n message:\r\n 'Page Layout:' +\r\n (e.message\r\n ? e.message\r\n : 'Unable to connect to the server'),\r\n })\r\n })\r\n }\r\n\r\n if (edited) {\r\n return (\r\n \r\n )\r\n }\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n )\r\n}\r\n\r\nexport default withConfig(PageLayoutEditor)\r\n","export default {\r\n background: {},\r\n backgroundMask: {\r\n cover: {\r\n color: {\r\n value: '#fff',\r\n },\r\n opacity: 1,\r\n },\r\n enable: false,\r\n },\r\n detectRetina: true,\r\n fpsLimit: 30,\r\n infection: {\r\n cure: false,\r\n delay: 0,\r\n enable: false,\r\n infections: 0,\r\n stages: [],\r\n },\r\n interactivity: {\r\n detectsOn: 'canvas',\r\n events: {\r\n onClick: {\r\n enable: false,\r\n mode: 'push',\r\n },\r\n onDiv: {\r\n elementId: '',\r\n enable: false,\r\n mode: [],\r\n },\r\n onHover: {\r\n enable: false,\r\n mode: 'repulse',\r\n parallax: {\r\n enable: false,\r\n force: 60,\r\n smooth: 10,\r\n },\r\n },\r\n resize: true,\r\n },\r\n modes: {\r\n bubble: {\r\n distance: 400,\r\n duration: 2,\r\n opacity: 0.8,\r\n size: 40,\r\n },\r\n connect: {\r\n distance: 80,\r\n links: {\r\n opacity: 0.5,\r\n },\r\n radius: 60,\r\n },\r\n grab: {\r\n distance: 400,\r\n links: {\r\n opacity: 1,\r\n },\r\n },\r\n push: {\r\n quantity: 4,\r\n },\r\n remove: {\r\n quantity: 2,\r\n },\r\n repulse: {\r\n distance: 200,\r\n duration: 0.4,\r\n speed: 1,\r\n },\r\n slow: {\r\n factor: 3,\r\n radius: 200,\r\n },\r\n },\r\n },\r\n particles: {\r\n collisions: {\r\n enable: false,\r\n mode: 'bounce',\r\n },\r\n color: {\r\n value: 'rgb(163, 9, 9)',\r\n animation: {\r\n enable: false,\r\n speed: 1,\r\n sync: true,\r\n },\r\n },\r\n links: {\r\n blink: false,\r\n color: {\r\n value: 'rgb(163, 9, 9)',\r\n },\r\n consent: false,\r\n distance: 150,\r\n enable: true,\r\n opacity: 0.4,\r\n shadow: {\r\n blur: 5,\r\n color: {\r\n value: 'lime',\r\n },\r\n enable: false,\r\n },\r\n triangles: {\r\n enable: false,\r\n },\r\n width: 1,\r\n warp: false,\r\n },\r\n move: {\r\n attract: {\r\n enable: false,\r\n rotate: {\r\n x: 600,\r\n y: 1200,\r\n },\r\n },\r\n direction: 'none',\r\n enable: true,\r\n noise: {\r\n delay: {\r\n random: {\r\n enable: false,\r\n minimumValue: 0,\r\n },\r\n value: 0,\r\n },\r\n enable: false,\r\n factor: {\r\n horizontal: {\r\n value: 50,\r\n offset: 0,\r\n },\r\n vertical: {\r\n value: 10,\r\n offset: 40000,\r\n },\r\n },\r\n },\r\n outMode: 'out',\r\n random: false,\r\n speed: 2,\r\n straight: false,\r\n trail: {\r\n enable: false,\r\n length: 10,\r\n fillColor: {\r\n value: '#000000',\r\n },\r\n },\r\n vibrate: false,\r\n warp: false,\r\n },\r\n number: {\r\n density: {\r\n enable: true,\r\n area: 800,\r\n factor: 1000,\r\n },\r\n limit: 0,\r\n value: 80,\r\n },\r\n opacity: {\r\n animation: {\r\n enable: false,\r\n minimumValue: 0.1,\r\n speed: 1,\r\n sync: false,\r\n },\r\n random: {\r\n enable: false,\r\n minimumValue: 1,\r\n },\r\n value: 0.5,\r\n },\r\n rotate: {\r\n animation: {\r\n enable: false,\r\n speed: 0,\r\n sync: false,\r\n },\r\n direction: 'clockwise',\r\n random: false,\r\n value: 0,\r\n },\r\n shadow: {\r\n blur: 0,\r\n color: {\r\n value: '#000000',\r\n },\r\n enable: false,\r\n offset: {\r\n x: 0,\r\n y: 0,\r\n },\r\n },\r\n shape: {\r\n options: {\r\n character: {\r\n fill: true,\r\n close: true,\r\n font: 'Verdana',\r\n style: '',\r\n value: '*',\r\n weight: '400',\r\n },\r\n char: {\r\n fill: true,\r\n close: true,\r\n font: 'Verdana',\r\n style: '',\r\n value: '*',\r\n weight: '400',\r\n },\r\n image: {\r\n fill: true,\r\n close: true,\r\n height: 100,\r\n replaceColor: false,\r\n src:\r\n 'https://cdn.matteobruni.it/images/particles/github.svg',\r\n width: 100,\r\n },\r\n images: {\r\n fill: true,\r\n close: true,\r\n height: 100,\r\n replaceColor: false,\r\n src:\r\n 'https://cdn.matteobruni.it/images/particles/github.svg',\r\n width: 100,\r\n },\r\n polygon: {\r\n fill: true,\r\n close: true,\r\n sides: 5,\r\n },\r\n star: {\r\n fill: true,\r\n close: true,\r\n sides: 5,\r\n },\r\n },\r\n type: 'circle',\r\n },\r\n size: {\r\n animation: {\r\n destroy: 'none',\r\n enable: false,\r\n minimumValue: 0.1,\r\n speed: 40,\r\n startValue: 'max',\r\n sync: false,\r\n },\r\n random: {\r\n enable: true,\r\n minimumValue: 1,\r\n },\r\n value: 5,\r\n },\r\n stroke: {\r\n color: {\r\n value: '#000000',\r\n },\r\n width: 0,\r\n opacity: 1,\r\n },\r\n twinkle: {\r\n lines: {\r\n enable: false,\r\n frequency: 0.05,\r\n opacity: 1,\r\n },\r\n particles: {\r\n enable: false,\r\n frequency: 0.05,\r\n opacity: 1,\r\n },\r\n },\r\n },\r\n pauseOnBlur: true,\r\n}","import React from 'react'\r\n// import { Link } from 'react-router-dom'\r\nimport Particles from 'react-particles-js'\r\nimport particleConfig from './particleConfig'\r\n\r\nconst ErrorFallback = ({ errorMessage }) => {\r\n\r\n\r\n return(\r\n <>\r\n \r\n\r\n \r\n \r\n Something went wrong...\r\n {errorMessage} \r\n {/* resetError()}\r\n to={{\r\n pathname: `/`,\r\n }}\r\n >\r\n Return Home\r\n */}\r\n \r\n \r\n >\r\n)}\r\n\r\nexport default ErrorFallback\r\n","import React from 'react'\r\nimport ErrorFallback from './ErrorFallback'\r\n\r\n\r\n\r\nexport default class ErrorBoundary extends React.Component {\r\n state = { hasError: false, errorMessage: null, errorKey: 0 }\r\n \r\n componentDidCatch(error) {\r\n const message = error.toString().slice(error.toString().indexOf(\":\") + 2)\r\n this.setState((state) => ({ hasError: true, errorMessage: message, errorKey: state.errorKey + 1 }))\r\n }\r\n\r\n \r\n render() {\r\n if (this.state.hasError) {\r\n return ( \r\n \r\n )\r\n }\r\n \r\n return this.props.children; \r\n }\r\n }","import React from 'react'\r\nimport ErrorFallback from './ErrorFallback'\r\n\r\n\r\nconst PageNotFound = () => (\r\n \r\n)\r\n\r\nexport default PageNotFound\r\n","const urls = {\r\n home: '/',\r\n userCallback: '/user/callback',\r\n login: '/login'\r\n}\r\n\r\nexport default urls","import React, { useEffect } from 'react'\r\n// import { useLocation} from 'react-router-dom'\r\n\r\nimport urls from '../../../utils/constants/urls'\r\nimport LoadingSpinner from '../../elem/LoadingSpinner'\r\n\r\nexport default () => {\r\n // const location = useLocation()\r\n useEffect(() => {\r\n const t = setTimeout(() => {\r\n window.location = urls.home\r\n }, 5000)\r\n return () => clearTimeout(t)\r\n }, [])\r\n // if (location.search.includes('access_denied')) {\r\n // window.location = urls.home\r\n // }\r\n \r\n return \r\n}\r\n","import React, { useContext } from 'react'\r\nimport LandingPageWrapper from '../../elem/landing-page/LandingPageWrapper'\r\nimport { UserContext } from '../../wrappers/UserContext'\r\nimport withConfig from '../../wrappers/withConfig'\r\n\r\nconst LoginPage = withConfig(({ config }) => {\r\n const { client } = useContext(UserContext)\r\n const { CONFIGURED_URLS } = config\r\n\r\n return (\r\n \r\n \r\n \r\n Please {' '}\r\n \r\n Login\r\n \r\n {' '} or {' '}\r\n \r\n Register\r\n \r\n {' '}to access dashboards\r\n \r\n \r\n \r\n )\r\n})\r\n\r\nexport default LoginPage\r\n","import React, { useContext, useEffect, useState } from 'react'\r\nimport { UserContext } from '../../wrappers/UserContext'\r\nimport { Redirect } from 'react-router-dom'\r\nimport { checkRole, getRoles } from '../../../utils/user/permissions'\r\nimport urls from '../../../utils/constants/urls'\r\nimport withConfig from '../../wrappers/withConfig'\r\n\r\nconst RestrictedAccess = withConfig(({ allowedRoles, config, children }) => {\r\n const { client } = useContext(UserContext)\r\n const [hasAccess, setHasAccess] = useState(true)\r\n const { USE_AUTHENTICATION } = config\r\n\r\n useEffect(() => {\r\n const checkClientAccess = async allowedRoles => {\r\n await client.getUser().then(user => {\r\n const roles = getRoles(user)\r\n if (allowedRoles.some(role => checkRole(role, roles))) {\r\n setHasAccess(true)\r\n } else {\r\n setHasAccess(false)\r\n }\r\n })\r\n }\r\n if (USE_AUTHENTICATION) {\r\n checkClientAccess(allowedRoles)\r\n }\r\n }, [client, allowedRoles, USE_AUTHENTICATION])\r\n \r\n if (hasAccess) {\r\n return children\r\n } else {\r\n return \r\n }\r\n})\r\n\r\nexport default RestrictedAccess","import React from 'react'\r\nimport { BrowserRouter as Router, Route, Switch } from 'react-router-dom'\r\nimport CurrentDashboardContextProvider from './components/wrappers/CurrentDashboardContext'\r\nimport DateContextProvider from './components/wrappers/DateContext'\r\nimport DashboardContextProvider from './components/wrappers/DashboardContext'\r\nimport LandingPage from './components/features/landing-page/LandingPage'\r\nimport AdminDashboard from './components/features/admin/AdminDashboard'\r\nimport CreateDashboard from './components/features/admin/dashboard/CreateDashboard'\r\nimport EditDashboard from './components/features/admin/dashboard/EditDashboard'\r\nimport CreatePage from './components/features/admin/page/CreatePage'\r\nimport EditPage from './components/features/admin/page/EditPage'\r\nimport Header from './components/elem/Header'\r\nimport SideNav from './components/features/navigation/SideNav'\r\nimport TopBar from './components/elem/TopBar'\r\nimport DateSelector from './components/elem/date/DateSelector'\r\nimport DashboardPanel from './components/features/panel/DashboardPanel'\r\nimport { Toast as ToastContainer } from './components/elem/Toast'\r\nimport DeleteItem from './components/features/admin/DeleteItem'\r\nimport CreateWidget from './components/features/admin/widget/CreateWidget'\r\nimport EditWidget from './components/features/admin/widget/EditWidget'\r\nimport PageLayoutEditor from './components/features/admin/widget/PageLayoutEditor'\r\nimport ErrorBoundary from './components/features/errors/ErrorBoundary'\r\nimport PageNotFound from './components/features/errors/PageNotFound'\r\nimport ColorContextProvider from './components/wrappers/ColorContext'\r\nimport UserContextProvider from './components/wrappers/UserContext'\r\nimport APIRequestContextProvider from './components/wrappers/APIRequestContext'\r\nimport CallbackComponent from './components/features/user/CallbackComponent'\r\nimport urls from './utils/constants/urls'\r\nimport LoginPage from './components/features/user/LoginPage'\r\nimport RestrictedAccess from './components/features/user/RestrictedAccess'\r\n\r\nexport default () => (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n (\r\n \r\n \r\n \r\n )}\r\n />\r\n \r\n \r\n \r\n \r\n \r\n (\r\n \r\n \r\n \r\n \r\n \r\n )}\r\n />\r\n (\r\n \r\n \r\n \r\n \r\n \r\n )}\r\n />\r\n (\r\n \r\n \r\n \r\n \r\n \r\n )}\r\n />\r\n (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n )}\r\n />\r\n (\r\n \r\n \r\n \r\n \r\n \r\n )}\r\n />\r\n (\r\n \r\n \r\n \r\n \r\n \r\n )}\r\n />\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n)\r\n","import 'react-app-polyfill/ie11'\r\nimport 'react-app-polyfill/stable'\r\nimport React from 'react'\r\nimport ReactDOM from 'react-dom'\r\nimport './styles/index.scss'\r\nimport App from './App'\r\n\r\nReactDOM.render(\r\n \r\n \r\n ,\r\n document.getElementById('root')\r\n)\r\n"],"sourceRoot":""} | |