ui: fixed card and tag removal, alert buttons, kai input disable state

This commit is contained in:
nick-delirium 2025-05-30 10:34:04 +02:00
parent 75230d72ec
commit 0d9c8d70c8
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
7 changed files with 96 additions and 82 deletions

View file

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import { Button, Form, Input, Space, Modal } from 'antd'; import { Button, Form, Input, Space } from 'antd';
import { Trash } from 'UI/Icons'; import { Trash } from 'UI/Icons';
import { useStore } from '@/mstore'; import { useStore } from '@/mstore';
import { useModal } from 'Components/ModalContext'; import { useModal } from 'Components/ModalContext';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { confirm } from 'UI';
interface Props { interface Props {
tag: any; tag: any;
projectId: number; projectId: number;
@ -23,14 +23,16 @@ function TagForm(props: Props) {
}; };
const onDelete = async () => { const onDelete = async () => {
Modal.confirm({ if (
title: t('Tag'), await confirm({
content: t('Are you sure you want to remove?'), header: t('Remove Tag'),
onOk: async () => { confirmButton: t('Remove'),
await tagWatchStore.deleteTag(tag.tagId, projectId); confirmation: t('Are you sure you want to remove this tag?'),
closeModal(); })
}, ) {
}); await tagWatchStore.deleteTag(tag.tagId, projectId);
closeModal();
}
}; };
const onSave = async () => { const onSave = async () => {

View file

@ -23,6 +23,7 @@ function BottomButtons({
<Button <Button
loading={loading} loading={loading}
type="primary" type="primary"
htmlType="submit"
disabled={loading || !instance.validate()} disabled={loading || !instance.validate()}
id="submit-button" id="submit-button"
> >

View file

@ -90,6 +90,7 @@ function Condition({
<label className="w-1/6 flex-shrink-0 font-normal">{t('is')}</label> <label className="w-1/6 flex-shrink-0 font-normal">{t('is')}</label>
<div className="w-2/6 flex items-center"> <div className="w-2/6 flex items-center">
<Select <Select
popupMatchSelectWidth={false}
placeholder={t('Select Condition')} placeholder={t('Select Condition')}
options={localizedConditions} options={localizedConditions}
name="operator" name="operator"

View file

@ -7,19 +7,18 @@ import {
Button, Button,
Dropdown, Dropdown,
Modal as AntdModal, Modal as AntdModal,
Avatar, TableColumnType, Spin Avatar,
TableColumnType,
Spin,
} from 'antd'; } from 'antd';
import { import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
EditOutlined,
DeleteOutlined,
} from '@ant-design/icons';
import { EllipsisVertical } from 'lucide-react'; import { EllipsisVertical } from 'lucide-react';
import { TablePaginationConfig, SorterResult } from 'antd/lib/table/interface'; import { TablePaginationConfig, SorterResult } from 'antd/lib/table/interface';
import { useStore } from 'App/mstore'; import { useStore } from 'App/mstore';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { withSiteId } from 'App/routes'; import { withSiteId } from 'App/routes';
import { Icon } from 'UI'; import { Icon, confirm } from 'UI';
import cn from 'classnames'; import cn from 'classnames';
import { TYPE_ICONS, TYPE_NAMES } from 'App/constants/card'; import { TYPE_ICONS, TYPE_NAMES } from 'App/constants/card';
import Widget from 'App/mstore/types/widget'; import Widget from 'App/mstore/types/widget';
@ -45,7 +44,7 @@ const ListView: React.FC<Props> = ({
toggleSelection, toggleSelection,
disableSelection = false, disableSelection = false,
inLibrary = false, inLibrary = false,
loading = false loading = false,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [editingMetricId, setEditingMetricId] = useState<number | null>(null); const [editingMetricId, setEditingMetricId] = useState<number | null>(null);
@ -63,7 +62,7 @@ const ListView: React.FC<Props> = ({
<Text strong> <Text strong>
{Math.min( {Math.min(
(metricStore.pageSize || 10) * (metricStore.page || 1), (metricStore.pageSize || 10) * (metricStore.page || 1),
list.length list.length,
)} )}
</Text>{' '} </Text>{' '}
{t('of')}&nbsp;<Text strong>{list.length}</Text>&nbsp;{t('cards')} {t('of')}&nbsp;<Text strong>{list.length}</Text>&nbsp;{t('cards')}
@ -124,15 +123,17 @@ const ListView: React.FC<Props> = ({
const onMenuClick = async (metric: Widget, { key }: { key: string }) => { const onMenuClick = async (metric: Widget, { key }: { key: string }) => {
if (key === 'delete') { if (key === 'delete') {
AntdModal.confirm({ if (
title: t('Confirm'), await confirm({
content: t('Are you sure you want to permanently delete this card?'), header: t('Delete Card'),
okText: t('Yes, delete'), confirmButton: t('Delete'),
cancelText: t('No'), confirmation: t(
onOk: async () => { 'Are you sure you want to permanently delete this card? This action cannot be undone.',
await metricStore.delete(metric); ),
} })
}); ) {
await metricStore.delete(metric);
}
} }
if (key === 'rename') { if (key === 'rename') {
setEditingMetricId(metric.metricId); setEditingMetricId(metric.metricId);
@ -155,7 +156,7 @@ const ListView: React.FC<Props> = ({
const menuItems = [ const menuItems = [
{ key: 'rename', icon: <EditOutlined />, label: t('Rename') }, { key: 'rename', icon: <EditOutlined />, label: t('Rename') },
{ key: 'delete', icon: <DeleteOutlined />, label: t('Delete') } { key: 'delete', icon: <DeleteOutlined />, label: t('Delete') },
]; ];
const renderTitle = (_text: string, metric: Widget) => ( const renderTitle = (_text: string, metric: Widget) => (
@ -201,9 +202,10 @@ const ListView: React.FC<Props> = ({
key: 'title', key: 'title',
className: 'cap-first pl-4', className: 'cap-first pl-4',
sorter: true, sorter: true,
sortOrder: metricStore.sort.field === 'name' ? metricStore.sort.order : undefined, sortOrder:
metricStore.sort.field === 'name' ? metricStore.sort.order : undefined,
width: inLibrary ? '31%' : '25%', width: inLibrary ? '31%' : '25%',
render: renderTitle render: renderTitle,
}, },
{ {
title: t('Owner'), title: t('Owner'),
@ -211,19 +213,25 @@ const ListView: React.FC<Props> = ({
key: 'owner', key: 'owner',
className: 'capitalize', className: 'capitalize',
sorter: true, sorter: true,
sortOrder: metricStore.sort.field === 'owner_email' ? metricStore.sort.order : undefined, sortOrder:
metricStore.sort.field === 'owner_email'
? metricStore.sort.order
: undefined,
width: inLibrary ? '31%' : '25%', width: inLibrary ? '31%' : '25%',
render: renderOwner render: renderOwner,
}, },
{ {
title: t('Last Modified'), title: t('Last Modified'),
dataIndex: 'edited_at', dataIndex: 'edited_at',
key: 'lastModified', key: 'lastModified',
sorter: true, sorter: true,
sortOrder: metricStore.sort.field === 'edited_at' ? metricStore.sort.order : undefined, sortOrder:
metricStore.sort.field === 'edited_at'
? metricStore.sort.order
: undefined,
width: inLibrary ? '31%' : '25%', width: inLibrary ? '31%' : '25%',
render: renderLastModified render: renderLastModified,
} },
]; ];
if (!inLibrary) { if (!inLibrary) {
@ -232,14 +240,14 @@ const ListView: React.FC<Props> = ({
key: 'options', key: 'options',
className: 'text-right', className: 'text-right',
width: '5%', width: '5%',
render: renderOptions render: renderOptions,
}); });
} }
const handleTableChange = ( const handleTableChange = (
pag: TablePaginationConfig, pag: TablePaginationConfig,
_filters: Record<string, (string | number | boolean)[] | null>, _filters: Record<string, (string | number | boolean)[] | null>,
sorterParam: SorterResult<Widget> | SorterResult<Widget>[] sorterParam: SorterResult<Widget> | SorterResult<Widget>[],
) => { ) => {
const sorter = Array.isArray(sorterParam) ? sorterParam[0] : sorterParam; const sorter = Array.isArray(sorterParam) ? sorterParam[0] : sorterParam;
let order = sorter.order; let order = sorter.order;
@ -268,19 +276,19 @@ const ListView: React.FC<Props> = ({
onRow={ onRow={
inLibrary inLibrary
? (record) => ({ ? (record) => ({
onClick: () => { onClick: () => {
if (!disableSelection) toggleSelection?.(record?.metricId); if (!disableSelection) toggleSelection?.(record?.metricId);
} },
}) })
: undefined : undefined
} }
rowSelection={ rowSelection={
!disableSelection !disableSelection
? { ? {
selectedRowKeys: selectedList, selectedRowKeys: selectedList,
onChange: (keys) => toggleSelection && toggleSelection(keys), onChange: (keys) => toggleSelection && toggleSelection(keys),
columnWidth: 16 columnWidth: 16,
} }
: undefined : undefined
} }
pagination={{ pagination={{
@ -292,7 +300,7 @@ const ListView: React.FC<Props> = ({
showLessItems: true, showLessItems: true,
showTotal: () => totalMessage, showTotal: () => totalMessage,
size: 'small', size: 'small',
simple: true simple: true,
}} }}
/> />
<AntdModal <AntdModal

View file

@ -10,10 +10,10 @@ import AddCardSection from '../AddCardSection/AddCardSection';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
function MetricsList({ function MetricsList({
siteId, siteId,
onSelectionChange, onSelectionChange,
inLibrary inLibrary,
}: { }: {
siteId: string; siteId: string;
onSelectionChange?: (selected: any[]) => void; onSelectionChange?: (selected: any[]) => void;
inLibrary?: boolean; inLibrary?: boolean;
@ -26,16 +26,16 @@ function MetricsList({
const dashboard = dashboardStore.selectedDashboard; const dashboard = dashboardStore.selectedDashboard;
const existingCardIds = useMemo( const existingCardIds = useMemo(
() => dashboard?.widgets?.map((i) => parseInt(i.metricId)), () => dashboard?.widgets?.map((i) => parseInt(i.metricId)),
[dashboard] [dashboard],
); );
const cards = useMemo( const cards = useMemo(
() => () =>
onSelectionChange onSelectionChange
? metricStore.filteredCards.filter( ? metricStore.filteredCards.filter(
(i) => !existingCardIds?.includes(parseInt(i.metricId)) (i) => !existingCardIds?.includes(parseInt(i.metricId)),
) )
: metricStore.filteredCards, : metricStore.filteredCards,
[metricStore.filteredCards, existingCardIds, onSelectionChange] [metricStore.filteredCards, existingCardIds, onSelectionChange],
); );
const loading = metricStore.isLoading; const loading = metricStore.isLoading;
@ -66,7 +66,8 @@ function MetricsList({
metricStore.updateKey('sessionsPage', 1); metricStore.updateKey('sessionsPage', 1);
}, [metricStore]); }, [metricStore]);
const isFiltered = metricStore.filter.query !== '' || metricStore.filter.type !== ''; const isFiltered =
metricStore.filter.query !== '' || metricStore.filter.type !== '';
const searchImageDimensions = { width: 60, height: 'auto' }; const searchImageDimensions = { width: 60, height: 'auto' };
const defaultImageDimensions = { width: 600, height: 'auto' }; const defaultImageDimensions = { width: 600, height: 'auto' };
@ -93,7 +94,9 @@ function MetricsList({
) : ( ) : (
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div> <div>
{t('Create and customize cards to analyze trends and user behavior effectively.')} {t(
'Create and customize cards to analyze trends and user behavior effectively.',
)}
</div> </div>
<Popover <Popover
arrow={false} arrow={false}

View file

@ -1,7 +1,7 @@
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { useStore } from 'App/mstore'; import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import { Button, Dropdown, MenuProps, Modal } from 'antd'; import { Button, Dropdown, MenuProps } from 'antd';
import { import {
BellIcon, BellIcon,
EllipsisVertical, EllipsisVertical,
@ -14,6 +14,7 @@ import { useModal } from 'Components/ModalContext';
import AlertFormModal from 'Components/Alerts/AlertFormModal/AlertFormModal'; import AlertFormModal from 'Components/Alerts/AlertFormModal/AlertFormModal';
import { showAddToDashboardModal } from 'Components/Dashboard/components/AddToDashboardButton'; import { showAddToDashboardModal } from 'Components/Dashboard/components/AddToDashboardButton';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { confirm } from 'UI';
function CardViewMenu() { function CardViewMenu() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -51,29 +52,25 @@ function CardViewMenu() {
label: t('Delete'), label: t('Delete'),
icon: <TrashIcon size={15} />, icon: <TrashIcon size={15} />,
disabled: !widget.exists(), disabled: !widget.exists(),
onClick: () => { onClick: async () => {
Modal.confirm({ if (
title: t('Confirm Card Deletion'), await confirm({
icon: null, header: t('Remove Card'),
content: confirmButton: t('Remove'),
t('Are you sure you want to remove this card? This action is permanent and cannot be undone.'), confirmation: t(
footer: (_, { OkBtn, CancelBtn }) => ( 'Are you sure you want to remove this card? This action is permanent and cannot be undone.',
<> ),
<CancelBtn /> })
<OkBtn /> ) {
</> metricStore
), .delete(widget)
onOk: () => { .then(() => {
metricStore history.goBack();
.delete(widget) })
.then(() => { .catch(() => {
history.goBack(); toast.error(t('Failed to remove card'));
}) });
.catch(() => { }
toast.error(t('Failed to remove card'));
});
},
});
}, },
}, },
]; ];

View file

@ -25,7 +25,9 @@ function ChatInput({
kaiStore.setQueryText(text); kaiStore.setQueryText(text);
}; };
const submit = () => { const submit = (e: any) => {
e.preventDefault();
e.stopPropagation();
if (limited) { if (limited) {
return; return;
} }
@ -80,7 +82,7 @@ function ChatInput({
); );
} }
return ( return (
<div className="relative"> <div className="relative bg-white">
<Input <Input
onPressEnter={submit} onPressEnter={submit}
onKeyDown={(e) => { onKeyDown={(e) => {