Skip to content
Snippets Groups Projects
Unverified Commit 14cf1923 authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

Convert ParameterSidebar to TypeScript (#27170)

parent 8e206ae5
No related merge requests found
Showing
with 650 additions and 330 deletions
......@@ -39,7 +39,7 @@ const VARIANTS = {
},
};
export interface RadioProps<TValue extends Key, TOption = RadioOption<TValue>>
export interface RadioProps<TValue, TOption = RadioOption<TValue>>
extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
name?: string;
value?: TValue;
......@@ -61,10 +61,7 @@ export interface RadioOption<TValue> {
value: TValue;
}
const Radio = forwardRef(function Radio<
TValue extends Key,
TOption = RadioOption<TValue>,
>(
const Radio = forwardRef(function Radio<TValue, TOption = RadioOption<TValue>>(
{
name,
value,
......@@ -121,7 +118,7 @@ const Radio = forwardRef(function Radio<
);
});
interface RadioItemProps<TValue extends Key> {
interface RadioItemProps<TValue> {
name: string;
checked: boolean;
label: string;
......@@ -135,7 +132,7 @@ interface RadioItemProps<TValue extends Key> {
onOptionClick?: (value: TValue) => void;
}
const RadioItem = <TValue extends Key, TOption>({
const RadioItem = <TValue, TOption>({
checked,
name,
label,
......@@ -163,7 +160,7 @@ const RadioItem = <TValue extends Key, TOption>({
<RadioInput
type="radio"
name={name}
value={value}
value={String(value)}
checked={checked}
disabled={disabled}
onChange={handleChange}
......@@ -184,19 +181,15 @@ const RadioItem = <TValue extends Key, TOption>({
);
};
const getDefaultOptionKey = <TValue extends Key, TOption>(
option: TOption,
): Key => {
const getDefaultOptionKey = <TValue, TOption>(option: TOption): Key => {
if (isDefaultOption<TValue>(option)) {
return option.value;
return String(option.value);
} else {
throw new TypeError();
}
};
const getDefaultOptionName = <TValue extends Key, TOption>(
option: TOption,
): string => {
const getDefaultOptionName = <TValue, TOption>(option: TOption): string => {
if (isDefaultOption(option)) {
return option.name;
} else {
......@@ -204,9 +197,7 @@ const getDefaultOptionName = <TValue extends Key, TOption>(
}
};
const getDefaultOptionValue = <TValue extends Key, TOption>(
option: TOption,
): TValue => {
const getDefaultOptionValue = <TValue, TOption>(option: TOption): TValue => {
if (isDefaultOption<TValue>(option)) {
return option.value;
} else {
......
......@@ -25,12 +25,11 @@ DashboardSidebars.propTypes = {
onUpdateDashCardVisualizationSettings: PropTypes.func.isRequired,
onUpdateDashCardColumnSettings: PropTypes.func.isRequired,
setEditingParameter: PropTypes.func.isRequired,
setParameter: PropTypes.func.isRequired,
setParameterName: PropTypes.func.isRequired,
setParameterDefaultValue: PropTypes.func.isRequired,
setParameterIsMultiSelect: PropTypes.func.isRequired,
dashcardData: PropTypes.object,
setParameterFilteringParameters: PropTypes.func.isRequired,
dashcardData: PropTypes.object,
isSharing: PropTypes.bool.isRequired,
isEditing: PropTypes.bool.isRequired,
isFullscreen: PropTypes.bool.isRequired,
......@@ -56,12 +55,11 @@ export function DashboardSidebars({
onReplaceAllDashCardVisualizationSettings,
onUpdateDashCardVisualizationSettings,
onUpdateDashCardColumnSettings,
setParameter,
setParameterName,
setParameterDefaultValue,
setParameterIsMultiSelect,
dashcardData,
setParameterFilteringParameters,
dashcardData,
isFullscreen,
onCancel,
params,
......@@ -130,23 +128,13 @@ export function DashboardSidebars({
<ParameterSidebar
parameter={parameter}
otherParameters={otherParameters}
remove={() => {
closeSidebar();
removeParameter(editingParameterId);
}}
done={() => closeSidebar()}
showAddParameterPopover={showAddParameterPopover}
setParameter={setParameter}
setName={name => setParameterName(editingParameterId, name)}
setDefaultValue={value =>
setParameterDefaultValue(editingParameterId, value)
}
setIsMultiSelect={value =>
setParameterIsMultiSelect(editingParameterId, value)
}
setFilteringParameters={ids =>
setParameterFilteringParameters(editingParameterId, ids)
}
onChangeName={setParameterName}
onChangeDefaultValue={setParameterDefaultValue}
onChangeIsMultiSelect={setParameterIsMultiSelect}
onChangeFilteringParameters={setParameterFilteringParameters}
onRemoveParameter={removeParameter}
onShowAddParameterPopover={showAddParameterPopover}
onClose={closeSidebar}
/>
);
}
......
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
export const SectionRoot = styled.div`
padding: 1.5rem 1rem;
`;
export const SectionHeader = styled.div`
font-size: 1rem;
font-weight: bold;
`;
export const SectionMessage = styled.p`
color: ${color("text-medium")};
`;
export const SectionMessageLink = styled.span`
color: ${color("brand")};
cursor: pointer;
`;
export const ParameterRoot = styled.div`
margin-bottom: 1rem;
border-radius: 0.5rem;
background-color: ${color("bg-light")};
`;
export const ParameterBody = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
`;
export const ParameterName = styled.div`
cursor: pointer;
border-bottom: 1px dashed ${color("border")};
font-weight: bold;
`;
export const FieldListRoot = styled.div`
font-size: 0.765rem;
`;
export const FieldListHeader = styled.div`
display: flex;
border-top: 1px solid ${color("border")};
`;
export const FieldListTitle = styled.div`
color: ${color("brand")};
width: 50%;
padding: 0.5rem 1rem 0;
`;
export const FieldListItem = styled.div`
display: flex;
&:not(:last-child) {
border-bottom: 1px solid ${color("border")};
}
`;
export const FieldRoot = styled.div`
width: 100%;
padding: 0.5rem 1rem;
`;
export const FieldLabel = styled.div`
color: ${color("text-medium")};
`;
import React, { useCallback, useMemo, useState } from "react";
import { jt, t } from "ttag";
import Toggle from "metabase/core/components/Toggle";
import Fields from "metabase/entities/fields";
import Tables from "metabase/entities/tables";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import { Field, Table } from "metabase-types/api";
import { UiParameter } from "metabase-lib/parameters/types";
import { usableAsLinkedFilter } from "../../utils/linked-filters";
import useFilterFields from "./use-filter-fields";
import {
SectionRoot,
SectionHeader,
SectionMessage,
SectionMessageLink,
ParameterRoot,
ParameterBody,
ParameterName,
FieldRoot,
FieldLabel,
FieldListRoot,
FieldListItem,
FieldListHeader,
FieldListTitle,
} from "./ParameterLinkedFilters.styled";
export interface ParameterLinkedFiltersProps {
parameter: UiParameter;
otherParameters: UiParameter[];
onChangeFilteringParameters: (
parameterId: string,
filteringParameters: string[],
) => void;
onShowAddParameterPopover: () => void;
}
const ParameterLinkedFilters = ({
parameter,
otherParameters,
onChangeFilteringParameters,
onShowAddParameterPopover,
}: ParameterLinkedFiltersProps): JSX.Element => {
const currentParameterId = parameter.id;
const [expandedParameterId, setExpandedParameterId] = useState<string>();
const filteringParameters = useMemo(
() => parameter.filteringParameters ?? [],
[parameter],
);
const usableParameters = useMemo(
() => otherParameters.filter(usableAsLinkedFilter),
[otherParameters],
);
const handleFilterChange = useCallback(
(otherParameter: UiParameter, isFiltered: boolean) => {
const newParameters = isFiltered
? filteringParameters.concat(otherParameter.id)
: filteringParameters.filter(id => id !== otherParameter.id);
onChangeFilteringParameters(currentParameterId, newParameters);
},
[currentParameterId, filteringParameters, onChangeFilteringParameters],
);
const handleExpandedChange = useCallback(
(otherParameter: UiParameter, isExpanded: boolean) => {
setExpandedParameterId(isExpanded ? otherParameter.id : undefined);
},
[],
);
return (
<SectionRoot>
<SectionHeader>{t`Limit this filter's choices`}</SectionHeader>
{usableParameters.length === 0 ? (
<div>
<SectionMessage>
{t`If you have another dashboard filter, you can limit the choices that are listed for this filter based on the selection of the other one.`}
</SectionMessage>
<SectionMessage>
{jt`So first, ${(
<SectionMessageLink
key="link"
onClick={onShowAddParameterPopover}
>
{t`add another dashboard filter`}
</SectionMessageLink>
)}.`}
</SectionMessage>
</div>
) : (
<div>
<SectionMessage>
{jt`If you toggle on one of these dashboard filters, selecting a value for that filter will limit the available choices for ${(
<em key="text">{t`this`}</em>
)} filter.`}
</SectionMessage>
{usableParameters.map(otherParameter => (
<LinkedParameter
key={otherParameter.id}
parameter={parameter}
otherParameter={otherParameter}
isFiltered={filteringParameters.includes(parameter.id)}
isExpanded={otherParameter.id === expandedParameterId}
onFilterChange={handleFilterChange}
onExpandedChange={handleExpandedChange}
/>
))}
</div>
)}
</SectionRoot>
);
};
interface LinkedParameterProps {
parameter: UiParameter;
otherParameter: UiParameter;
isFiltered: boolean;
isExpanded: boolean;
onFilterChange: (otherParameter: UiParameter, isFiltered: boolean) => void;
onExpandedChange: (otherParameter: UiParameter, isExpanded: boolean) => void;
}
const LinkedParameter = ({
parameter,
otherParameter,
isFiltered,
isExpanded,
onFilterChange,
onExpandedChange,
}: LinkedParameterProps): JSX.Element => {
const handleFilterToggle = useCallback(
(isFiltered: boolean) => {
onFilterChange(otherParameter, isFiltered);
},
[otherParameter, onFilterChange],
);
const handleExpandedChange = useCallback(() => {
onExpandedChange(otherParameter, !isExpanded);
}, [isExpanded, otherParameter, onExpandedChange]);
return (
<ParameterRoot>
<ParameterBody>
<ParameterName onClick={handleExpandedChange}>
{otherParameter.name}
</ParameterName>
<Toggle value={isFiltered} onChange={handleFilterToggle} />
</ParameterBody>
{isExpanded && (
<LinkedFieldList
parameter={parameter}
otherParameter={otherParameter}
/>
)}
</ParameterRoot>
);
};
interface LinkedFieldListProps {
parameter: UiParameter;
otherParameter: UiParameter;
}
const LinkedFieldList = ({
parameter,
otherParameter,
}: LinkedFieldListProps) => {
const { data, error, loading } = useFilterFields(parameter, otherParameter);
return (
<LoadingAndErrorWrapper loading={loading} error={error}>
<FieldListRoot>
{data && data.length > 0 && (
<FieldListHeader>
<FieldListTitle>{t`Filtering column`}</FieldListTitle>
<FieldListTitle>{t`Filtered column`}</FieldListTitle>
</FieldListHeader>
)}
{data?.map(([filteringId, filteredId]) => (
<FieldListItem key={filteredId}>
<LinkedField fieldId={filteringId} />
<LinkedField fieldId={filteredId} />
</FieldListItem>
))}
</FieldListRoot>
</LoadingAndErrorWrapper>
);
};
interface LinkedFieldProps {
fieldId: string;
}
const LinkedField = ({ fieldId }: LinkedFieldProps) => {
return (
<Fields.Loader id={fieldId}>
{({ field }: { field: Field }) => (
<FieldRoot>
<FieldLabel>
<Tables.Loader id={field.table_id}>
{({ table }: { table: Table }) => (
<span>{table.display_name}</span>
)}
</Tables.Loader>
</FieldLabel>
<div>{field.display_name}</div>
</FieldRoot>
)}
</Fields.Loader>
);
};
export default ParameterLinkedFilters;
export { default } from "./ParameterLinkedFilters";
import { useCallback, useState } from "react";
import { t } from "ttag";
import { DashboardApi } from "metabase/services";
import { useOnMount } from "metabase/hooks/use-on-mount";
import { UiParameter } from "metabase-lib/parameters/types";
export interface UseFilterFieldsState {
data?: string[][];
error?: string;
loading: boolean;
}
const useFilterFields = (
parameter: UiParameter,
otherParameter: UiParameter,
): UseFilterFieldsState => {
const [state, setState] = useState<UseFilterFieldsState>({ loading: false });
const handleLoad = useCallback(async () => {
const filtered = getParameterFieldIds(parameter);
const filtering = getParameterFieldIds(otherParameter);
if (!filtered.length || !filtering.length) {
const errorParameter = !filtered.length ? parameter : otherParameter;
const error = getParameterError(errorParameter);
setState({ error, loading: false });
} else {
setState({ loading: true });
const request = { filtered, filtering };
const response = await DashboardApi.validFilterFields(request);
setState({ data: getParameterMapping(response), loading: false });
}
}, [parameter, otherParameter]);
useOnMount(() => {
handleLoad();
});
return state;
};
const getParameterError = ({ name }: UiParameter) => {
return t`To view this, ${name} must be connected to at least one field.`;
};
const getParameterFieldIds = (parameter: UiParameter) => {
if ("fields" in parameter) {
return parameter.fields.map(field => field.id);
} else {
return [];
}
};
const getParameterMapping = (data: Record<string, string[]>) => {
return Object.entries(data).flatMap(([filteredId, filteringIds]) =>
filteringIds.map(filteringId => [filteringId, filteredId]),
);
};
export default useFilterFields;
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
import IconButtonWrapper from "metabase/components/IconButtonWrapper";
import ParameterValueWidget from "../ParameterValueWidget";
export const SettingsRoot = styled.div`
padding: 1.5rem 1rem;
`;
export const SettingSection = styled.div`
margin-bottom: 2rem;
`;
export const SettingLabel = styled.label`
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
`;
export const SettingValueWidget = styled(ParameterValueWidget)`
color: ${color("text-dark")};
padding: 0.75rem 0.75rem;
border: 1px solid ${color("border")};
border-radius: 0.5rem;
background-color: ${color("white")};
`;
export const SettingRemoveButton = styled(IconButtonWrapper)`
color: ${color("text-medium")};
font-weight: bold;
&:hover {
color: ${color("error")};
}
`;
import React, {
ChangeEvent,
FocusEvent,
useCallback,
useLayoutEffect,
useState,
} from "react";
import { t } from "ttag";
import Input from "metabase/core/components/Input";
import Radio from "metabase/core/components/Radio";
import { UiParameter } from "metabase-lib/parameters/types";
import { getIsMultiSelect } from "../../utils/dashboards";
import { isSingleOrMultiSelectable } from "../../utils/parameter-type";
import {
SettingLabel,
SettingRemoveButton,
SettingSection,
SettingsRoot,
SettingValueWidget,
} from "./ParameterSettings.styled";
const MULTI_SELECT_OPTIONS = [
{ name: t`Multiple values`, value: true },
{ name: t`A single value`, value: false },
];
interface ParameterSettingsProps {
parameter: UiParameter;
onChangeName: (parameterId: string, name: string) => void;
onChangeDefaultValue: (parameterId: string, value: unknown) => void;
onChangeIsMultiSelect: (parameterId: string, isMultiSelect: boolean) => void;
onRemoveParameter: (parameterId: string) => void;
}
const ParameterSettings = ({
parameter,
onChangeName,
onChangeDefaultValue,
onChangeIsMultiSelect,
onRemoveParameter,
}: ParameterSettingsProps): JSX.Element => {
const parameterId = parameter.id;
const handleNameChange = useCallback(
(name: string) => {
onChangeName(parameterId, name);
},
[parameterId, onChangeName],
);
const handleDefaultValueChange = useCallback(
(value: unknown) => {
onChangeDefaultValue(parameterId, value);
},
[parameterId, onChangeDefaultValue],
);
const handleMultiSelectChange = useCallback(
(isMultiSelect: boolean) => {
onChangeIsMultiSelect(parameterId, isMultiSelect);
},
[parameterId, onChangeIsMultiSelect],
);
const handleRemove = useCallback(() => {
onRemoveParameter(parameterId);
}, [parameterId, onRemoveParameter]);
return (
<SettingsRoot>
<SettingSection>
<SettingLabel>{t`Label`}</SettingLabel>
<ParameterInput
initialValue={parameter.name}
onChange={handleNameChange}
/>
</SettingSection>
<SettingSection>
<SettingLabel>{t`Default value`}</SettingLabel>
<SettingValueWidget
parameter={parameter}
name={parameter.name}
value={parameter.default}
placeholder={t`No default`}
setValue={handleDefaultValueChange}
/>
</SettingSection>
{isSingleOrMultiSelectable(parameter) && (
<SettingSection>
<SettingLabel>{t`Users can pick`}</SettingLabel>
<Radio
value={getIsMultiSelect(parameter)}
options={MULTI_SELECT_OPTIONS}
vertical
onChange={handleMultiSelectChange}
/>
</SettingSection>
)}
<SettingRemoveButton onClick={handleRemove}>
{t`Remove`}
</SettingRemoveButton>
</SettingsRoot>
);
};
interface ParameterInputProps {
initialValue: string;
onChange: (value: string) => void;
}
const ParameterInput = ({ initialValue, onChange }: ParameterInputProps) => {
const [value, setValue] = useState(initialValue);
useLayoutEffect(() => {
setValue(initialValue);
}, [initialValue]);
const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
}, []);
const handleBlur = useCallback(
(event: FocusEvent<HTMLInputElement>) => {
onChange(event.target.value);
},
[onChange],
);
return (
<Input
value={value}
fullWidth
onChange={handleChange}
onBlur={handleBlur}
/>
);
};
export default ParameterSettings;
export { default } from "./ParameterSettings";
/* eslint-disable react/prop-types */
import React from "react";
import { t, jt } from "ttag";
import cx from "classnames";
import { DashboardApi } from "metabase/services";
import Fields from "metabase/entities/fields";
import Tables from "metabase/entities/tables";
import {
canUseLinkedFilters,
usableAsLinkedFilter,
} from "metabase/parameters/utils/linked-filters";
import Radio from "metabase/core/components/Radio";
import Toggle from "metabase/core/components/Toggle";
import InputBlurChange from "metabase/components/InputBlurChange";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import ParameterValueWidget from "metabase/parameters/components/ParameterValueWidget";
import { isSingleOrMultiSelectable } from "metabase/parameters/utils/parameter-type";
import Sidebar from "metabase/dashboard/components/Sidebar";
import { getIsMultiSelect } from "metabase/parameters/utils/dashboards";
const LINKED_FILTER = "linked-filters";
const TABS = [
{ value: "settings", name: t`Settings`, icon: "gear" },
{ value: LINKED_FILTER, name: t`Linked filters`, icon: "link" },
];
class ParameterSidebar extends React.Component {
state = { currentTab: "settings", originalParameter: null };
componentDidMount() {
this.setState({ originalParameter: this.props.parameter });
}
componentDidUpdate(prevProps) {
if (this.props.parameter.id !== prevProps.parameter.id) {
this.setState({ originalParameter: this.props.parameter });
}
}
handleCancel = () => {
this.props.setParameter(
this.props.parameter.id,
this.state.originalParameter,
);
this.props.done();
};
render() {
const {
parameter,
otherParameters,
remove,
done,
setName,
setDefaultValue,
setIsMultiSelect,
setFilteringParameters,
} = this.props;
const { currentTab } = this.state;
const tabs = canUseLinkedFilters(parameter)
? TABS
: TABS.filter(({ value }) => value !== LINKED_FILTER);
return (
<Sidebar onClose={done} onCancel={this.handleCancel}>
<div className="flex justify-evenly border-bottom">
<Radio
options={tabs}
variant="underlined"
value={currentTab}
onChange={value => this.setState({ currentTab: value })}
/>
</div>
<div className="px2">
{currentTab === "settings" ? (
<div className="px2">
<div className="py2">
<label className="mt2 mb1 block text-bold">{t`Label`}</label>
<InputBlurChange
className="block full"
value={parameter.name}
onBlurChange={e => setName(e.target.value)}
/>
</div>
<label className="mt2 mb1 block text-bold">{t`Default value`}</label>
<div className="pb2">
<ParameterValueWidget
parameter={parameter}
name={parameter.name}
value={parameter.default}
setValue={setDefaultValue}
placeholder={t`No default`}
className="input bg-white"
/>
</div>
{isSingleOrMultiSelectable(parameter) && (
<div className="pb2">
<label className="mt2 mb1 block text-bold">{t`Users can pick`}</label>
<Radio
value={getIsMultiSelect(parameter)}
onChange={setIsMultiSelect}
options={[
{ name: t`Multiple values`, value: true },
{ name: t`A single value`, value: false },
]}
vertical
/>
</div>
)}
<a
borderless
className="mt2 block text-medium text-error-hover text-bold"
onClick={remove}
>
{t`Remove`}
</a>
</div>
) : (
<OtherParameterList
showAddParameterPopover={this.props.showAddParameterPopover}
parameter={parameter}
otherParameters={otherParameters}
setFilteringParameters={setFilteringParameters}
/>
)}
</div>
</Sidebar>
);
}
}
class OtherParameterList extends React.Component {
state = {
expandedParameterId: null,
columnPairs: [],
loading: false,
error: null,
};
componentDidUpdate(prevProps) {
if (this.props.parameter.id !== prevProps.parameter.id) {
this.setState({
expandedParameterId: null,
columnPairs: [],
loading: false,
error: null,
});
}
}
expandColumnPairs = async id => {
if (id === this.state.expandedParameterId) {
this.setState({ expandedParameterId: null, error: null });
return;
} else {
this.setState({ expandedParameterId: id, loading: true, error: null });
}
const { parameter, otherParameters } = this.props;
const filtered = parameter.fields.map(field => field.id);
const parameterForId = otherParameters.find(p => p.id === id);
const filtering = parameterForId.fields.map(field => field.id);
if (filtered.length === 0 || filtering.length === 0) {
const param = filtered.length === 0 ? parameter : parameterForId;
const error = t`To view this, ${param.name} must be connected to at least one field.`;
this.setState({ loading: false, error });
return;
}
const result = await DashboardApi.validFilterFields({
filtered,
filtering,
});
const columnPairs = Object.entries(result).flatMap(
([filteredId, filteringIds]) =>
filteringIds.map(filteringId => [filteringId, filteredId]),
);
this.setState({ columnPairs, loading: false });
};
render() {
const {
otherParameters,
parameter: { filteringParameters = [] },
setFilteringParameters,
showAddParameterPopover,
} = this.props;
const { expandedParameterId, columnPairs } = this.state;
const usableParameters = otherParameters.filter(usableAsLinkedFilter);
return (
<div className="py3 px2">
<h3>{t`Limit this filter's choices`}</h3>
{usableParameters.length === 0 ? (
<div>
<p className="text-medium">{t`If you have another dashboard filter, you can limit the choices that are listed for this filter based on the selection of the other one.`}</p>
<p className="text-medium">{jt`So first, ${(
<span
onClick={showAddParameterPopover}
className="cursor-pointer text-brand"
>{t`add another dashboard filter`}</span>
)}.`}</p>
</div>
) : (
<div>
<p className="text-medium">{jt`If you toggle on one of these dashboard filters, selecting a value for that filter will limit the available choices for ${(
<span className="text-italic">{t`this`}</span>
)} filter.`}</p>
{usableParameters.map(({ id, name }) => (
<div className="bg-light rounded mb2" key={name}>
<div className="flex justify-between align-center p2">
<span
className="border-dashed-bottom text-bold cursor-pointer"
onClick={() => this.expandColumnPairs(id)}
>
{name}
</span>
<Toggle
value={(filteringParameters || []).includes(id)}
onChange={included =>
setFilteringParameters(
included
? filteringParameters.concat(id)
: filteringParameters.filter(x => x !== id),
)
}
/>
</div>
{id === expandedParameterId && (
<LoadingAndErrorWrapper
loading={this.state.loading}
error={this.state.error}
className="border-top text-small"
>
{columnPairs.map((row, index) => (
<div
key={index}
className={cx({ "border-top": index > 0 })}
>
{index === 0 && (
<div className="flex">
<div className="half text-brand px2 pt1">{t`Filtering column`}</div>
<div className="half text-brand px2 pt1">{t`Filtered column`}</div>
</div>
)}
<div className="flex">
{row.map(fieldId => (
<FieldAndTableName
fieldId={fieldId}
key={fieldId}
/>
))}
</div>
</div>
))}
</LoadingAndErrorWrapper>
)}
</div>
))}
</div>
)}
</div>
);
}
}
function FieldAndTableName({ fieldId }) {
return (
<Fields.Loader id={fieldId}>
{({ field }) => (
<div className="half px2 py1">
<div className="text-medium">
<Tables.Loader id={field.table_id}>
{({ table }) => <span>{table.display_name}</span>}
</Tables.Loader>
</div>
<div>{field.display_name}</div>{" "}
</div>
)}
</Fields.Loader>
);
}
export default ParameterSidebar;
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
export const SidebarHeader = styled.div`
display: flex;
justify-content: space-evenly;
border-bottom: 1px solid ${color("border")};
`;
export const SidebarBody = styled.div`
padding: 0 1rem;
`;
import React, { useCallback, useMemo, useState } from "react";
import { t } from "ttag";
import Radio from "metabase/core/components/Radio";
import Sidebar from "metabase/dashboard/components/Sidebar";
import { UiParameter } from "metabase-lib/parameters/types";
import { canUseLinkedFilters } from "../../utils/linked-filters";
import ParameterSettings from "../ParameterSettings";
import ParameterLinkedFilters from "../ParameterLinkedFilters";
import { SidebarBody, SidebarHeader } from "./ParameterSidebar.styled";
export interface ParameterSidebarProps {
parameter: UiParameter;
otherParameters: UiParameter[];
onChangeName: (parameterId: string, name: string) => void;
onChangeDefaultValue: (parameterId: string, value: unknown) => void;
onChangeIsMultiSelect: (parameterId: string, isMultiSelect: boolean) => void;
onChangeFilteringParameters: (
parameterId: string,
filteringParameters: string[],
) => void;
onRemoveParameter: (parameterId: string) => void;
onShowAddParameterPopover: () => void;
onClose: () => void;
}
const ParameterSidebar = ({
parameter,
otherParameters,
onChangeName,
onChangeDefaultValue,
onChangeIsMultiSelect,
onChangeFilteringParameters,
onRemoveParameter,
onShowAddParameterPopover,
onClose,
}: ParameterSidebarProps): JSX.Element => {
const tabs = useMemo(() => getTabs(parameter), [parameter]);
const [tab, setTab] = useState(tabs[0].value);
const handleRemove = useCallback(
(parameterId: string) => {
onRemoveParameter(parameterId);
onClose();
},
[onRemoveParameter, onClose],
);
return (
<Sidebar onClose={onClose}>
<SidebarHeader>
<Radio
value={tab}
options={tabs}
variant="underlined"
onChange={setTab}
/>
</SidebarHeader>
<SidebarBody>
{tab === "settings" ? (
<ParameterSettings
parameter={parameter}
onChangeName={onChangeName}
onChangeDefaultValue={onChangeDefaultValue}
onChangeIsMultiSelect={onChangeIsMultiSelect}
onRemoveParameter={handleRemove}
/>
) : (
<ParameterLinkedFilters
parameter={parameter}
otherParameters={otherParameters}
onChangeFilteringParameters={onChangeFilteringParameters}
onShowAddParameterPopover={onShowAddParameterPopover}
/>
)}
</SidebarBody>
</Sidebar>
);
};
const getTabs = (parameter: UiParameter) => {
const tabs = [{ value: "settings", name: t`Settings`, icon: "gear" }];
if (canUseLinkedFilters(parameter)) {
tabs.push({ value: "filters", name: t`Linked filters`, icon: "link" });
}
return tabs;
};
export default ParameterSidebar;
export { default } from "./ParameterSidebar";
import React, { Key } from "react";
import React from "react";
import _ from "underscore";
import { useToggle } from "metabase/hooks/use-toggle";
......@@ -23,7 +23,7 @@ function BooleanPicker({
const value = getValue(filter);
const [isExpanded, { toggle }] = useToggle(!_.isBoolean(value));
const updateFilter = (value: Key | boolean) => {
const updateFilter = (value: unknown) => {
if (_.isBoolean(value)) {
onFilterChange(filter.setOperator("=").setArguments([value]));
} else if (typeof value === "string") {
......
......@@ -30,7 +30,7 @@ const ChartSettingsWidgetPopover = ({
sections.current = _.groupBy(widgets, "section");
}, [widgets]);
const [currentSection, setCurrentSection] = useState<React.Key>("");
const [currentSection, setCurrentSection] = useState("");
useEffect(() => {
setCurrentSection(Object.keys(sections.current)[0]);
......@@ -61,7 +61,7 @@ const ChartSettingsWidgetPopover = ({
name: sectionName,
value: sectionName,
}))}
onChange={setCurrentSection}
onChange={section => setCurrentSection(String(section))}
variant="underlined"
/>
)}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment