onInputChange({filter, value})}
+ sx={{flex: "1 1 auto"}}
+ />
+ );
+}
+
+export function MinMax({filter, ...rest}: {filter: FilterItem}) {
+ const actions = useActions();
+
+ function onInputChangeMinMax(props: {filter: FilterItem; min: number | ""; max: number | ""}) {
+ const {filter, min, max} = props;
+ const conditions =
+ getFiltersConditions(getFilterFn(filter) || "greaterThan", [Number(min), Number(max)]) || {};
+ const active = Boolean(min) && Boolean(max);
+
+ actions.updateFilter(buildFilter({...filter, active, ...conditions}));
+ }
+
+ function getFilterValue(condition?: [`${Comparison}`, string, number]) {
+ if (condition) {
+ return isNotValid(condition[2]) || condition[2] === 0 ? "" : condition[2];
+ }
+ return "";
+ }
+
+ const min = getFilterValue(filter.conditionOne);
+ const max = getFilterValue(filter.conditionTwo);
+ return (
+
+ onInputChangeMinMax({filter, min: value, max})}
+ {...rest}
+ />
+ onInputChangeMinMax({filter, min, max: value})}
+ {...rest}
+ />
+
+ );
+}
+
+export function FilterFnsMenu({filter}: {filter: FilterItem}) {
+ const actions = useActions();
+
+ return (
+ <>
+
+ >
+ );
+}
+
+function MeasuresOptions() {
+ // param measures
+ const [activeFilter, setActiveFilter] = useState(false);
+ const {code: locale} = useSelector(selectLocale);
+ const itemMap = useSelector(selectMeasureMap);
+ const filtersMap = useSelector(selectFilterMap);
+ const filtersItems = useSelector(selectFilterItems);
+ // server
+ const measureMap = useSelector(selectOlapMeasureMap);
+ const measures = useSelector(selectOlapMeasureItems);
+ //actions
+ const actions = useActions();
+
+ function handlerCreateMeasure(data: MeasureItem) {
+ const measure = buildMeasure(data);
+ actions.updateMeasure(measure);
+ return measure;
+ }
+ function handlerCreateFilter(data: FilterItem) {
+ const filter = buildFilter(data);
+ actions.updateFilter(filter);
+ return filter;
+ }
+
+ const filteredItems = useMemo(() => {
+ return filterMap(measures, (m: MeasureItem) => {
+ const measure = itemMap[m.name] || handlerCreateMeasure({...m, active: false});
+ const foundFilter = filtersMap[m.name] || filtersItems.find(f => f.measure === measure.name);
+
+ const filter =
+ foundFilter ||
+ handlerCreateFilter({
+ measure: measure.name,
+ active: false,
+ key: measure.name
+ } as FilterItem);
+ return {measure, filter};
+ });
+ }, [itemMap, measures, filtersMap, filtersItems, locale]);
+
+ const activeItems = filteredItems.filter(f => isActiveItem(f.measure));
+
+ const options = filteredItems.map(({measure, filter}) => {
+ const filterFn = getFilterFn(filter);
+ const text = getFilterfnText(filterFn);
+ const isBetween = filterFn === "between";
+ const checked = activeItems.map(active => active.measure.name).includes(measure.name);
+ return (
+
+
+ {
+ actions.updateMeasure({...measure, active: !measure.active});
+ actions.updateFilter({...filter, active: checked ? false : true});
+ }}
+ checked={checked}
+ label={measure.name}
+ />
+
+
+ setActiveFilter(value => !value)}>
+ {activeFilter ? : }
+
+
+
+ {activeFilter && (
+
+ {isBetween ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+ );
+ });
+
+ return options;
+}
+
+export default AddColumnsDrawer;
diff --git a/src/components/ExplorerContent.tsx b/src/components/ExplorerContent.tsx
index 922d245..dd83f78 100644
--- a/src/components/ExplorerContent.tsx
+++ b/src/components/ExplorerContent.tsx
@@ -1,13 +1,7 @@
import React, {useState, useEffect, useMemo} from "react";
import {ServerConfig} from "@datawheel/olap-client";
import {TranslationContextProps} from "@datawheel/use-translation";
-import {
- CSSObject,
- Center,
- createStyles,
- Header,
- useMantineTheme
-} from "@mantine/core";
+import {CSSObject, Center, createStyles, Header, useMantineTheme} from "@mantine/core";
import {useSelector} from "react-redux";
import {useSetup} from "../hooks/setup";
import {useTranslation} from "../hooks/translation";
@@ -18,7 +12,7 @@ import {ExplorerResults} from "./ExplorerResults";
import SideBar, {SideBarProvider, SideBarItem} from "./SideBar";
import ParamsExplorer from "./ParamsExplorer";
import {HomeSVG} from "./icons";
-
+import {AppProviders} from "../context";
const useStyles = createStyles((theme, params: {height: CSSObject["height"]}) => ({
container: {
height: "100%",
@@ -30,17 +24,16 @@ const useStyles = createStyles((theme, params: {height: CSSObject["height"]}) =>
root: {
display: "flex",
flexFlow: "column nowrap",
- height: "calc(100% - 50px)",
+ height: "calc(100% - 70px)",
[theme.fn.largerThan("md")]: {
flexDirection: "row"
// height: params.height,
// width: "100%"
}
},
-
flexCol: {
flex: "1 1 auto",
- height: "calc(100vh - 50px)",
+ height: "calc(100vh - 70px)",
[theme.fn.largerThan("md")]: {
width: 0
}
@@ -91,37 +84,17 @@ export function ExplorerContent(props: {
-
-
-
-
-
-
-
-
- {/*
*/}
-
+
+
+
+
+
+
+
+
+
+
);
-
- // return (
- //
-
- //
- // {isSetupDone && serverState.online && props.withMultiQuery
- // ?
- // : null
- // }
- // {isSetupDone && serverState.online
- // ?
- // : null
- // }
- //
- //
- // );
}
diff --git a/src/components/ExplorerParams.tsx b/src/components/ExplorerParams.tsx
index 5aa5cb5..ce6935e 100644
--- a/src/components/ExplorerParams.tsx
+++ b/src/components/ExplorerParams.tsx
@@ -10,7 +10,7 @@ import {ButtonExecuteQuery} from "./ButtonExecuteQuery";
import {CollapsiblePanel} from "./Layout/CollapsiblePanel";
import {PreviewModeSwitch} from "./PreviewModeSwitch";
import {SelectCube} from "./SelectCube";
-import {SelectLocale} from "./LocaleSelector";
+// import {SelectLocale} from "./LocaleSelector";
/** Defines which panel will be opened by default on the first load. */
export const ExplorerParams = (props: {defaultOpen: string}) => {
@@ -18,7 +18,7 @@ export const ExplorerParams = (props: {defaultOpen: string}) => {
return (
-
+ {/* */}
({
container: {
@@ -46,9 +47,7 @@ export function ExplorerResults(props: {
}) {
const cube = useSelector(selectOlapCube);
const serverStatus = useSelector(selectServerState);
- const {isDirty, params, result} = useSelector(selectCurrentQueryItem);
-
- const {loading: isLoading} = useSelector(selectLoadingState);
+ const {params, result} = useSelector(selectCurrentQueryItem);
const {online: isServerOnline, url: serverUrl} = serverStatus;
const {data, error} = result;
@@ -92,7 +91,8 @@ export function ExplorerResults(props: {
// or the user changed parameters since last query
// check is loading
// use set loading when seraching members.
- if (isServerOnline == null || !cube || isDirty || isLoading) {
+
+ if (isServerOnline == null || !cube) {
return (
}
- title={t("results.error_emptyresult_title")}
- description={t("results.error_emptyresult_detail")}
- />
- );
- }
+ // if (data.length === 0) {
+ // return (
+ // }
+ // title={t("results.error_emptyresult_title")}
+ // description={t("results.error_emptyresult_detail")}
+ // />
+ // );
+ // }
return (
({
alignItems: "center",
- background: t.colorScheme === "dark" ? t.colors.dark[7]: t.colors.gray[1],
+ background: t.colorScheme === "dark" ? t.colors.dark[7] : t.colors.gray[1],
justifyContent: "space-between"
})}
- w="100%">
+ w="100%"
+ >
{/* need to update this logic */}
{(!queryItem.panel || queryItem.panel === "table") && (
-
+
+
)}
@@ -241,20 +243,22 @@ function SuccessResult(props: {
-
+
+ {/* */}
-
);
}
diff --git a/src/components/LoadingOverlay.jsx b/src/components/LoadingOverlay.jsx
index 8fcaada..03dd218 100644
--- a/src/components/LoadingOverlay.jsx
+++ b/src/components/LoadingOverlay.jsx
@@ -1,4 +1,11 @@
-import {Flex, Loader, LoadingOverlay as MantineLoadingOverlay, Space, Text, Title} from "@mantine/core";
+import {
+ Flex,
+ Loader,
+ LoadingOverlay as MantineLoadingOverlay,
+ Space,
+ Text,
+ Title
+} from "@mantine/core";
import React from "react";
import {useSelector} from "react-redux";
import {useTranslation} from "../hooks/translation";
@@ -11,25 +18,21 @@ export const LoadingOverlay = () => {
const {loading: isLoading, message} = useSelector(selectLoadingState);
/* eslint-disable indent, operator-linebreak */
- const description =
- !message ? undefined :
- message.type === "HEAVY_QUERY" ? t("loading.message_heavyquery", message) :
- /* else */ t("loading.message_default", message);
+ const description = !message
+ ? undefined
+ : message.type === "HEAVY_QUERY"
+ ? t("loading.message_heavyquery", message)
+ : /* else */ t("loading.message_default", message);
/* eslint-enable indent, operator-linebreak */
- const customLoader =
-
+ const customLoader = (
+
{t("loading.title")}
{description}
- ;
-
+ );
return (
{
@@ -38,7 +37,7 @@ function MeasuresMenu(props: Props) {
if (query && !query.test(getCaption(measure, locale))) {
return null;
}
- return itemMap[measure.name] || buildMeasure({ active: false, ...measure });
+ return itemMap[measure.name] || buildMeasure({active: false, ...measure});
});
}, [itemMap, measures, filter, locale]);
@@ -49,8 +48,8 @@ function MeasuresMenu(props: Props) {
disabled={activeItems.map(active => active.name).includes(item.name)}
key={item.name}
onClick={() => {
- actions.updateMeasure({ ...item, active: !item.active });
- actions.willRequestQuery()
+ actions.updateMeasure({...item, active: !item.active});
+ // actions.willRequestQuery();
}}
>
{item.name}
diff --git a/src/components/OptionsMenu.tsx b/src/components/OptionsMenu.tsx
index 66bc2ba..b4ab07c 100644
--- a/src/components/OptionsMenu.tsx
+++ b/src/components/OptionsMenu.tsx
@@ -3,15 +3,12 @@ import {Menu, ActionIcon, ActionIconProps, UnstyledButton, Group, Text} from "@m
import {IconChevronRight, IconStack2} from "@tabler/icons-react";
import {DimensionMenu} from "./MenuDimension";
import MeasuresMenu from "./MeasuresMenu";
-import {IconArrowsLeftRight} from "@tabler/icons-react";
-import {useMantineTheme} from "@mantine/core";
-import {useMediaQuery} from "@mantine/hooks";
import {stringifyName} from "../utils/transform";
import {useSelector} from "react-redux";
import {selectDrilldownItems} from "../state/queries";
import {selectOlapDimensionItems} from "../state/selectors";
import {useActions} from "../hooks/settings";
-import {buildDrilldown} from "../utils/structs";
+import {buildDrilldown, buildCut} from "../utils/structs";
import type {LevelDescriptor} from "../utils/types";
import type {ComponentProps, ReactNode} from "react";
import type {PlainLevel} from "@datawheel/olap-client";
@@ -22,9 +19,18 @@ function OptionsMenu({children}: {children: ReactNode}) {
const dimensions = useSelector(selectOlapDimensionItems);
const {willRequestQuery} = useActions();
+ const createCutHandler = React.useCallback((level: PlainLevel) => {
+ const cutItem = buildCut({...level, key: level.fullName});
+ cutItem.active = false;
+ actions.updateCut(cutItem);
+ }, []);
+
const createHandler = useCallback(
(level: PlainLevel) => {
- const drilldownItem = buildDrilldown({...level, key: level.fullName});
+ // find or create drilldown
+ const drilldownItem =
+ items.find(item => item.uniqueName === level.uniqueName) ?? buildDrilldown({...level});
+ createCutHandler(level);
actions.updateDrilldown(drilldownItem);
actions
.willFetchMembers({...level, level: level.name})
@@ -59,9 +65,11 @@ function OptionsMenu({children}: {children: ReactNode}) {
Metrics
-
+
+ i.active)} onItemSelect={createHandler}>
Dimensions
+
{/* }>Calculations */}
diff --git a/src/components/SelectCubes.tsx b/src/components/SelectCubes.tsx
index 9a8bd9e..3beeaff 100644
--- a/src/components/SelectCubes.tsx
+++ b/src/components/SelectCubes.tsx
@@ -1,15 +1,15 @@
import {type PlainCube} from "@datawheel/olap-client";
-import {Anchor, Stack, Text, TextProps, Box, Accordion, AccordionControlProps} from "@mantine/core";
+import {Stack, Text, TextProps, Box, Accordion, AccordionControlProps} from "@mantine/core";
import React, {PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState} from "react";
import {useSelector} from "react-redux";
import {useActions} from "../hooks/settings";
import {useTranslation} from "../hooks/translation";
-import {selectLocale, selectMeasureMap, selectValidQueryStatus} from "../state/queries";
+import {selectLocale, selectMeasureMap} from "../state/queries";
import {selectOlapCube, selectOlapDimensionItems} from "../state/selectors";
import {selectOlapCubeItems} from "../state/server";
import {selectCubeName} from "../state/queries";
import {getAnnotation} from "../utils/string";
-import {buildDrilldown} from "../utils/structs";
+import {buildDrilldown, buildCut, MeasureItem} from "../utils/structs";
import type {Annotated} from "../utils/types";
import type {PlainLevel} from "@datawheel/olap-client";
import {useSideBar} from "./SideBar";
@@ -32,16 +32,22 @@ function SelectCubeInternal(props: {items: PlainCube[]; selectedItem: PlainCube
const {items, selectedItem} = props;
const {translate: t} = useTranslation();
const {code: locale} = useSelector(selectLocale);
- const {willRequestQuery, updateMeasure, updateDrilldown, willFetchMembers} = useActions();
- const initRef = useRef(false);
+ const {updateMeasure, updateDrilldown, willFetchMembers, updateCut} = useActions();
const cube = useSelector(selectCubeName);
const itemMap = useSelector(selectMeasureMap);
const dimensions = useSelector(selectOlapDimensionItems);
+ const createCutHandler = React.useCallback((level: PlainLevel) => {
+ const cutItem = buildCut({...level});
+ cutItem.active = false;
+ updateCut(cutItem);
+ }, []);
+
const addDrilldown = useCallback(
(level: PlainLevel) => {
- const drilldownItem = buildDrilldown({...level, key: level.fullName});
+ const drilldownItem = buildDrilldown(level);
+ createCutHandler(level);
updateDrilldown(drilldownItem);
return willFetchMembers({...level, level: level.name}).then(members => {
const dimension = dimensions.find(dim => dim.name === level.dimension);
@@ -60,35 +66,12 @@ function SelectCubeInternal(props: {items: PlainCube[]; selectedItem: PlainCube
useEffect(() => {
const params = new URLSearchParams(location.search);
const cubeParam = params.get("cube");
-
- //autoload if not params
if (selectedItem && cube && !cubeParam) {
- initRef.current = true;
- const measure = Object.keys(itemMap)
- .map(k => itemMap[k])
- .shift();
- const dimension = [...dimensions].shift();
+ const [measure] = Object.values(itemMap);
+ const [dimension] = dimensions;
if (measure && dimension) {
updateMeasure({...measure, active: true});
- addDrilldown(dimension.hierarchies[0].levels[0]).then(() => {
- willRequestQuery();
- });
- }
- }
- if (selectedItem && cube && cubeParam) {
- if (!initRef.current) {
- initRef.current = true;
- } else {
- const measure = Object.keys(itemMap)
- .map(k => itemMap[k])
- .shift();
- const dimension = [...dimensions].shift();
- if (measure && dimension) {
- updateMeasure({...measure, active: true});
- addDrilldown(dimension.hierarchies[0].levels[0]).then(() => {
- willRequestQuery();
- });
- }
+ addDrilldown(dimension.hierarchies[0].levels[0]);
}
}
}, [selectedItem, cube]);
@@ -189,6 +172,7 @@ function CubeTree({
const {graph, setGraph, map} = useSideBar();
useBuildGraph(items, locale, graph, setGraph);
const actions = useActions();
+
const onSelectCube = (table: string, subtopic: string) => {
const cube = items.find(
item =>
@@ -196,7 +180,7 @@ function CubeTree({
getAnnotation(item, "subtopic", locale) === subtopic
);
if (cube) {
- actions.willSetCube(cube.name);
+ return actions.willSetCube(cube.name);
}
};
@@ -250,7 +234,7 @@ function RootAccordions({items, graph, locale, selectedItem, onSelectCube}) {
w={"100%"}
styles={t => ({
control: {
- background: t.colorScheme === "dark" ? t.colors.dark[7] :t.colors.gray[1],
+ background: t.colorScheme === "dark" ? t.colors.dark[7] : t.colors.gray[1],
borderLeft: 8,
borderLeftColor: "transparent",
borderLeftStyle: "solid",
@@ -258,7 +242,7 @@ function RootAccordions({items, graph, locale, selectedItem, onSelectCube}) {
borderLeft: 8,
borderLeftColor: t.colors[t.primaryColor][t.fn.primaryShade()],
borderLeftStyle: "solid",
- color: t.colors[t.primaryColor][t.fn.primaryShade()],
+ color: t.colors[t.primaryColor][t.fn.primaryShade()]
}
},
content: {
@@ -306,8 +290,31 @@ function CubeButton({
locale: string;
parent?: string;
}) {
- const {setExpanded} = useSideBar();
+ const {updateMeasure, updateCut, updateDrilldown, willFetchMembers} = useActions();
const {classes} = useLinkStyles();
+
+ const createCutHandler = React.useCallback((level: PlainLevel) => {
+ const cutItem = buildCut({...level});
+ cutItem.active = false;
+ updateCut(cutItem);
+ }, []);
+
+ const addDrilldown = useCallback((level: PlainLevel, dimensions) => {
+ const drilldownItem = buildDrilldown(level);
+ createCutHandler(level);
+ updateDrilldown(drilldownItem);
+ return willFetchMembers({...level, level: level.name}).then(members => {
+ const dimension = dimensions.find(dim => dim.name === level.dimension);
+ if (!dimension) return;
+ return updateDrilldown({
+ ...drilldownItem,
+ dimType: dimension.dimensionType,
+ memberCount: members.length,
+ members
+ });
+ });
+ }, []);
+
const table = item;
const subtopic = parent ?? "";
return (
@@ -324,14 +331,23 @@ function CubeButton({
: classes.link
}
sx={t => ({
- background: isSelected(selectedItem, getCube(graph.items, table, subtopic, locale)) ?t.fn.primaryColor(): t.colorScheme === "dark" ? t.colors.dark[6]: t.colors.gray[3],
- whiteSpace: "nowrap",
- overflow: "hidden",
- textOverflow: "ellipsis",
+ background: isSelected(selectedItem, getCube(graph.items, table, subtopic, locale))
+ ? t.fn.primaryColor()
+ : t.colorScheme === "dark"
+ ? t.colors.dark[6]
+ : t.colors.gray[3],
+ overflow: "hidden"
})}
onClick={() => {
- onSelectCube(item, subtopic);
- setExpanded(false);
+ onSelectCube(item, subtopic).then(({cube, measures, dimensions}) => {
+ console.log(cube, measures, dimensions, "cubeData");
+ const [measure]: MeasureItem[] = Object.values(measures);
+ const [dimension] = dimensions;
+ if (measure && dimension) {
+ updateMeasure({...measure, active: true});
+ addDrilldown(dimension.hierarchies[0].levels[0], dimensions);
+ }
+ });
}}
>
{item}
@@ -368,7 +384,7 @@ function SubtopicAccordion({
styles={t => ({
control: {
fontSize: 14,
- background: t.colorScheme === "dark" ? t.colors.dark[7]: t.colors.gray[2],
+ background: t.colorScheme === "dark" ? t.colors.dark[7] : t.colors.gray[2],
borderLeft: 8,
borderLeftColor: "transparent",
borderLeftStyle: "solid",
@@ -379,7 +395,7 @@ function SubtopicAccordion({
}
},
content: {
- padding: 0,
+ padding: 0
}
})}
>
diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx
index 78ff031..95ccf77 100644
--- a/src/components/SideBar.tsx
+++ b/src/components/SideBar.tsx
@@ -1,5 +1,5 @@
import React, {PropsWithChildren, useState, useMemo, useEffect} from "react";
-import {Box, Flex, ActionIcon, Text, ScrollArea, Input, Group, Title} from "@mantine/core";
+import {Box, Flex, ActionIcon, Text, ScrollArea, Input, Group} from "@mantine/core";
import {createContext} from "../utils/create-context";
import {IconSearch} from "@tabler/icons-react";
import {DataSetSVG, IconChevronLeft, IconChevronRight} from "./icons";
@@ -8,9 +8,8 @@ import Graph from "../utils/graph";
import {useSelector} from "react-redux";
import {getKeys} from "./SelectCubes";
import {AnnotatedCube} from "./SelectCubes";
-import { LocaleSelector } from "./LocaleSelector";
+import {LocaleSelector} from "./LocaleSelector";
import {useTranslation} from "../hooks/translation";
-import {selectOlapCube} from "../state/selectors";
type SidebarProviderProps = {
expanded: boolean;
@@ -36,9 +35,7 @@ export function SideBarProvider(props: PropsWithChildren<{}>) {
const [map, setMap] = useState
- (
- {
- borderTopColor: theme.colorScheme === "dark" ? theme.colors.dark[6] :theme.colors.gray[3],
+ ({
+ borderTopColor:
+ theme.colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[3],
borderTopWidth: "1px",
- borderTopStyle: expanded ? "solid": "none"
- }
- )}>
- {props.children}
+ borderTopStyle: expanded ? "solid" : "none"
+ })}
+ >
+ {props.children}
diff --git a/src/components/TableFooter.tsx b/src/components/TableFooter.tsx
index 53330da..8286a4a 100644
--- a/src/components/TableFooter.tsx
+++ b/src/components/TableFooter.tsx
@@ -27,7 +27,7 @@ function TableFooter(props: Props) {
return (
-
+
{t("results.count_rows", {n: result.data.length})}
@@ -92,7 +92,11 @@ const DownloadQuery = () => {
);
}
- if (components.length === 0 || isDirty || result.data.length === 0) {
+ // if (components.length === 0 || isDirty || result.data.length === 0) {
+ // return null;
+ // }
+
+ if (components.length === 0 || result.data.length === 0) {
return null;
}
@@ -221,7 +225,7 @@ function MenuOpts({formats}: MenuOptsProps) {
minWidth: 0
}}
>
-
+
{/*