mirror of
https://github.com/umami-software/umami.git
synced 2026-02-25 06:55:35 +01:00
Update insights report parameters. Added contains logic.
This commit is contained in:
parent
d59477deb5
commit
5daad2726e
15 changed files with 280 additions and 190 deletions
|
|
@ -1,7 +1,7 @@
|
|||
.popup {
|
||||
display: flex;
|
||||
max-width: 300px;
|
||||
max-height: 400px;
|
||||
max-height: 210px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
|
|
@ -19,6 +19,6 @@
|
|||
min-width: 180px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
min-width: 200px;
|
||||
.text {
|
||||
min-width: 180px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,19 @@
|
|||
import { useState, useMemo } from 'react';
|
||||
import { Form, FormRow, Item, Flexbox, Dropdown, Button } from 'react-basics';
|
||||
import {
|
||||
Form,
|
||||
FormRow,
|
||||
Item,
|
||||
Flexbox,
|
||||
Dropdown,
|
||||
Button,
|
||||
TextField,
|
||||
Menu,
|
||||
Popup,
|
||||
PopupTrigger,
|
||||
} from 'react-basics';
|
||||
import { useMessages, useFilters, useFormat, useLocale } from 'components/hooks';
|
||||
import { safeDecodeURIComponent } from 'next-basics';
|
||||
import { OPERATORS } from 'lib/constants';
|
||||
import styles from './FieldFilterForm.module.css';
|
||||
|
||||
export interface FieldFilterFormProps {
|
||||
|
|
@ -22,12 +35,11 @@ export default function FieldFilterForm({
|
|||
}: FieldFilterFormProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [filter, setFilter] = useState('eq');
|
||||
const [value, setValue] = useState();
|
||||
const [value, setValue] = useState('');
|
||||
const { getFilters } = useFilters();
|
||||
const { formatValue } = useFormat();
|
||||
const { locale } = useLocale();
|
||||
const filters = getFilters(type);
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const formattedValues = useMemo(() => {
|
||||
const formatted = {};
|
||||
|
|
@ -45,21 +57,25 @@ export default function FieldFilterForm({
|
|||
}, [formatValue, locale, name, values]);
|
||||
|
||||
const filteredValues = useMemo(() => {
|
||||
return search ? values.filter(n => n.includes(search)) : values;
|
||||
}, [search, formattedValues]);
|
||||
return value ? values.filter(n => n.includes(value)) : values;
|
||||
}, [value, formattedValues]);
|
||||
|
||||
const renderFilterValue = value => {
|
||||
return filters.find(f => f.value === value)?.label;
|
||||
};
|
||||
|
||||
const renderValue = value => {
|
||||
return formattedValues[value];
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
onSelect({ name, type, filter, value });
|
||||
};
|
||||
|
||||
const handleMenuSelect = value => {
|
||||
setValue(value);
|
||||
};
|
||||
|
||||
const showMenu =
|
||||
[OPERATORS.equals, OPERATORS.notEquals].includes(filter as any) &&
|
||||
!(filteredValues.length === 1 && filteredValues[0] === value);
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<FormRow label={label} className={styles.filter}>
|
||||
|
|
@ -77,21 +93,24 @@ export default function FieldFilterForm({
|
|||
}}
|
||||
</Dropdown>
|
||||
)}
|
||||
<Dropdown
|
||||
className={styles.dropdown}
|
||||
popupProps={{ className: styles.popup }}
|
||||
menuProps={{ className: styles.menu }}
|
||||
items={filteredValues}
|
||||
value={value}
|
||||
renderValue={renderValue}
|
||||
onChange={(key: any) => setValue(key)}
|
||||
allowSearch={true}
|
||||
onSearch={setSearch}
|
||||
>
|
||||
{(value: string) => {
|
||||
return <Item key={value}>{formattedValues[value]}</Item>;
|
||||
}}
|
||||
</Dropdown>
|
||||
<PopupTrigger>
|
||||
<TextField
|
||||
className={styles.text}
|
||||
value={decodeURIComponent(value)}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
/>
|
||||
{showMenu && (
|
||||
<Popup className={styles.popup} alignment="end">
|
||||
{filteredValues.length > 0 && (
|
||||
<Menu variant="popup" onSelect={handleMenuSelect}>
|
||||
{filteredValues.map(value => {
|
||||
return <Item key={value}>{safeDecodeURIComponent(value)}</Item>;
|
||||
})}
|
||||
</Menu>
|
||||
)}
|
||||
</Popup>
|
||||
)}
|
||||
</PopupTrigger>
|
||||
</Flexbox>
|
||||
<Button variant="primary" onClick={handleAdd} disabled={!filter || !value}>
|
||||
{formatMessage(labels.add)}
|
||||
|
|
|
|||
|
|
@ -24,14 +24,14 @@ function useValues(websiteId: string, type: string) {
|
|||
|
||||
export interface FilterSelectFormProps {
|
||||
websiteId: string;
|
||||
items: any[];
|
||||
fields: any[];
|
||||
onSelect?: (key: any) => void;
|
||||
allowFilterSelect?: boolean;
|
||||
}
|
||||
|
||||
export default function FilterSelectForm({
|
||||
websiteId,
|
||||
items,
|
||||
fields,
|
||||
onSelect,
|
||||
allowFilterSelect,
|
||||
}: FilterSelectFormProps) {
|
||||
|
|
@ -39,7 +39,7 @@ export default function FilterSelectForm({
|
|||
const { data, isLoading } = useValues(websiteId, field?.name);
|
||||
|
||||
if (!field) {
|
||||
return <FieldSelectForm fields={items} onSelect={setField} showType={false} />;
|
||||
return <FieldSelectForm fields={fields} onSelect={setField} showType={false} />;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
|
|
|
|||
|
|
@ -13,9 +13,4 @@
|
|||
border: 1px solid var(--base400);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 1px 1px 1px var(--base400);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
align-self: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +1,36 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Icon, TooltipPopup } from 'react-basics';
|
||||
import { Icon } from 'react-basics';
|
||||
import Icons from 'components/icons';
|
||||
import Empty from 'components/common/Empty';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import styles from './ParameterList.module.css';
|
||||
|
||||
export interface ParameterListProps {
|
||||
items: any[];
|
||||
children?: ReactNode | ((item: any) => ReactNode);
|
||||
onRemove: (index: number, e: any) => void;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export function ParameterList({ items = [], children, onRemove }: ParameterListProps) {
|
||||
export function ParameterList({ children }: ParameterListProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<div className={styles.list}>
|
||||
{!items.length && <Empty message={formatMessage(labels.none)} />}
|
||||
{items.map((item, index) => {
|
||||
return (
|
||||
<div key={index} className={styles.item}>
|
||||
{typeof children === 'function' ? children(item) : item}
|
||||
<TooltipPopup
|
||||
className={styles.icon}
|
||||
label={formatMessage(labels.remove)}
|
||||
position="right"
|
||||
>
|
||||
<Icon onClick={onRemove.bind(null, index)}>
|
||||
<Icons.Close />
|
||||
</Icon>
|
||||
</TooltipPopup>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{!children && <Empty message={formatMessage(labels.none)} />}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Item = ({ children, onRemove }: { children?: ReactNode; onRemove?: () => void }) => {
|
||||
return (
|
||||
<div className={styles.item}>
|
||||
{children}
|
||||
<Icon onClick={onRemove}>
|
||||
<Icons.Close />
|
||||
</Icon>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ParameterList.Item = Item;
|
||||
|
||||
export default ParameterList;
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@
|
|||
display: grid;
|
||||
grid-template-rows: max-content 1fr;
|
||||
grid-template-columns: max-content 1fr;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue