parent
512230f224
commit
31d82f9d1d
14 changed files with 367 additions and 124 deletions
|
|
@ -1,60 +1,93 @@
|
||||||
|
import { Card, Col, Modal, Row, Typography } from 'antd';
|
||||||
|
import { GalleryVertical, Plus } from 'lucide-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Card, Col, Modal, Row, Typography} from "antd";
|
|
||||||
import {GalleryVertical, Plus} from "lucide-react";
|
import { useStore } from 'App/mstore';
|
||||||
import NewDashboardModal from "Components/Dashboard/components/DashboardList/NewDashModal";
|
import NewDashboardModal from 'Components/Dashboard/components/DashboardList/NewDashModal';
|
||||||
import {useStore} from "App/mstore";
|
|
||||||
|
import AiQuery from './DashboardView/AiQuery';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AddCardSelectionModal(props: Props) {
|
function AddCardSelectionModal(props: Props) {
|
||||||
const {metricStore} = useStore();
|
const { metricStore } = useStore();
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const [isLibrary, setIsLibrary] = React.useState(false);
|
const [isLibrary, setIsLibrary] = React.useState(false);
|
||||||
|
|
||||||
const onCloseModal = () => {
|
const onCloseModal = () => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
props.onClose && props.onClose();
|
props.onClose && props.onClose();
|
||||||
}
|
};
|
||||||
|
|
||||||
const onClick = (isLibrary: boolean) => {
|
const onClick = (isLibrary: boolean) => {
|
||||||
if (!isLibrary) {
|
if (!isLibrary) {
|
||||||
metricStore.init();
|
metricStore.init();
|
||||||
}
|
|
||||||
setIsLibrary(isLibrary);
|
|
||||||
setOpen(true);
|
|
||||||
}
|
}
|
||||||
return (
|
setIsLibrary(isLibrary);
|
||||||
<>
|
setOpen(true);
|
||||||
<Modal
|
};
|
||||||
title="Add a card to dashboard"
|
|
||||||
open={props.open}
|
const originStr = window.env.ORIGIN || window.location.origin;
|
||||||
footer={null}
|
const testingKey = localStorage.getItem('__mauricio_testing_access') === 'true';
|
||||||
onCancel={props.onClose}
|
|
||||||
className='addCard'
|
const isSaas = testingKey && /app\.openreplay\.com/.test(originStr);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
title="Add a card to dashboard"
|
||||||
|
open={props.open}
|
||||||
|
footer={null}
|
||||||
|
onCancel={props.onClose}
|
||||||
|
className="addCard"
|
||||||
|
width={isSaas ? 900 : undefined}
|
||||||
|
>
|
||||||
|
{isSaas ? (
|
||||||
|
<>
|
||||||
|
<Row gutter={16} justify="center" className="py-2">
|
||||||
|
<AiQuery />
|
||||||
|
</Row>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'flex items-center justify-center w-full text-disabled-text'
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Row gutter={16} justify="center" className='py-5'>
|
or
|
||||||
<Col span={12}>
|
</div>
|
||||||
<div className="flex flex-col items-center justify-center hover:bg-indigo-50 border rounded-lg shadow-sm cursor-pointer gap-3" style={{height: '80px'}} onClick={() => onClick(true)}>
|
</>
|
||||||
<GalleryVertical style={{fontSize: '24px', color: '#394EFF'}}/>
|
) : null}
|
||||||
<Typography.Text strong>Add from library</Typography.Text>
|
<Row gutter={16} justify="center" className="py-5">
|
||||||
{/*<p>Select from 12 available</p>*/}
|
<Col span={12}>
|
||||||
</div>
|
<div
|
||||||
|
className="flex flex-col items-center justify-center hover:bg-indigo-50 border rounded-lg shadow-sm cursor-pointer gap-3"
|
||||||
</Col>
|
style={{ height: '80px' }}
|
||||||
<Col span={12}>
|
onClick={() => onClick(true)}
|
||||||
<div className="flex flex-col items-center justify-center hover:bg-indigo-50 border rounded-lg shadow-sm cursor-pointer gap-3" style={{height: '80px'}} onClick={() => onClick(false)}>
|
>
|
||||||
<Plus style={{fontSize: '24px', color: '#394EFF'}}/>
|
<GalleryVertical style={{ fontSize: '24px', color: '#394EFF' }} />
|
||||||
<Typography.Text strong>Create New</Typography.Text>
|
<Typography.Text strong>Add from library</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
<Col span={12}>
|
||||||
</Modal>
|
<div
|
||||||
<NewDashboardModal open={open} onClose={onCloseModal} isAddingFromLibrary={isLibrary}/>
|
className="flex flex-col items-center justify-center hover:bg-indigo-50 border rounded-lg shadow-sm cursor-pointer gap-3"
|
||||||
</>
|
style={{ height: '80px' }}
|
||||||
);
|
onClick={() => onClick(false)}
|
||||||
|
>
|
||||||
|
<Plus style={{ fontSize: '24px', color: '#394EFF' }} />
|
||||||
|
<Typography.Text strong>Create New</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Modal>
|
||||||
|
<NewDashboardModal
|
||||||
|
open={open}
|
||||||
|
onClose={onCloseModal}
|
||||||
|
isAddingFromLibrary={isLibrary}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AddCardSelectionModal;
|
export default AddCardSelectionModal;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ const getTitleByType = (type: string) => {
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
// cardType: string,
|
// cardType: string,
|
||||||
onBack: () => void
|
onBack?: () => void
|
||||||
|
onAdded?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function CreateCard(props: Props) {
|
function CreateCard(props: Props) {
|
||||||
|
|
@ -67,7 +68,8 @@ function CreateCard(props: Props) {
|
||||||
|
|
||||||
if (dashboardId) {
|
if (dashboardId) {
|
||||||
await addCardToDashboard(dashboardId, cardId);
|
await addCardToDashboard(dashboardId, cardId);
|
||||||
dashboardStore.fetch(dashboardId);
|
void dashboardStore.fetch(dashboardId);
|
||||||
|
props.onAdded?.();
|
||||||
} else if (isItDashboard) {
|
} else if (isItDashboard) {
|
||||||
const dashboardId = await createNewDashboard();
|
const dashboardId = await createNewDashboard();
|
||||||
await addCardToDashboard(dashboardId, cardId);
|
await addCardToDashboard(dashboardId, cardId);
|
||||||
|
|
@ -81,9 +83,9 @@ function CreateCard(props: Props) {
|
||||||
<div className="flex gap-4 flex-col">
|
<div className="flex gap-4 flex-col">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Space>
|
<Space>
|
||||||
<Button type="text" onClick={props.onBack}>
|
{props.onBack ? <Button type="text" onClick={props.onBack}>
|
||||||
<ArrowLeft size={16}/>
|
<ArrowLeft size={16} />
|
||||||
</Button>
|
</Button> : null}
|
||||||
<div className="text-xl leading-4 font-medium">
|
<div className="text-xl leading-4 font-medium">
|
||||||
{metric.name}
|
{metric.name}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import { Modal } from 'antd';
|
import { Modal } from 'antd';
|
||||||
import SelectCard from './SelectCard';
|
import React, { useEffect } from 'react';
|
||||||
import CreateCard from 'Components/Dashboard/components/DashboardList/NewDashModal/CreateCard';
|
|
||||||
import colors from 'tailwindcss/colors';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import colors from 'tailwindcss/colors';
|
||||||
|
|
||||||
|
import CreateCard from 'Components/Dashboard/components/DashboardList/NewDashModal/CreateCard';
|
||||||
|
|
||||||
|
import SelectCard from './SelectCard';
|
||||||
|
|
||||||
interface NewDashboardModalProps {
|
interface NewDashboardModalProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
|
@ -14,14 +16,15 @@ interface NewDashboardModalProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const NewDashboardModal: React.FC<NewDashboardModalProps> = ({
|
const NewDashboardModal: React.FC<NewDashboardModalProps> = ({
|
||||||
onClose,
|
onClose,
|
||||||
open,
|
open,
|
||||||
isAddingFromLibrary = false,
|
isAddingFromLibrary = false,
|
||||||
isEnterprise = false,
|
isEnterprise = false,
|
||||||
isMobile = false
|
isMobile = false,
|
||||||
}) => {
|
}) => {
|
||||||
const [step, setStep] = React.useState<number>(0);
|
const [step, setStep] = React.useState<number>(0);
|
||||||
const [selectedCategory, setSelectedCategory] = React.useState<string>('product-analytics');
|
const [selectedCategory, setSelectedCategory] =
|
||||||
|
React.useState<string>('product-analytics');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|
@ -40,35 +43,42 @@ const NewDashboardModal: React.FC<NewDashboardModalProps> = ({
|
||||||
closeIcon={false}
|
closeIcon={false}
|
||||||
styles={{
|
styles={{
|
||||||
content: {
|
content: {
|
||||||
backgroundColor: colors.gray[100]
|
backgroundColor: colors.gray[100],
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
centered={true}
|
centered={true}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-4" style={{
|
<div
|
||||||
height: 'calc(100vh - 100px)',
|
className="flex flex-col gap-4"
|
||||||
overflowY: 'auto',
|
style={{
|
||||||
overflowX: 'hidden'
|
height: 'calc(100vh - 100px)',
|
||||||
}}>
|
overflowY: 'auto',
|
||||||
{step === 0 && <SelectCard onClose={onClose}
|
overflowX: 'hidden',
|
||||||
selected={selectedCategory}
|
}}
|
||||||
setSelectedCategory={setSelectedCategory}
|
>
|
||||||
onCard={() => setStep(step + 1)}
|
{step === 0 && (
|
||||||
isLibrary={isAddingFromLibrary}
|
<SelectCard
|
||||||
isMobile={isMobile}
|
onClose={onClose}
|
||||||
isEnterprise={isEnterprise} />}
|
selected={selectedCategory}
|
||||||
|
setSelectedCategory={setSelectedCategory}
|
||||||
|
onCard={() => setStep(step + 1)}
|
||||||
|
isLibrary={isAddingFromLibrary}
|
||||||
|
isMobile={isMobile}
|
||||||
|
isEnterprise={isEnterprise}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{step === 1 && <CreateCard onBack={() => setStep(0)} />}
|
{step === 1 && <CreateCard onBack={() => setStep(0)} />}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state: any) => ({
|
const mapStateToProps = (state: any) => ({
|
||||||
isMobile: state.getIn(['site', 'instance', 'platform']) === 'ios',
|
isMobile: state.getIn(['site', 'instance', 'platform']) === 'ios',
|
||||||
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee' ||
|
isEnterprise:
|
||||||
state.getIn(['user', 'account', 'edition']) === 'msaas'
|
state.getIn(['user', 'account', 'edition']) === 'ee' ||
|
||||||
|
state.getIn(['user', 'account', 'edition']) === 'msaas',
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(NewDashboardModal);
|
export default connect(mapStateToProps)(NewDashboardModal);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
import { SendOutlined } from '@ant-design/icons';
|
||||||
|
import { Modal } from 'antd';
|
||||||
|
import Lottie from 'lottie-react';
|
||||||
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import React from 'react';
|
||||||
|
import colors from 'tailwindcss/colors';
|
||||||
|
|
||||||
|
import { gradientBox } from 'App/components/shared/SessionSearchField/AiSessionSearchField';
|
||||||
|
import aiSpinner from 'App/lottie/aiSpinner.json';
|
||||||
|
import { useStore } from 'App/mstore';
|
||||||
|
import { Icon, Input } from 'UI';
|
||||||
|
|
||||||
|
import CreateCard from '../DashboardList/NewDashModal/CreateCard';
|
||||||
|
|
||||||
|
function AiQuery() {
|
||||||
|
const grad = {
|
||||||
|
background: 'linear-gradient(90deg, #F3F4FF 0%, #F2FEFF 100%)',
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<QueryModal />
|
||||||
|
<div className={'rounded p-4 mb-4'} style={grad}>
|
||||||
|
<InputBox />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputBox = observer(({ inModal }: { inModal?: boolean }) => {
|
||||||
|
const { aiFiltersStore, metricStore } = useStore();
|
||||||
|
const metric = metricStore.instance;
|
||||||
|
const fetchResults = () => {
|
||||||
|
aiFiltersStore
|
||||||
|
.getCardFilters(aiFiltersStore.query, undefined)
|
||||||
|
.then((f) => metric.createSeries(f.filters));
|
||||||
|
if (!inModal) {
|
||||||
|
aiFiltersStore.setModalOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={'flex items-center mb-2 gap-2'}>
|
||||||
|
<Icon name={'sparkles'} size={16} />
|
||||||
|
<div className={'font-semibold'}>What would you like to visualize?</div>
|
||||||
|
</div>
|
||||||
|
<div style={gradientBox}>
|
||||||
|
<Input
|
||||||
|
wrapperClassName={'w-full pr-2'}
|
||||||
|
value={aiFiltersStore.query}
|
||||||
|
style={{
|
||||||
|
minWidth: inModal ? '600px' : '840px',
|
||||||
|
height: 34,
|
||||||
|
borderRadius: 32,
|
||||||
|
}}
|
||||||
|
onChange={({ target }: any) => aiFiltersStore.setQuery(target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' && aiFiltersStore.query.trim().length > 2) {
|
||||||
|
fetchResults();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder={'E.g., Track all the errors in checkout flow.'}
|
||||||
|
className="ml-2 px-2 pe-9 text-lg placeholder-lg !border-0 rounded-e-full nofocus"
|
||||||
|
leadingButton={
|
||||||
|
aiFiltersStore.query !== '' ? (
|
||||||
|
<div
|
||||||
|
className={'h-full flex items-center cursor-pointer'}
|
||||||
|
onClick={fetchResults}
|
||||||
|
>
|
||||||
|
<div className={'px-2 py-1 hover:bg-active-blue rounded mr-2'}>
|
||||||
|
<SendOutlined />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const QueryModal = observer(() => {
|
||||||
|
const { aiFiltersStore } = useStore();
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
aiFiltersStore.setModalOpen(false);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={aiFiltersStore.modalOpen}
|
||||||
|
onCancel={onClose}
|
||||||
|
width={900}
|
||||||
|
destroyOnClose={true}
|
||||||
|
footer={null}
|
||||||
|
closeIcon={false}
|
||||||
|
styles={{
|
||||||
|
content: {
|
||||||
|
backgroundColor: colors.gray[100],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
centered={true}
|
||||||
|
>
|
||||||
|
<div className={'flex flex-col gap-2'}>
|
||||||
|
<InputBox inModal />
|
||||||
|
{aiFiltersStore.isLoading ? (
|
||||||
|
<Loader />
|
||||||
|
) : (
|
||||||
|
<CreateCard onAdded={onClose} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function Loader() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'flex items-center justify-center flex-col font-semibold text-xl min-h-80'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div style={{ width: 150, height: 150 }}>
|
||||||
|
<Lottie animationData={aiSpinner} loop={true} />
|
||||||
|
</div>
|
||||||
|
<div>AI is brewing your card, wait a few seconds...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(AiQuery);
|
||||||
|
|
@ -13,6 +13,7 @@ import withPageTitle from 'HOCs/withPageTitle';
|
||||||
import withReport from 'App/components/hocs/withReport';
|
import withReport from 'App/components/hocs/withReport';
|
||||||
import DashboardHeader from '../DashboardHeader';
|
import DashboardHeader from '../DashboardHeader';
|
||||||
import {useHistory} from "react-router";
|
import {useHistory} from "react-router";
|
||||||
|
import AiQuery from "./AiQuery";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
siteId: string;
|
siteId: string;
|
||||||
|
|
@ -91,12 +92,16 @@ function DashboardView(props: Props) {
|
||||||
|
|
||||||
if (!dashboard) return null;
|
if (!dashboard) return null;
|
||||||
|
|
||||||
|
const originStr = window.env.ORIGIN || window.location.origin;
|
||||||
|
const testingKey = localStorage.getItem('__mauricio_testing_access') === 'true';
|
||||||
|
|
||||||
|
const isSaas = testingKey && /app\.openreplay\.com/.test(originStr);
|
||||||
return (
|
return (
|
||||||
<Loader loading={loading}>
|
<Loader loading={loading}>
|
||||||
<div style={{maxWidth: '1360px', margin: 'auto'}}>
|
<div style={{maxWidth: '1360px', margin: 'auto'}}>
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
<DashboardHeader renderReport={props.renderReport} siteId={siteId} dashboardId={dashboardId}/>
|
<DashboardHeader renderReport={props.renderReport} siteId={siteId} dashboardId={dashboardId}/>
|
||||||
|
{isSaas ? <AiQuery /> : null}
|
||||||
<DashboardWidgetGrid
|
<DashboardWidgetGrid
|
||||||
siteId={siteId}
|
siteId={siteId}
|
||||||
dashboardId={dashboardId}
|
dashboardId={dashboardId}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import {
|
||||||
TIMESERIES, TABLE, HEATMAP, FUNNEL, ERRORS, RESOURCE_MONITORING,
|
TIMESERIES, TABLE, HEATMAP, FUNNEL, ERRORS, RESOURCE_MONITORING,
|
||||||
PERFORMANCE, WEB_VITALS, INSIGHTS, USER_PATH, RETENTION
|
PERFORMANCE, WEB_VITALS, INSIGHTS, USER_PATH, RETENTION
|
||||||
} from 'App/constants/card';
|
} from 'App/constants/card';
|
||||||
import {useParams} from 'react-router-dom';
|
|
||||||
import {useHistory} from "react-router";
|
import {useHistory} from "react-router";
|
||||||
|
|
||||||
const tableOptions = metricOf.filter((i) => i.type === 'table');
|
const tableOptions = metricOf.filter((i) => i.type === 'table');
|
||||||
|
|
|
||||||
|
|
@ -313,7 +313,7 @@ export const AskAiSwitchToggle = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const gradientBox = {
|
export const gradientBox = {
|
||||||
border: 'double 1.5px transparent',
|
border: 'double 1.5px transparent',
|
||||||
borderRadius: '100px',
|
borderRadius: '100px',
|
||||||
background:
|
background:
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,72 @@
|
||||||
import React from 'react';
|
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { Icon } from 'UI';
|
import { Icon } from 'UI';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
wrapperClassName?: string;
|
wrapperClassName?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
leadingButton?: React.ReactNode;
|
leadingButton?: React.ReactNode;
|
||||||
type?: string;
|
type?: string;
|
||||||
rows?: number;
|
rows?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
width?: number;
|
width?: number;
|
||||||
[x: string]: any;
|
[x: string]: any;
|
||||||
}
|
}
|
||||||
const Input = React.forwardRef((props: Props, ref: any) => {
|
const Input = React.forwardRef((props: Props, ref: any) => {
|
||||||
const { height = 36, width = 0, className = '', leadingButton = '', wrapperClassName = '', icon = '', type = 'text', rows = 4, ...rest } = props;
|
const {
|
||||||
return (
|
height = 36,
|
||||||
<div className={cn({ relative: icon || leadingButton }, wrapperClassName)}>
|
width = 0,
|
||||||
{icon && <Icon name={icon} className="absolute top-0 bottom-0 my-auto ml-4" size="14" />}
|
className = '',
|
||||||
{type === 'textarea' ? (
|
leadingButton = '',
|
||||||
<textarea
|
wrapperClassName = '',
|
||||||
ref={ref}
|
icon = '',
|
||||||
rows={rows}
|
type = 'text',
|
||||||
style={{ resize: 'none' }}
|
rows = 4,
|
||||||
maxLength={500}
|
...rest
|
||||||
className={cn('p-2 border border-gray-light bg-white w-full rounded-lg', className, { 'pl-10': icon })}
|
} = props;
|
||||||
{...rest}
|
return (
|
||||||
/>
|
<div className={cn({ relative: icon || leadingButton }, wrapperClassName)}>
|
||||||
) : (
|
{icon && (
|
||||||
<input
|
<Icon
|
||||||
ref={ref}
|
name={icon}
|
||||||
type={type}
|
className="absolute top-0 bottom-0 my-auto ml-4"
|
||||||
style={{ height: `${height}px`, width: width? `${width}px` : '' }}
|
size="14"
|
||||||
className={cn('p-2 border border-gray-light bg-white w-full rounded-lg', className, { 'pl-10': icon })}
|
/>
|
||||||
{...rest}
|
)}
|
||||||
/>
|
{type === 'textarea' ? (
|
||||||
)}
|
<textarea
|
||||||
|
ref={ref}
|
||||||
|
rows={rows}
|
||||||
|
style={{ resize: 'none' }}
|
||||||
|
maxLength={500}
|
||||||
|
className={cn(
|
||||||
|
'p-2 border border-gray-light bg-white w-full rounded-lg',
|
||||||
|
className,
|
||||||
|
{ 'pl-10': icon }
|
||||||
|
)}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<input
|
||||||
|
ref={ref}
|
||||||
|
type={type}
|
||||||
|
style={{ height: `${height}px`, width: width ? `${width}px` : '' }}
|
||||||
|
className={cn(
|
||||||
|
'p-2 border border-gray-light bg-white w-full rounded-lg',
|
||||||
|
className,
|
||||||
|
{ 'pl-10': icon }
|
||||||
|
)}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{leadingButton && <div className="absolute top-0 bottom-0 right-0">{leadingButton}</div>}
|
{leadingButton && (
|
||||||
</div>
|
<div className="absolute top-0 bottom-0 right-0">{leadingButton}</div>
|
||||||
);
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Input;
|
export default Input;
|
||||||
|
|
|
||||||
1
frontend/app/lottie/aiSpinner.json
Normal file
1
frontend/app/lottie/aiSpinner.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -10,11 +10,21 @@ export default class AiFiltersStore {
|
||||||
cardFilters: Record<string, any> = { filters: [] };
|
cardFilters: Record<string, any> = { filters: [] };
|
||||||
filtersSetKey = 0;
|
filtersSetKey = 0;
|
||||||
isLoading: boolean = false;
|
isLoading: boolean = false;
|
||||||
|
query: string = '';
|
||||||
|
modalOpen: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setQuery = (query: string): void => {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
setModalOpen = (isOpen: boolean): void => {
|
||||||
|
this.modalOpen = isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
setFilters = (filters: Record<string, any>): void => {
|
setFilters = (filters: Record<string, any>): void => {
|
||||||
this.filters = filters;
|
this.filters = filters;
|
||||||
this.filtersSetKey += 1;
|
this.filtersSetKey += 1;
|
||||||
|
|
@ -35,8 +45,12 @@ export default class AiFiltersStore {
|
||||||
console.log(r)
|
console.log(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
getCardFilters = async (query: string, chartType: string): Promise<any> => {
|
setLoading = (loading: boolean): void => {
|
||||||
this.isLoading = true;
|
this.isLoading = loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCardFilters = async (query: string, chartType?: string): Promise<any> => {
|
||||||
|
this.setLoading(true)
|
||||||
try {
|
try {
|
||||||
const r = await aiService.getCardFilters(query, chartType);
|
const r = await aiService.getCardFilters(query, chartType);
|
||||||
const filterObj = Filter({
|
const filterObj = Filter({
|
||||||
|
|
@ -57,6 +71,7 @@ export default class AiFiltersStore {
|
||||||
? { ...filtersMap[matchingFilter], ...f }
|
? { ...filtersMap[matchingFilter], ...f }
|
||||||
: { ...f, value: f.value ?? [] };
|
: { ...f, value: f.value ?? [] };
|
||||||
return {
|
return {
|
||||||
|
type: filter.key,
|
||||||
...filter,
|
...filter,
|
||||||
value: filter.value
|
value: filter.value
|
||||||
? filter.value.map((i: string) => parseInt(i, 10) * 60 * 1000)
|
? filter.value.map((i: string) => parseInt(i, 10) * 60 * 1000)
|
||||||
|
|
@ -76,7 +91,7 @@ export default class AiFiltersStore {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.trace(e);
|
console.trace(e);
|
||||||
} finally {
|
} finally {
|
||||||
this.isLoading = false;
|
this.setLoading(false)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ export default class FilterItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
fromData(data: any) {
|
fromData(data: any) {
|
||||||
|
Object.assign(this, data)
|
||||||
this.type = data.type
|
this.type = data.type
|
||||||
this.key = data.key
|
this.key = data.key
|
||||||
this.label = data.label
|
this.label = data.label
|
||||||
|
|
@ -78,7 +79,7 @@ export default class FilterItem {
|
||||||
this.isActive = Boolean(data.isActive)
|
this.isActive = Boolean(data.isActive)
|
||||||
this.completed = data.completed
|
this.completed = data.completed
|
||||||
this.dropped = data.dropped
|
this.dropped = data.dropped
|
||||||
|
this.options = data.options
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export default class AiService extends BaseService {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCardFilters(query: string, chartType: string): Promise<Record<string, any>> {
|
async getCardFilters(query: string, chartType?: string): Promise<Record<string, any>> {
|
||||||
const r = await this.client.post('/intelligent/search-plus', {
|
const r = await this.client.post('/intelligent/search-plus', {
|
||||||
question: query,
|
question: query,
|
||||||
chartType
|
chartType
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@
|
||||||
"jsbi": "^4.1.0",
|
"jsbi": "^4.1.0",
|
||||||
"jshint": "^2.11.1",
|
"jshint": "^2.11.1",
|
||||||
"jspdf": "^2.5.1",
|
"jspdf": "^2.5.1",
|
||||||
|
"lottie-react": "^2.4.0",
|
||||||
"lucide-react": "^0.396.0",
|
"lucide-react": "^0.396.0",
|
||||||
"luxon": "^1.24.1",
|
"luxon": "^1.24.1",
|
||||||
"microdiff": "^1.4.0",
|
"microdiff": "^1.4.0",
|
||||||
|
|
|
||||||
|
|
@ -16655,6 +16655,25 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"lottie-react@npm:^2.4.0":
|
||||||
|
version: 2.4.0
|
||||||
|
resolution: "lottie-react@npm:2.4.0"
|
||||||
|
dependencies:
|
||||||
|
lottie-web: ^5.10.2
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
checksum: 5c0ef3f1832b21232fe6826cc021cd90bb0e3c9d63f1047031ce77a0992092f8712b6f3a6aeeaa0f410d918ca557df160b1c776399f69b498c560273767befe0
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"lottie-web@npm:^5.10.2":
|
||||||
|
version: 5.12.2
|
||||||
|
resolution: "lottie-web@npm:5.12.2"
|
||||||
|
checksum: 0aeaf631b10a76afd025df70c2a1486543530708e07a316946c08e55891dac483ffbaf2bf3648ae0b9c54c733118a0a086fd150aa76f7848606214c67ad72c30
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"loud-rejection@npm:^1.0.0":
|
"loud-rejection@npm:^1.0.0":
|
||||||
version: 1.6.0
|
version: 1.6.0
|
||||||
resolution: "loud-rejection@npm:1.6.0"
|
resolution: "loud-rejection@npm:1.6.0"
|
||||||
|
|
@ -18704,6 +18723,7 @@ __metadata:
|
||||||
jsbi: ^4.1.0
|
jsbi: ^4.1.0
|
||||||
jshint: ^2.11.1
|
jshint: ^2.11.1
|
||||||
jspdf: ^2.5.1
|
jspdf: ^2.5.1
|
||||||
|
lottie-react: ^2.4.0
|
||||||
lucide-react: ^0.396.0
|
lucide-react: ^0.396.0
|
||||||
luxon: ^1.24.1
|
luxon: ^1.24.1
|
||||||
microdiff: ^1.4.0
|
microdiff: ^1.4.0
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue