update some grid layout stuff

This commit is contained in:
Nate Kelley 2025-03-11 17:01:39 -06:00
parent 8c43ca0403
commit f7b8d87c01
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
22 changed files with 191 additions and 120 deletions

2
web/package-lock.json generated
View File

@ -62,7 +62,7 @@
"prettier-plugin-tailwindcss": "^0.6.11",
"react": "^18",
"react-color": "^2.19.3",
"react-data-grid": "^7.0.0-beta.48",
"react-data-grid": "^7.0.0-beta.47",
"react-day-picker": "^9.6.1",
"react-dom": "^18",
"react-hotkeys-hook": "^4.6.1",

View File

@ -70,7 +70,7 @@
"prettier-plugin-tailwindcss": "^0.6.11",
"react": "^18",
"react-color": "^2.19.3",
"react-data-grid": "^7.0.0-beta.48",
"react-data-grid": "^7.0.0-beta.47",
"react-day-picker": "^9.6.1",
"react-dom": "^18",
"react-hotkeys-hook": "^4.6.1",

View File

@ -14,24 +14,34 @@ import type { GetMetricParams, ListMetricsParams } from './interfaces';
import { upgradeMetricToIMetric } from '@/lib/chat';
import { queryKeys } from '@/api/query_keys';
import { useMemo } from 'react';
import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider';
export const useGetMetric = (params: GetMetricParams) => {
const queryClient = useQueryClient();
const setAssetPasswordError = useBusterAssetsContextSelector(
(state) => state.setAssetPasswordError
);
const queryFn = useMemoizedFn(async () => {
const result = await getMetric(params);
return upgradeMetricToIMetric(result, null);
const oldMetric = queryClient.getQueryData(queryKeys.metricsGetMetric(params.id).queryKey);
return upgradeMetricToIMetric(result, oldMetric || null);
});
return useQuery({
...queryKeys.useMetricsGetMetric(params.id),
queryFn,
enabled: false //this is handle via a socket query? maybe it should not be?
...queryKeys.metricsGetMetric(params.id),
throwOnError: (error, query) => {
setAssetPasswordError(params.id, error.message || 'An error occurred');
return false;
},
queryFn
});
};
export const prefetchGetMetric = async (params: GetMetricParams, queryClientProp?: QueryClient) => {
const queryClient = queryClientProp || new QueryClient();
await queryClient.prefetchQuery({
...queryKeys.useMetricsGetMetric(params.id),
...queryKeys.metricsGetMetric(params.id),
queryFn: async () => {
const result = await getMetric_server(params);
return upgradeMetricToIMetric(result, null);

View File

@ -9,18 +9,22 @@ import type {
export const getMetric = async ({ id, password }: GetMetricParams) => {
return mainApi
.get<BusterMetric>(`/metrics/get`, {
.get<BusterMetric>(`/metrics/${id}`, {
params: { id, ...(password && { password }) }
})
.then((res) => res.data);
};
export const getMetric_server = async ({ id, password }: GetMetricParams) => {
return await serverFetch<BusterMetric>(`/metrics/get`, {
params: { id, ...(password && { password }) }
return await serverFetch<BusterMetric>(`/metrics/${id}`, {
params: { ...(password && { password }) }
});
};
export const getMetricData = async ({ id }: { id: string }) => {
return mainApi.get<BusterMetricData>(`/metrics/${id}/data`).then((res) => res.data);
};
export const listMetrics = async (params: ListMetricsParams) => {
return mainApi.get<BusterMetricListItem[]>('/metrics/list', { params }).then((res) => res.data);
};
@ -29,10 +33,6 @@ export const listMetrics_server = async (params: ListMetricsParams) => {
return await serverFetch<BusterMetricListItem[]>('/metrics/list', { params });
};
export const getMetricData = async ({ id }: { id: string }) => {
return mainApi.get<BusterMetricData>(`/metrics/${id}/data`).then((res) => res.data);
};
export const updateMetric = async (params: UpdateMetricParams) => {
return mainApi
.put<BusterMetric>(`/metrics/update/${params.id}`, { params })

View File

@ -36,7 +36,7 @@ const chatsBlackBoxMessages = (messageId: string) =>
queryOptions<string | null>({
queryKey: ['chats', 'messages', messageId, 'black-box'] as const,
staleTime: Infinity,
enabled: false,
enabled: false, //this is local
queryFn: () => Promise.resolve(null)
});

View File

@ -2,32 +2,16 @@
import { queryOptions } from '@tanstack/react-query';
import type {
BusterMetricData,
BusterMetricListItem,
IBusterMetric,
IBusterMetricData
} from '@/api/asset_interfaces/metric';
import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider';
import { ListMetricsParams } from '../buster_rest/metrics';
export const metricsGetMetric = (metricId: string) => {
return queryOptions<IBusterMetric>({
queryKey: ['metrics', 'get', metricId] as const,
staleTime: 30 * 60 * 1000,
enabled: false
});
};
export const useMetricsGetMetric = (metricId: string) => {
const setAssetPasswordError = useBusterAssetsContextSelector(
(state) => state.setAssetPasswordError
);
return queryOptions<IBusterMetric>({
...metricsGetMetric(metricId),
throwOnError: (error, query) => {
setAssetPasswordError(metricId, error.message || 'An error occurred');
return false;
}
staleTime: 30 * 60 * 1000
});
};
@ -45,7 +29,6 @@ export const metricsGetData = (id: string) =>
export const metricsQueryKeys = {
metricsGetMetric,
useMetricsGetMetric,
metricsGetList,
metricsGetData
};

View File

@ -8,9 +8,6 @@ export default async function Page(props: {
const { chatId, metricId } = params;
console.log('chatId', chatId);
console.log('metricId', metricId);
return (
<AppAssetCheckLayout metricId={metricId} type="metric">
<MetricController metricId={metricId} />

View File

@ -13,10 +13,11 @@ export default function Page(params: { params: { chatId: string } }) {
}
return (
<StatusCard
className="text-red-500"
title="Error"
message="If you are seeing this, tell Nate and screenshot this whole page including the URL and logs..."
/>
<div className="p-5">
<StatusCard
title="Error"
message="If you are seeing this, tell Nate and screenshot this whole page including the URL and logs..."
/>
</div>
);
}

View File

@ -38,7 +38,7 @@ export const NewUserController = () => {
});
onChangePage({
route: BusterRoutes.APP_METRIC
route: BusterRoutes.APP_HOME
});
} catch (error) {
//
@ -81,7 +81,6 @@ export const NewUserController = () => {
placeholder="What is your full name"
className="w-full"
value={name || ''}
defaultValue={user?.name || ''}
name="name"
onChange={(e) => setName(e.target.value)}
/>

View File

@ -38,6 +38,7 @@ export const SidebarUserFooterComponent: React.FC<{
email: string;
signOut: () => Promise<{ error: string }>;
}> = React.memo(({ name, avatarUrl, email, signOut }) => {
if (!name || !email) return null;
return (
<SidebarUserDropdown signOut={signOut}>
<div className="flex w-full">

View File

@ -0,0 +1,112 @@
import type { Meta, StoryObj } from '@storybook/react';
import { AppDataGrid } from './AppDataGrid';
const meta: Meta<typeof AppDataGrid> = {
title: 'UI/table/AppDataGrid',
component: AppDataGrid,
parameters: {
layout: 'centered'
},
tags: ['autodocs']
};
export default meta;
type Story = StoryObj<typeof AppDataGrid>;
const sampleData = [
{ id: 1, name: 'John Doe', age: 30, email: 'john@example.com', joinDate: new Date('2023-01-15') },
{
id: 2,
name: 'Jane Smith',
age: 25,
email: 'jane@example.com',
joinDate: new Date('2023-02-20')
},
{
id: 3,
name: 'Bob Johnson',
age: 35,
email: 'bob@example.com',
joinDate: new Date('2023-03-10')
},
{
id: 4,
name: 'Alice Brown',
age: 28,
email: 'alice@example.com',
joinDate: new Date('2023-04-05')
}
];
export const Default: Story = {
args: {
rows: sampleData,
animate: true,
resizable: true,
draggable: true,
sortable: true
}
};
export const NonResizable: Story = {
args: {
rows: sampleData,
resizable: false,
draggable: true,
sortable: true
}
};
export const NonDraggable: Story = {
args: {
rows: sampleData,
resizable: true,
draggable: false,
sortable: true
}
};
export const NonSortable: Story = {
args: {
rows: sampleData,
resizable: true,
draggable: true,
sortable: false
}
};
export const CustomColumnOrder: Story = {
args: {
rows: sampleData,
columnOrder: ['name', 'email', 'age', 'id', 'joinDate'],
resizable: true,
draggable: true,
sortable: true
}
};
export const CustomFormatting: Story = {
args: {
rows: sampleData,
headerFormat: (value, columnName) => columnName.toUpperCase(),
cellFormat: (value, columnName) => {
if (columnName === 'joinDate' && value instanceof Date) {
return value.toLocaleDateString();
}
return String(value);
},
resizable: true,
draggable: true,
sortable: true
}
};
export const WithInitialWidth: Story = {
args: {
rows: sampleData,
initialWidth: 800,
resizable: true,
draggable: true,
sortable: true
}
};

View File

@ -35,7 +35,6 @@ import {
defaultHeaderFormat,
MIN_WIDTH
} from './helpers';
import styles from './AppDataGrid.module.css';
type Row = Record<string, string | number | null | Date>;
@ -339,6 +338,19 @@ export const AppDataGrid: React.FC<AppDataGridProps> = React.memo(
});
});
const columnsX = [
{ key: 'id', name: 'ID' },
{ key: 'title', name: 'Title' }
];
const rowsX = [
{ id: 0, title: 'Example' },
{ id: 1, title: 'Demo' }
];
console.log('columnsX', columnsX);
console.log('rowsX', rowsX);
return (
<React.Fragment key={forceRenderId}>
<ErrorBoundary onError={handleErrorBoundary}>
@ -348,7 +360,9 @@ export const AppDataGrid: React.FC<AppDataGridProps> = React.memo(
style={{
transition: animate ? 'opacity 0.25s' : undefined
}}>
<DataGrid
<DataGrid columns={columnsX} rows={rowsX} />
{/* <DataGrid
className={styles.dataGrid}
columns={reorderedColumns}
rows={sortedRows}
@ -363,7 +377,7 @@ export const AppDataGrid: React.FC<AppDataGridProps> = React.memo(
onColumnsReorder={onColumnsReorder}
defaultColumnOptions={DEFAULT_COLUMN_WIDTH}
direction={'ltr'}
/>
/> */}
<div style={{ width: '100%' }}></div>
</div>
</ErrorBoundary>

View File

@ -20,7 +20,7 @@ export const AppNoPageAccess: React.FC<{
<div className="flex h-[85vh] w-full flex-col items-center justify-center space-y-6">
<BusterLogo className="h-16 w-16" />
<div className="max-w-[340px] text-center">
<div className="max-w-[440px] text-center">
<Title
as="h2"
className="text-center">{`It looks like you dont have access to this file...`}</Title>

View File

@ -63,7 +63,7 @@ const AppPasswordInputComponent: React.FC<{
style={{
marginTop: '25vh'
}}>
<div className="flex max-w-[340px] flex-col items-center space-y-6">
<div className="flex max-w-[440px] flex-col items-center space-y-6">
<BusterLogo className="h-16 w-16" />
<div className="text-center">

View File

@ -53,7 +53,7 @@ export const MetricTitle: React.FC<{
<Title
{...titleConfig}
as="h4"
className="text-md! max-w-[calc(100%_-_22px)]"
className="text-md! max-w-[calc(100%_-_22px)] whitespace-nowrap"
style={{ fontSize: '14px' }}>
{`${title}`}
</Title>
@ -96,11 +96,7 @@ const ThreeDotPlaceholder: React.FC<{
}> = React.memo(({ className }) => {
return (
<div className={`relative h-[24px] w-[24px] ${className}`}>
{/* <Button
className="absolute top-[-2px] hidden"
type="text"
icon={<AppMaterialIcons icon="more_vert" />}
/> */}
<Button variant="link" prefix={<DotsVertical />} />
</div>
);
});

View File

@ -19,7 +19,7 @@ export const MetricViewChartHeader: React.FC<{
<EditableTitle level={4} className="mb-0" inputClassName="text-md!" onChange={onSetTitle}>
{title}
</EditableTitle>
<div className="flex items-center space-x-1">
<div className="flex items-center space-x-1 whitespace-nowrap">
{!!timeFrame && (
<>
<Text size={'sm'} variant="secondary">

View File

@ -1,7 +1,7 @@
'use client';
import { createContext, useContextSelector } from 'use-context-selector';
import React, { PropsWithChildren, useTransition } from 'react';
import React, { PropsWithChildren } from 'react';
import { useMemoizedFn } from '@/hooks';
import type { AppSplitterRef } from '@/components/ui/layouts';
import { DEFAULT_CHAT_OPTION_SIDEBAR_SIZE } from './config';
@ -38,9 +38,7 @@ export const useChatLayout = ({ appSplitterRef }: UseChatSplitterProps) => {
renderViewLayoutKey
} = useSelectedFileAndLayout({ animateOpenSplitter });
const { collapseDirection, isCollapseOpen, onCollapseFileClick } = useLayoutCollapse({
selectedFile,
selectedLayout,
const { onCollapseFileClick } = useLayoutCollapse({
animateOpenSplitter
});
@ -56,8 +54,6 @@ export const useChatLayout = ({ appSplitterRef }: UseChatSplitterProps) => {
renderViewLayoutKey,
selectedFileType: selectedFile?.type,
selectedFile,
collapseDirection,
isCollapseOpen,
onCollapseFileClick,
onSetSelectedFile,
animateOpenSplitter

View File

@ -1,4 +1,4 @@
import { DoubleChevronRight, DoubleChevronLeft } from '@/components/ui/icons';
import { DoubleChevronRight } from '@/components/ui/icons';
import { Button } from '@/components/ui/buttons';
import React, { useMemo } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
@ -6,30 +6,21 @@ import { useMemoizedFn } from '@/hooks';
export const CollapseFileButton: React.FC<{
showCollapseButton: boolean;
isOpen: boolean;
collapseDirection: 'left' | 'right';
onCollapseFileClick: (value?: boolean) => void;
}> = React.memo(({ showCollapseButton, isOpen, collapseDirection, onCollapseFileClick }) => {
const icon = useMemo(() => {
if (collapseDirection === 'left') {
return isOpen ? <DoubleChevronLeft /> : <DoubleChevronRight />;
}
return isOpen ? <DoubleChevronRight /> : <DoubleChevronLeft />;
}, [isOpen, collapseDirection]);
}> = React.memo(({ showCollapseButton, onCollapseFileClick }) => {
const icon = <DoubleChevronRight />;
const onClick = useMemoizedFn(() => {
onCollapseFileClick();
});
const animation = useMemo(() => {
const baseAnimation = {
return {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 }
};
return baseAnimation;
}, [collapseDirection, isOpen]);
}, []);
return (
<AnimatePresence mode="wait" initial={false}>

View File

@ -2,7 +2,7 @@
import React from 'react';
import { CollapseFileButton } from './CollapseFileButton';
import type { FileType } from '@/api/asset_interfaces';
import type { FileType } from '@/api/asset_interfaces/chat';
import { FileContainerSegmentProps, FileContainerButtonsProps } from './interfaces';
import { DashboardContainerHeaderButtons } from './DashboardContainerHeaderButtons';
import { DashboardContainerHeaderSegment } from './DashboardContainerHeaderSegment';
@ -15,8 +15,6 @@ export const FileContainerHeader: React.FC = React.memo(() => {
const selectedFileType = useChatLayoutContextSelector((x) => x.selectedFileType);
const selectedFileView = useChatLayoutContextSelector((x) => x.selectedFileView);
const onCollapseFileClick = useChatLayoutContextSelector((state) => state.onCollapseFileClick);
const collapseDirection = useChatLayoutContextSelector((state) => state.collapseDirection);
const isCollapseOpen = useChatLayoutContextSelector((state) => state.isCollapseOpen);
const renderViewLayoutKey = useChatLayoutContextSelector((state) => state.renderViewLayoutKey);
const showCollapseButton = renderViewLayoutKey !== 'file';
@ -33,7 +31,7 @@ export const FileContainerHeader: React.FC = React.memo(() => {
() =>
selectedFileType && SelectedFileButtonsRecord[selectedFileType]
? SelectedFileButtonsRecord[selectedFileType]
: () => <>no buttons</>,
: () => null,
[selectedFileType]
);
@ -41,9 +39,7 @@ export const FileContainerHeader: React.FC = React.memo(() => {
<>
<div className="flex items-center gap-1.5">
<CollapseFileButton
collapseDirection={collapseDirection}
showCollapseButton={showCollapseButton}
isOpen={isCollapseOpen}
onCollapseFileClick={onCollapseFileClick}
/>
{selectedFileView && <SelectedFileSegment selectedFileView={selectedFileView} />}

View File

@ -11,6 +11,7 @@ import { useMetricIndividual } from '@/context/Metrics';
import { SaveMetricToCollectionButton } from '../../../../components/features/buttons/SaveMetricToCollectionButton';
import { SaveMetricToDashboardButton } from '../../../../components/features/buttons/SaveMetricToDashboardButton';
import { ShareMetricButton } from '../../../../components/features/buttons/ShareMetricButton';
import { Code3, SquareChartPen } from '@/components/ui/icons';
export const MetricContainerHeaderButtons: React.FC<FileContainerButtonsProps> = React.memo(() => {
const renderViewLayoutKey = useChatLayoutContextSelector((x) => x.renderViewLayoutKey);
@ -52,7 +53,7 @@ const EditChartButton = React.memo(() => {
return (
<SelectableButton
tooltipText="Edit chart"
icon="chart_edit"
icon={<SquareChartPen />}
onClick={onClickButton}
selected={isSelectedView}
/>
@ -76,7 +77,7 @@ const EditSQLButton = React.memo(() => {
return (
<SelectableButton
tooltipText="Edit SQL"
icon="code_blocks"
icon={<Code3 />}
onClick={onClickButton}
selected={isSelectedView}
/>

View File

@ -2,7 +2,6 @@ import React from 'react';
import { Button } from '@/components/ui/buttons';
import { AppTooltip } from '@/components/ui/tooltip';
import {} from '@/components/ui/icons';
export const SelectableButton = React.memo<{
tooltipText: string;

View File

@ -6,50 +6,25 @@ import { SelectedFileParams } from './useSelectedFileAndLayout';
import { ChatLayoutView } from '../interfaces';
export const useLayoutCollapse = ({
selectedFile,
animateOpenSplitter,
selectedLayout
animateOpenSplitter
}: {
selectedFile: SelectedFileParams['selectedFile'];
selectedLayout: ChatLayoutView;
animateOpenSplitter: (side: 'left' | 'right' | 'both') => void;
}) => {
const isReasoningFile = selectedFile?.type === 'reasoning';
const [isCollapseOpen, setIsCollapseOpen] = useState(isReasoningFile ? true : false);
const collapseDirection: 'left' | 'right' = useMemo(() => {
if (selectedFile?.type === 'reasoning') return 'right';
return selectedLayout === 'file' ? 'left' : 'right';
}, [selectedLayout, selectedFile?.type]);
const onCollapseFileClick = useMemoizedFn((close?: boolean) => {
const isCloseAction = close ?? isCollapseOpen;
const isFileLayout = selectedLayout === 'file';
// if (selectedFile && selectedFile.type === 'reasoning') {
// animateOpenSplitter(!isCloseAction ? 'both' : 'left');
// } else if (isFileLayout) {
// // For file layout, toggle between 'both' and 'right'
// animateOpenSplitter(!isCloseAction && selectedFile ? 'both' : 'right');
// } else {
// // For other layouts, toggle between 'right' and 'both'
// animateOpenSplitter(isCloseAction ? 'left' : 'both');
// }
setIsCollapseOpen(!isCloseAction);
if (selectedFile && selectedFile.type === 'reasoning') {
animateOpenSplitter(!isCloseAction ? 'both' : 'left');
} else if (isFileLayout) {
// For file layout, toggle between 'both' and 'right'
animateOpenSplitter(!isCloseAction && selectedFile ? 'both' : 'right');
} else {
// For other layouts, toggle between 'right' and 'both'
animateOpenSplitter(isCloseAction ? 'left' : 'both');
}
animateOpenSplitter('left');
});
useEffect(() => {
if (isReasoningFile && !isCollapseOpen) {
setIsCollapseOpen(true);
}
}, [isReasoningFile]);
return {
collapseDirection,
isCollapseOpen,
onCollapseFileClick
};
};