update icons for

This commit is contained in:
Nate Kelley 2025-03-01 23:54:44 -07:00
parent 845b1bd017
commit f625ec4276
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
12 changed files with 309 additions and 254 deletions

View File

@ -1,7 +1,7 @@
import { ShareAssetType, VerificationStatus, BusterChatListItem } from '@/api/asset_interfaces';
import { makeHumanReadble, formatDate } from '@/lib';
import React, { memo, useMemo, useRef, useState } from 'react';
import { StatusBadgeIndicator, FavoriteStar, getShareStatus } from '@/components/features/list';
import { FavoriteStar, getShareStatus } from '@/components/features/list';
import { Text } from '@/components/ui';
import { Avatar } from '@/components/ui/avatar';
import { BusterRoutes, createBusterRoute } from '@/routes';
@ -152,9 +152,6 @@ const TitleCell = React.memo<{ title: string; status: VerificationStatus; chatId
return (
<div className="flex w-full items-center space-x-2">
<div className="flex items-center justify-center">
<StatusBadgeIndicator status={status} />
</div>
<Text ellipsis={true}>{title}</Text>
<div className="flex items-center" onClick={onFavoriteDivClick}>
<FavoriteStar

View File

@ -31,11 +31,7 @@ export const ChatSelectedOptionPopup: React.FC<{
selectedRowKeys={selectedRowKeys}
onSelectChange={onSelectChange}
/>,
<StatusButton
key="status"
selectedRowKeys={selectedRowKeys}
onSelectChange={onSelectChange}
/>,
<DeleteButton
key="delete"
selectedRowKeys={selectedRowKeys}
@ -122,22 +118,6 @@ const DashboardButton: React.FC<{
);
};
const StatusButton: React.FC<{
selectedRowKeys: string[];
onSelectChange: (selectedRowKeys: string[]) => void;
}> = ({ selectedRowKeys, onSelectChange }) => {
return (
<StatusBadgeButton
status={VerificationStatus.notRequested}
type="metric"
id={selectedRowKeys}
onChangedStatus={async () => {
onSelectChange([]);
}}
/>
);
};
const DeleteButton: React.FC<{
selectedRowKeys: string[];
onSelectChange: (selectedRowKeys: string[]) => void;

View File

@ -1,219 +0,0 @@
'use client';
import { BusterMetricListItem, VerificationStatus } from '@/api/asset_interfaces';
import { AppMaterialIcons, AppPopoverMenu, AppTooltip } from '@/components/ui';
import { useBusterDashboardContextSelector } from '@/context/Dashboards';
import { useUserConfigContextSelector } from '@/context/Users';
import { useBusterMetricsIndividualContextSelector } from '@/context/Metrics';
import { useMemoizedFn } from 'ahooks';
import { Button } from 'antd';
import React, { useMemo, useState } from 'react';
import { StatusNotRequestedIcon } from '@/assets';
export const StatusBadgeButton: React.FC<{
status: BusterMetricListItem['status'];
type: 'metric' | 'dashboard';
id: string | string[];
disabled?: boolean;
onChangedStatus?: () => Promise<void>;
}> = React.memo(
({ type, id, status = VerificationStatus.notRequested, onChangedStatus, disabled }) => {
const onVerifiedDashboard = useBusterDashboardContextSelector(
(state) => state.onVerifiedDashboard
);
const onVerifiedMetric = useBusterMetricsIndividualContextSelector(
(state) => state.onVerifiedMetric
);
const isAdmin = useUserConfigContextSelector((state) => state.isAdmin);
const text = useMemo(() => getTooltipText(status), [status]);
const [isOpen, setIsOpen] = React.useState(false);
const onOpenChange = useMemoizedFn((open: boolean) => {
setIsOpen(open);
});
const onChangeStatus = useMemoizedFn(async (newStatus: VerificationStatus) => {
const userStatus = [VerificationStatus.notRequested, VerificationStatus.requested];
if ((!isAdmin && !userStatus.includes(newStatus)) || newStatus === status) {
return;
}
const ids = Array.isArray(id) ? id : [id];
const verifyFunction =
type === 'dashboard'
? (id: string) => onVerifiedDashboard({ dashboardId: id, status: newStatus })
: (id: string) => onVerifiedMetric({ metricId: id, status: newStatus });
const allPromises = ids.map((id) => verifyFunction(id));
await Promise.all(allPromises);
setIsOpen(false);
onChangedStatus?.();
});
const items = useMemo(
() =>
[
{
label: getTooltipText(VerificationStatus.notRequested),
icon: <StatusBadgeIndicator status={VerificationStatus.notRequested} />,
key: VerificationStatus.notRequested,
onClick: () => {
onChangeStatus(VerificationStatus.notRequested);
}
},
{
label: getTooltipText(VerificationStatus.requested),
icon: <StatusBadgeIndicator status={VerificationStatus.requested} />,
key: VerificationStatus.requested,
onClick: () => {
onChangeStatus(VerificationStatus.requested);
}
},
{
label: getTooltipText(VerificationStatus.inReview),
icon: <StatusBadgeIndicator status={VerificationStatus.inReview} />,
key: VerificationStatus.inReview,
disabled: !isAdmin,
onClick: () => {
isAdmin && onChangeStatus(VerificationStatus.inReview);
}
},
{
label: getTooltipText(VerificationStatus.verified),
icon: <StatusBadgeIndicator status={VerificationStatus.verified} />,
key: VerificationStatus.verified,
disabled: !isAdmin,
onClick: () => {
isAdmin && onChangeStatus(VerificationStatus.verified);
}
},
{
label: getTooltipText(VerificationStatus.backlogged),
icon: <StatusBadgeIndicator status={VerificationStatus.backlogged} />,
key: VerificationStatus.backlogged,
disabled: !isAdmin,
onClick: () => {
isAdmin && onChangeStatus(VerificationStatus.backlogged);
}
}
].map((item, index) => ({
index,
...item
})),
[isAdmin, status]
);
const selectedItem = useMemo(
() => items.find((item) => item!.key === status) || items[0],
[text]
);
return (
<AppPopoverMenu
items={items}
trigger={['click']}
onOpenChange={onOpenChange}
open={isOpen}
hideCheckbox
doNotSortSelected={true}
disabled={disabled}
destroyPopupOnHide={true}
selectedItems={selectedItem?.key ? [selectedItem.key! as string] : []}
placement="bottomRight"
headerContent={'Verification status...'}>
<AppTooltip title={isOpen ? '' : 'Request verification from data team'}>
<Button
disabled={disabled || ((!id || status === 'verified') && !isAdmin)}
icon={<StatusBadgeIndicator showTooltip={false} status={status} size={14} />}
type={Array.isArray(id) ? 'default' : 'text'}>
{Array.isArray(id) ? 'Status' : ''}
</Button>
</AppTooltip>
</AppPopoverMenu>
);
}
);
StatusBadgeButton.displayName = 'StatusBadgeButton';
export const StatusBadgeIndicator: React.FC<{
status: BusterMetricListItem['status'];
size?: number;
className?: string;
showTooltip?: boolean;
}> = ({
showTooltip = true,
status = VerificationStatus.notRequested,
size = 16,
className = ''
}) => {
const [isHovering, setIsHovering] = useState(false);
const Icon = getIcon(status);
const colorClasses = getColorClasses(status);
const tooltipText = getTooltipText(status);
const isNotVerified =
status === VerificationStatus.notVerified || VerificationStatus.notRequested;
const sharedClass = `h-[16px] w-[16px] flex items-center justify-center rounded-full ${colorClasses}`;
const _size = isNotVerified ? size : 16;
const mouseEvents = showTooltip
? { onMouseEnter: () => setIsHovering(true), onMouseLeave: () => setIsHovering(false) }
: {};
return (
<AppTooltip title={showTooltip && isHovering ? tooltipText : ''}>
<div
{...mouseEvents}
className={`rounded-full ${className} ${sharedClass} ${isNotVerified ? '' : ''}`}
style={{
width: _size,
height: _size
}}>
<Icon size={_size * 1} />
</div>
</AppTooltip>
);
};
const statusRecordIcon: Record<VerificationStatus, React.FC<any>> = {
[VerificationStatus.verified]: () => <AppMaterialIcons icon="check_circle" fill />,
[VerificationStatus.requested]: () => <AppMaterialIcons icon="contrast" />, //contrast
[VerificationStatus.inReview]: () => <AppMaterialIcons icon="timelapse" />,
[VerificationStatus.backlogged]: () => <AppMaterialIcons icon="cancel" fill />,
[VerificationStatus.notVerified]: () => <StatusNotRequestedIcon />,
[VerificationStatus.notRequested]: () => <StatusNotRequestedIcon />
};
const getIcon = (status: BusterMetricListItem['status']) => {
return statusRecordIcon[status] || (() => <AppMaterialIcons icon="motion_photos_on" />);
};
const statusRecordColors: Record<VerificationStatus, string> = {
verified: 'text-[#34A32D]!',
requested: 'text-[#F2BE01]!',
inReview: 'text-[#7C3AED]!',
backlogged: 'text-[#575859]!',
notVerified: 'text-[#575859]!',
notRequested: 'text-[#575859]!'
};
const getColorClasses = (status: BusterMetricListItem['status']) => {
return statusRecordColors[status] || statusRecordColors.notRequested;
};
const statusRecordText: Record<VerificationStatus, string> = {
verified: 'Verified',
requested: 'Requested',
inReview: 'In review',
backlogged: 'Backlogged',
notVerified: 'Not verified',
notRequested: 'Not requested'
};
const getTooltipText = (status: VerificationStatus) => {
return statusRecordText[status] || statusRecordText.notRequested;
};
export const getShareStatus = ({ is_shared }: { is_shared: BusterMetricListItem['is_shared'] }) => {
if (is_shared) return 'Shared';
return 'Private';
};

View File

@ -1,3 +1,3 @@
export * from './StatusBadgeIndicator';
export * from '../metrics/StatusBadgeIndicator';
export * from './FavoriteStar';
export * from './ListUserItem';

View File

@ -0,0 +1,95 @@
import { type BusterMetricListItem, VerificationStatus } from '@/api/asset_interfaces';
import React, { useMemo } from 'react';
import { getTooltipText } from './helpers';
import { useMemoizedFn } from 'ahooks';
import { AppPopoverMenu, AppTooltip } from '@/components/ui/tooltip';
import { Button } from '@/components/ui/buttons';
import { StatusBadgeIndicator } from './StatusBadgeIndicator';
export const StatusBadgeButton: React.FC<{
status: BusterMetricListItem['status'];
id: string | string[];
disabled?: boolean;
isAdmin: boolean | undefined;
onVerify: (d: { id: string; status: VerificationStatus }[]) => Promise<void>;
}> = React.memo(({ isAdmin, id, status = VerificationStatus.notRequested, onVerify, disabled }) => {
const text = useMemo(() => getTooltipText(status), [status]);
const [isOpen, setIsOpen] = React.useState(false);
const onOpenChange = useMemoizedFn((open: boolean) => {
setIsOpen(open);
});
const onChangeStatus = useMemoizedFn(async (newStatus: VerificationStatus) => {
const userStatus = [VerificationStatus.notRequested, VerificationStatus.requested];
if ((!isAdmin && !userStatus.includes(newStatus)) || newStatus === status) {
return;
}
const ids = Array.isArray(id) ? id : [id];
const params = ids.map((id) => ({ id, status: newStatus }));
await onVerify(params);
setIsOpen(false);
});
const items = useMemo(() => {
const statuses = [
VerificationStatus.notRequested,
VerificationStatus.requested,
VerificationStatus.inReview,
VerificationStatus.verified,
VerificationStatus.backlogged
];
const requiresAdminItems = [
VerificationStatus.inReview,
VerificationStatus.verified,
VerificationStatus.backlogged
];
return statuses.map((status, index) => {
const requiresAdmin = requiresAdminItems.includes(status);
return {
index,
label: getTooltipText(status),
icon: <StatusBadgeIndicator status={status} />,
key: status,
disabled: requiresAdmin && !isAdmin,
onClick: () => {
if (!requiresAdmin || isAdmin) {
onChangeStatus(status);
}
}
};
});
}, [isAdmin, status, onChangeStatus]);
const selectedItem = useMemo(
() => items.find((item) => item!.key === status) || items[0],
[text]
);
return (
<AppPopoverMenu
items={items}
trigger={['click']}
onOpenChange={onOpenChange}
open={isOpen}
hideCheckbox
doNotSortSelected={true}
disabled={disabled}
destroyPopupOnHide={true}
selectedItems={selectedItem?.key ? [selectedItem.key! as string] : []}
placement="bottomRight"
headerContent={'Verification status...'}>
<AppTooltip title={isOpen ? '' : 'Request verification from data team'}>
<Button
disabled={disabled || ((!id || status === 'verified') && !isAdmin)}
prefix={<StatusBadgeIndicator showTooltip={false} status={status} size={14} />}
variant={Array.isArray(id) ? 'default' : 'ghost'}>
{Array.isArray(id) ? 'Status' : ''}
</Button>
</AppTooltip>
</AppPopoverMenu>
);
});
StatusBadgeButton.displayName = 'StatusBadgeButton';

View File

@ -0,0 +1,101 @@
import type { Meta, StoryObj } from '@storybook/react';
import { StatusBadgeIndicator } from './StatusBadgeIndicator';
import { VerificationStatus } from '@/api/asset_interfaces';
const meta = {
title: 'Features/Metrics/StatusBadgeIndicator',
component: StatusBadgeIndicator,
parameters: {
layout: 'centered'
},
tags: ['autodocs'],
args: {
status: VerificationStatus.notRequested,
size: 16,
showTooltip: true
},
argTypes: {
status: {
control: 'select',
options: Object.values(VerificationStatus),
description: 'The verification status of the badge'
},
size: {
control: { type: 'number' },
description: 'The size of the badge in pixels',
defaultValue: 16
},
showTooltip: {
control: 'boolean',
description: 'Whether to show a tooltip on hover',
defaultValue: true
},
className: {
control: 'text',
description: 'Additional CSS classes to apply to the badge'
}
}
} satisfies Meta<typeof StatusBadgeIndicator>;
export default meta;
type Story = StoryObj<typeof meta>;
// Basic StatusBadgeIndicator examples for each status
export const NotRequested: Story = {
args: {
status: VerificationStatus.notRequested
}
};
export const Requested: Story = {
args: {
status: VerificationStatus.requested
}
};
export const InReview: Story = {
args: {
status: VerificationStatus.inReview
}
};
export const Verified: Story = {
args: {
status: VerificationStatus.verified
}
};
export const Backlogged: Story = {
args: {
status: VerificationStatus.backlogged
}
};
export const NotVerified: Story = {
args: {
status: VerificationStatus.notVerified
}
};
// Size variations
export const LargeSize: Story = {
args: {
status: VerificationStatus.verified,
size: 24
}
};
export const SmallSize: Story = {
args: {
status: VerificationStatus.verified,
size: 12
}
};
// Without tooltip
export const WithoutTooltip: Story = {
args: {
status: VerificationStatus.verified,
showTooltip: false
}
};

View File

@ -0,0 +1,70 @@
import { BusterMetricListItem, VerificationStatus } from '@/api/asset_interfaces';
import { AppTooltip } from '@/components/ui/tooltip';
import React from 'react';
import { StatusNotRequestedIcon } from '@/assets/svg/Status_NotRequested';
import {
CircleCheck,
CircleXmark,
ProgressCircle1Of4,
ProgressCircle2Of4
} from '@/components/ui/icons/NucleoIconFilled';
import { getTooltipText } from './helpers';
export const StatusBadgeIndicator: React.FC<{
status: BusterMetricListItem['status'];
size?: number;
className?: string;
showTooltip?: boolean;
}> = ({
showTooltip = true,
status = VerificationStatus.notRequested,
size = 16,
className = ''
}) => {
const Icon = getIcon(status);
const colorClasses = getColorClasses(status);
const tooltipText = getTooltipText(status);
const isNotVerified =
status === VerificationStatus.notVerified || VerificationStatus.notRequested;
const sharedClass = `h-[16px] w-[16px] flex items-center justify-center rounded-full ${colorClasses}`;
const _size = isNotVerified ? size : 16;
return (
<AppTooltip title={showTooltip ? tooltipText : ''}>
<div
className={`rounded-full ${className} ${sharedClass} ${isNotVerified ? '' : ''}`}
style={{
width: _size,
height: _size
}}>
<Icon size={_size} />
</div>
</AppTooltip>
);
};
const statusRecordIcon: Record<VerificationStatus, React.FC<any>> = {
[VerificationStatus.verified]: () => <CircleCheck />,
[VerificationStatus.requested]: () => <ProgressCircle1Of4 />, //contrast
[VerificationStatus.inReview]: () => <ProgressCircle2Of4 />,
[VerificationStatus.backlogged]: () => <CircleXmark />,
[VerificationStatus.notVerified]: () => <StatusNotRequestedIcon />,
[VerificationStatus.notRequested]: () => <StatusNotRequestedIcon />
};
const getIcon = (status: BusterMetricListItem['status']) => {
return statusRecordIcon[status] || (() => <ProgressCircle2Of4 />);
};
const statusRecordColors: Record<VerificationStatus, string> = {
verified: 'text-[#34A32D]!',
requested: 'text-[#F2BE01]!',
inReview: 'text-[#7C3AED]!',
backlogged: 'text-[#575859]!',
notVerified: 'text-[#575859]!',
notRequested: 'text-[#575859]!'
};
const getColorClasses = (status: BusterMetricListItem['status']) => {
return statusRecordColors[status] || statusRecordColors.notRequested;
};

View File

@ -0,0 +1,19 @@
import type { VerificationStatus, BusterMetricListItem } from '@/api/asset_interfaces';
const statusRecordText: Record<VerificationStatus, string> = {
verified: 'Verified',
requested: 'Requested',
inReview: 'In review',
backlogged: 'Backlogged',
notVerified: 'Not verified',
notRequested: 'Not requested'
};
export const getTooltipText = (status: VerificationStatus) => {
return statusRecordText[status] || statusRecordText.notRequested;
};
export const getShareStatus = ({ is_shared }: { is_shared: BusterMetricListItem['is_shared'] }) => {
if (is_shared) return 'Shared';
return 'Private';
};

View File

@ -0,0 +1,3 @@
export * from './StatusBadgeIndicator';
export * from './StatusBadgeButton';
export * from './helpers';

View File

@ -4,7 +4,7 @@ import React, { useEffect } from 'react';
import { Button, Checkbox } from 'antd';
import { useAntToken } from '@/styles/useAntToken';
import { AppMaterialIcons } from '../icons';
import { Text } from '@/components/ui';
import { Text } from '@/components/ui/typography';
import Link from 'next/link';
export const AppSelectItem: React.FC<{
@ -79,7 +79,7 @@ export const AppSelectItem: React.FC<{
)}
{validIndex && (
<Text
type={disabled ? 'tertiary' : 'secondary'}
variant={disabled ? 'tertiary' : 'secondary'}
className={`min-w-[9px] text-center ${link ? '' : ''} `}>
{index}
</Text>

View File

@ -1,8 +1,11 @@
import { ShareAssetType, VerificationStatus, BusterMetricListItem } from '@/api/asset_interfaces';
import { makeHumanReadble, formatDate } from '@/lib';
import React, { memo, useMemo, useRef, useState } from 'react';
import { StatusBadgeIndicator, getShareStatus } from '@/components/features/list';
import { Text } from '@/components/ui';
import {
StatusBadgeIndicator,
getShareStatus
} from '@/components/features/metrics/StatusBadgeIndicator';
import { Text } from '@/components/ui/typography';
import { Avatar } from '@/components/ui/avatar';
import { BusterRoutes, createBusterRoute } from '@/routes';
import { useMemoizedFn } from 'ahooks';
@ -179,7 +182,7 @@ const TitleCell = React.memo<{ title: string; status: VerificationStatus; metric
<div className="flex items-center justify-center">
<StatusBadgeIndicator status={status} />
</div>
<Text ellipsis={true}>{title}</Text>
<Text truncate>{title}</Text>
<div className="flex items-center" onClick={onFavoriteDivClick}>
<FavoriteStar
id={metricId}

View File

@ -123,14 +123,20 @@ const StatusButton: React.FC<{
selectedRowKeys: string[];
onSelectChange: (selectedRowKeys: string[]) => void;
}> = ({ selectedRowKeys, onSelectChange }) => {
const onVerifiedMetric = useBusterMetricsIndividualContextSelector(
(state) => state.onVerifiedMetric
);
const onVerify = useMemoizedFn(async (d: { id: string; status: VerificationStatus }[]) => {
// await onVerifiedMetric(d);
onSelectChange([]);
});
return (
<StatusBadgeButton
status={VerificationStatus.notRequested}
type="metric"
id={selectedRowKeys}
onChangedStatus={async () => {
onSelectChange([]);
}}
onVerify={onVerify}
/>
);
};