mirror of
https://github.com/umami-software/umami.git
synced 2026-02-08 06:37:18 +01:00
Merge branch 'dev' of https://github.com/umami-software/umami into dev
This commit is contained in:
commit
6b03935fca
7 changed files with 411 additions and 143 deletions
|
|
@ -6,27 +6,48 @@ import styles from './GoalsAddForm.module.css';
|
|||
export function GoalsAddForm({
|
||||
type: defaultType = 'url',
|
||||
value: defaultValue = '',
|
||||
property: defaultProperty = '',
|
||||
operator: defaultAggregae = null,
|
||||
goal: defaultGoal = 10,
|
||||
onChange,
|
||||
}: {
|
||||
type?: string;
|
||||
value?: string;
|
||||
operator?: string;
|
||||
property?: string;
|
||||
goal?: number;
|
||||
onChange?: (step: { type: string; value: string; goal: number }) => void;
|
||||
onChange?: (step: {
|
||||
type: string;
|
||||
value: string;
|
||||
goal: number;
|
||||
operator?: string;
|
||||
property?: string;
|
||||
}) => void;
|
||||
}) {
|
||||
const [type, setType] = useState(defaultType);
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
const [operator, setOperator] = useState(defaultAggregae);
|
||||
const [property, setProperty] = useState(defaultProperty);
|
||||
const [goal, setGoal] = useState(defaultGoal);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const items = [
|
||||
{ label: formatMessage(labels.url), value: 'url' },
|
||||
{ label: formatMessage(labels.event), value: 'event' },
|
||||
{ label: formatMessage(labels.eventData), value: 'event-data' },
|
||||
];
|
||||
const operators = [
|
||||
{ label: formatMessage(labels.count), value: 'count' },
|
||||
{ label: formatMessage(labels.average), value: 'average' },
|
||||
{ label: formatMessage(labels.sum), value: 'sum' },
|
||||
];
|
||||
const isDisabled = !type || !value;
|
||||
|
||||
const handleSave = () => {
|
||||
onChange({ type, value, goal });
|
||||
onChange(
|
||||
type === 'event-data' ? { type, value, goal, operator, property } : { type, value, goal },
|
||||
);
|
||||
setValue('');
|
||||
setProperty('');
|
||||
setGoal(10);
|
||||
};
|
||||
|
||||
|
|
@ -45,6 +66,10 @@ export function GoalsAddForm({
|
|||
return items.find(item => item.value === value)?.label;
|
||||
};
|
||||
|
||||
const renderoperatorValue = (value: any) => {
|
||||
return operators.find(item => item.value === value)?.label;
|
||||
};
|
||||
|
||||
return (
|
||||
<Flexbox direction="column" gap={10}>
|
||||
<FormRow label={formatMessage(defaultValue ? labels.update : labels.add)}>
|
||||
|
|
@ -70,6 +95,31 @@ export function GoalsAddForm({
|
|||
/>
|
||||
</Flexbox>
|
||||
</FormRow>
|
||||
{type === 'event-data' && (
|
||||
<FormRow label={formatMessage(labels.property)}>
|
||||
<Flexbox gap={10}>
|
||||
<Dropdown
|
||||
className={styles.dropdown}
|
||||
items={operators}
|
||||
value={operator}
|
||||
renderValue={renderoperatorValue}
|
||||
onChange={(value: any) => setOperator(value)}
|
||||
>
|
||||
{({ value, label }) => {
|
||||
return <Item key={value}>{label}</Item>;
|
||||
}}
|
||||
</Dropdown>
|
||||
<TextField
|
||||
className={styles.input}
|
||||
value={property}
|
||||
onChange={e => handleChange(e, setProperty)}
|
||||
autoFocus={true}
|
||||
autoComplete="off"
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</Flexbox>
|
||||
</FormRow>
|
||||
)}
|
||||
<FormRow label={formatMessage(labels.goal)}>
|
||||
<Flexbox gap={10}>
|
||||
<TextField
|
||||
|
|
|
|||
|
|
@ -11,19 +11,36 @@ export function GoalsChart({ className }: { className?: string; isLoading?: bool
|
|||
|
||||
const { data } = report || {};
|
||||
|
||||
const getLabel = type => {
|
||||
let label = '';
|
||||
switch (type) {
|
||||
case 'url':
|
||||
label = labels.viewedPage;
|
||||
break;
|
||||
case 'event':
|
||||
label = labels.triggeredEvent;
|
||||
break;
|
||||
default:
|
||||
label = labels.collectedData;
|
||||
break;
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.chart, className)}>
|
||||
{data?.map(({ type, value, goal, result }, index: number) => {
|
||||
{data?.map(({ type, value, goal, result, property, operator }, index: number) => {
|
||||
const percent = result > goal ? 100 : (result / goal) * 100;
|
||||
|
||||
return (
|
||||
<div key={index} className={styles.goal}>
|
||||
<div className={styles.card}>
|
||||
<div className={styles.header}>
|
||||
<span className={styles.label}>
|
||||
{formatMessage(type === 'url' ? labels.viewedPage : labels.triggeredEvent)}
|
||||
</span>
|
||||
<span className={styles.item}>{value}</span>
|
||||
<span className={styles.label}>{formatMessage(getLabel(type))}</span>
|
||||
<span className={styles.item}>{`${value}${
|
||||
type === 'event-data' ? `:(${operator}):${property}` : ''
|
||||
}`}</span>
|
||||
</div>
|
||||
<div className={styles.track}>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -4,6 +4,16 @@
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
.eventData {
|
||||
color: var(--orange900);
|
||||
background-color: var(--orange100);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
padding: 2px 8px;
|
||||
border-radius: 5px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.goal {
|
||||
color: var(--blue900);
|
||||
background-color: var(--blue100);
|
||||
|
|
@ -11,4 +21,5 @@
|
|||
font-weight: 900;
|
||||
padding: 2px 8px;
|
||||
border-radius: 5px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { formatNumber } from 'lib/format';
|
|||
import { useContext } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Flexbox,
|
||||
Form,
|
||||
FormButtons,
|
||||
FormRow,
|
||||
|
|
@ -79,33 +80,53 @@ export function GoalsParameters() {
|
|||
<BaseParameters allowWebsiteSelect={!id} />
|
||||
<FormRow label={formatMessage(labels.goals)} action={<AddGoalsButton />}>
|
||||
<ParameterList>
|
||||
{goals.map((goal: { type: string; value: string; goal: number }, index: number) => {
|
||||
return (
|
||||
<PopupTrigger key={index}>
|
||||
<ParameterList.Item
|
||||
icon={goal.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />}
|
||||
onRemove={() => handleRemoveGoals(index)}
|
||||
>
|
||||
<div className={styles.value}>{goal.value}</div>
|
||||
<div className={styles.goal}>
|
||||
{formatMessage(labels.goal)}: {formatNumber(goal.goal)}
|
||||
</div>
|
||||
</ParameterList.Item>
|
||||
<Popup alignment="start">
|
||||
{(close: () => void) => (
|
||||
<PopupForm>
|
||||
<GoalsAddForm
|
||||
type={goal.type}
|
||||
value={goal.value}
|
||||
goal={goal.goal}
|
||||
onChange={handleUpdateGoals.bind(null, close, index)}
|
||||
/>
|
||||
</PopupForm>
|
||||
)}
|
||||
</Popup>
|
||||
</PopupTrigger>
|
||||
);
|
||||
})}
|
||||
{goals.map(
|
||||
(
|
||||
goal: {
|
||||
type: string;
|
||||
value: string;
|
||||
goal: number;
|
||||
operator?: string;
|
||||
property?: string;
|
||||
},
|
||||
index: number,
|
||||
) => {
|
||||
return (
|
||||
<PopupTrigger key={index}>
|
||||
<ParameterList.Item
|
||||
icon={goal.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />}
|
||||
onRemove={() => handleRemoveGoals(index)}
|
||||
>
|
||||
<Flexbox direction="column" gap={5}>
|
||||
<div className={styles.value}>{goal.value}</div>
|
||||
{goal.type === 'event-data' && (
|
||||
<div className={styles.eventData}>
|
||||
{formatMessage(labels[goal.operator])}: {goal.property}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.goal}>
|
||||
{formatMessage(labels.goal)}: {formatNumber(goal.goal)}
|
||||
</div>
|
||||
</Flexbox>
|
||||
</ParameterList.Item>
|
||||
<Popup alignment="start">
|
||||
{(close: () => void) => (
|
||||
<PopupForm>
|
||||
<GoalsAddForm
|
||||
type={goal.type}
|
||||
value={goal.value}
|
||||
goal={goal.goal}
|
||||
operator={goal.operator}
|
||||
property={goal.property}
|
||||
onChange={handleUpdateGoals.bind(null, close, index)}
|
||||
/>
|
||||
</PopupForm>
|
||||
)}
|
||||
</Popup>
|
||||
</PopupTrigger>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</ParameterList>
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue