Updated styling of goals report.

This commit is contained in:
Mike Cao 2024-05-10 11:15:23 -07:00
parent f259130202
commit 9f43ae67ef
12 changed files with 93 additions and 85 deletions

View File

@ -6,11 +6,24 @@
.item { .item {
display: flex; display: flex;
flex-direction: row; gap: 12px;
align-items: center; width: 100%;
justify-content: space-between; flex-wrap: nowrap;
padding: 12px; padding: 12px;
border: 1px solid var(--base400); border: 1px solid var(--base400);
border-radius: var(--border-radius); border-radius: var(--border-radius);
box-shadow: 1px 1px 1px var(--base400); box-shadow: 1px 1px 1px var(--base400);
} }
.value {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
flex: 1;
}
.icon,
.close {
height: 1.5rem;
}

View File

@ -24,18 +24,21 @@ export function ParameterList({ children }: ParameterListProps) {
const Item = ({ const Item = ({
children, children,
className, className,
icon,
onClick, onClick,
onRemove, onRemove,
}: { }: {
children?: ReactNode; children?: ReactNode;
className?: string; className?: string;
icon?: ReactNode;
onClick?: () => void; onClick?: () => void;
onRemove?: () => void; onRemove?: () => void;
}) => { }) => {
return ( return (
<div className={classNames(styles.item, className)} onClick={onClick}> <div className={classNames(styles.item, className)} onClick={onClick}>
{children} {icon && <Icon className={styles.icon}>{icon}</Icon>}
<Icon onClick={onRemove}> <div className={styles.value}>{children}</div>
<Icon className={styles.close} onClick={onRemove}>
<Icons.Close /> <Icons.Close />
</Icon> </Icon>
</div> </div>

View File

@ -5,6 +5,7 @@ import Funnel from 'assets/funnel.svg';
import Lightbulb from 'assets/lightbulb.svg'; import Lightbulb from 'assets/lightbulb.svg';
import Magnet from 'assets/magnet.svg'; import Magnet from 'assets/magnet.svg';
import Tag from 'assets/tag.svg'; import Tag from 'assets/tag.svg';
import Target from 'assets/target.svg';
import styles from './ReportTemplates.module.css'; import styles from './ReportTemplates.module.css';
import { useMessages, useTeamUrl } from 'components/hooks'; import { useMessages, useTeamUrl } from 'components/hooks';
@ -41,7 +42,7 @@ export function ReportTemplates({ showHeader = true }: { showHeader?: boolean })
title: formatMessage(labels.goals), title: formatMessage(labels.goals),
description: formatMessage(labels.goalsDescription), description: formatMessage(labels.goalsDescription),
url: renderTeamUrl('/reports/goals'), url: renderTeamUrl('/reports/goals'),
icon: <Tag />, icon: <Target />,
}, },
]; ];

View File

@ -5,10 +5,6 @@
width: 100%; width: 100%;
} }
.type {
color: var(--base700);
}
.value { .value {
display: flex; display: flex;
align-self: center; align-self: center;

View File

@ -93,12 +93,10 @@ export function FunnelParameters() {
<PopupTrigger key={index}> <PopupTrigger key={index}>
<ParameterList.Item <ParameterList.Item
className={styles.item} className={styles.item}
icon={step.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />}
onRemove={() => handleRemoveStep(index)} onRemove={() => handleRemoveStep(index)}
> >
<div className={styles.value}> <div className={styles.value}>
<div className={styles.type}>
<Icon>{step.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />}</Icon>
</div>
<div>{step.value}</div> <div>{step.value}</div>
</div> </div>
</ParameterList.Item> </ParameterList.Item>

View File

@ -76,11 +76,9 @@ export function GoalsAddForm({
className={styles.input} className={styles.input}
value={goal?.toString()} value={goal?.toString()}
onChange={e => handleChange(e, setGoal)} onChange={e => handleChange(e, setGoal)}
autoFocus={true}
autoComplete="off" autoComplete="off"
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
/> />
.
</Flexbox> </Flexbox>
</FormRow> </FormRow>
<FormRow> <FormRow>

View File

@ -1,27 +1,15 @@
.chart { .chart {
display: grid; display: grid;
gap: 30px;
} }
.num { .goal {
display: flex; padding-bottom: 40px;
align-items: center; border-bottom: 1px solid var(--base400);
justify-content: center;
border-radius: 100%;
width: 50px;
height: 50px;
font-size: 16px;
font-weight: 700;
color: var(--base800);
background: var(--base100);
z-index: 1;
} }
.step { .goal:last-child {
display: grid; border: 0;
grid-template-columns: max-content 1fr;
column-gap: 30px;
position: relative;
padding-bottom: 60px;
} }
.card { .card {
@ -36,28 +24,12 @@
gap: 20px; gap: 20px;
} }
.bar {
display: flex;
align-items: center;
justify-content: flex-end;
background: var(--base900);
height: 30px;
border-radius: 5px;
overflow: hidden;
position: relative;
}
.label { .label {
color: var(--base600); color: var(--base600);
font-weight: 700; font-weight: 700;
text-transform: uppercase; text-transform: uppercase;
} }
.track {
background-color: var(--base100);
border-radius: 5px;
}
.item { .item {
font-size: 20px; font-size: 20px;
color: var(--base900); color: var(--base900);
@ -73,7 +45,7 @@
text-transform: lowercase; text-transform: lowercase;
} }
.visitors { .value {
color: var(--base900); color: var(--base900);
font-size: 24px; font-size: 24px;
font-weight: 900; font-weight: 900;
@ -85,3 +57,39 @@
font-weight: 700; font-weight: 700;
align-self: flex-end; align-self: flex-end;
} }
.total {
color: var(--base700);
}
.bar {
display: flex;
align-items: center;
justify-content: flex-end;
background: var(--base900);
height: 10px;
border-radius: 5px;
overflow: hidden;
position: relative;
}
.bar.level1 {
background: var(--red800);
}
.bar.level2 {
background: var(--orange200);
}
.bar.level3 {
background: var(--orange400);
}
.bar.level4 {
background: var(--orange600);
}
.bar.level5 {
background: var(--green600);
}
.track {
background-color: var(--base100);
border-radius: 5px;
}

View File

@ -14,9 +14,10 @@ export function GoalsChart({ className }: { className?: string; isLoading?: bool
return ( return (
<div className={classNames(styles.chart, className)}> <div className={classNames(styles.chart, className)}>
{data?.map(({ type, value, goal, result }, index: number) => { {data?.map(({ type, value, goal, result }, index: number) => {
const percent = result > goal ? 100 : (result / goal) * 100;
return ( return (
<div key={index} className={styles.step}> <div key={index} className={styles.goal}>
<div className={styles.num}>{index + 1}</div>
<div className={styles.card}> <div className={styles.card}>
<div className={styles.header}> <div className={styles.header}>
<span className={styles.label}> <span className={styles.label}>
@ -25,20 +26,24 @@ export function GoalsChart({ className }: { className?: string; isLoading?: bool
<span className={styles.item}>{value}</span> <span className={styles.item}>{value}</span>
</div> </div>
<div className={styles.metric}> <div className={styles.metric}>
<div> <div className={styles.value}>
<span className={styles.visitors}>{formatLongNumber(result)}</span> {formatLongNumber(result)}
{formatMessage(labels.visitors)} <span className={styles.total}> / {formatLongNumber(goal)}</span>
</div>
<div>
<span className={styles.visitors}>{formatLongNumber(goal)}</span>
{formatMessage(labels.goal)}
</div> </div>
<div className={styles.percent}>{((result / goal) * 100).toFixed(2)}%</div> <div className={styles.percent}>{((result / goal) * 100).toFixed(2)}%</div>
</div> </div>
<div className={styles.track}> <div className={styles.track}>
<div <div
className={styles.bar} className={classNames(
style={{ width: `${result > goal ? 100 : (result / goal) * 100}%` }} classNames(styles.bar, {
[styles.level1]: percent <= 20,
[styles.level2]: percent > 20 && percent <= 40,
[styles.level3]: percent > 40 && percent <= 60,
[styles.level4]: percent > 60 && percent <= 80,
[styles.level5]: percent > 80,
}),
)}
style={{ width: `${percent}%` }}
></div> ></div>
</div> </div>
</div> </div>

View File

@ -1,18 +1,7 @@
.item {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
}
.type {
color: var(--base700);
}
.value { .value {
display: flex; width: 100%;
align-self: center; margin-bottom: 8px;
gap: 20px; font-weight: 600;
} }
.goal { .goal {
@ -22,5 +11,4 @@
font-weight: 900; font-weight: 900;
padding: 2px 8px; padding: 2px 8px;
border-radius: 5px; border-radius: 5px;
white-space: nowrap;
} }

View File

@ -83,15 +83,12 @@ export function GoalsParameters() {
return ( return (
<PopupTrigger key={index}> <PopupTrigger key={index}>
<ParameterList.Item <ParameterList.Item
className={styles.item} icon={goal.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />}
onRemove={() => handleRemoveGoals(index)} onRemove={() => handleRemoveGoals(index)}
> >
<div className={styles.value}> <div className={styles.value}>{goal.value}</div>
<div className={styles.type}> <div className={styles.goal}>
<Icon>{goal.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />}</Icon> {formatMessage(labels.goal)}: {formatNumber(goal.goal)}
</div>
<div>{goal.value}</div>
<div className={styles.goal}>{formatNumber(goal.goal)}</div>
</div> </div>
</ParameterList.Item> </ParameterList.Item>
<Popup alignment="start"> <Popup alignment="start">

View File

@ -4,7 +4,7 @@ import Report from '../[reportId]/Report';
import ReportHeader from '../[reportId]/ReportHeader'; import ReportHeader from '../[reportId]/ReportHeader';
import ReportMenu from '../[reportId]/ReportMenu'; import ReportMenu from '../[reportId]/ReportMenu';
import ReportBody from '../[reportId]/ReportBody'; import ReportBody from '../[reportId]/ReportBody';
import Goals from 'assets/funnel.svg'; import Target from 'assets/target.svg';
import { REPORT_TYPES } from 'lib/constants'; import { REPORT_TYPES } from 'lib/constants';
const defaultParameters = { const defaultParameters = {
@ -15,7 +15,7 @@ const defaultParameters = {
export default function GoalsReport({ reportId }: { reportId?: string }) { export default function GoalsReport({ reportId }: { reportId?: string }) {
return ( return (
<Report reportId={reportId} defaultParameters={defaultParameters}> <Report reportId={reportId} defaultParameters={defaultParameters}>
<ReportHeader icon={<Goals />} /> <ReportHeader icon={<Target />} />
<ReportMenu> <ReportMenu>
<GoalsParameters /> <GoalsParameters />
</ReportMenu> </ReportMenu>

1
src/assets/target.svg Normal file
View File

@ -0,0 +1 @@
<svg clip-rule="evenodd" fill-rule="evenodd" height="512" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg"><g id="Icon"><path d="m19.393 10.825c-.097-.403.151-.808.553-.905.402-.098.808.15.905.553.181.75.277 1.533.277 2.338 0 5.485-4.453 9.939-9.939 9.939-5.485 0-9.939-4.454-9.939-9.939 0-5.486 4.454-9.939 9.939-9.939.805 0 1.588.096 2.338.277.403.097.651.503.553.905-.097.402-.502.65-.905.553-.637-.154-1.302-.235-1.986-.235-4.658 0-8.439 3.781-8.439 8.439s3.781 8.439 8.439 8.439 8.439-3.781 8.439-8.439c0-.684-.081-1.349-.235-1.986z"/><path d="m14.764 12.811c0-.414.336-.75.75-.75.413 0 .75.336.75.75 0 2.8-2.274 5.074-5.075 5.074-2.8 0-5.074-2.274-5.074-5.074 0-2.801 2.274-5.075 5.074-5.075.414 0 .75.337.75.75 0 .414-.336.75-.75.75-1.973 0-3.574 1.602-3.574 3.575s1.601 3.574 3.574 3.574 3.575-1.601 3.575-3.574z"/><path d="m22.53 5.588-3.057 3.058c-.141.141-.332.22-.531.22h-3.058c-.414 0-.75-.336-.75-.75v-3.058c0-.199.079-.39.22-.531l3.058-3.057c.184-.184.45-.26.703-.2s.457.246.539.493l.646 1.937 1.937.646c.247.082.433.286.493.539s-.016.519-.2.703zm-1.918-.202-1.142-.381c-.224-.075-.4-.251-.475-.475l-.381-1.142-1.98 1.98v1.998h1.998z"/><path d="m15.354 7.585c.293-.293.768-.293 1.061 0s.293.768 0 1.061l-4.587 4.586c-.293.293-.768.293-1.06 0-.293-.292-.293-.767 0-1.06z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB