mirror of https://github.com/buster-so/buster.git
make page layout better
This commit is contained in:
parent
92fe355f47
commit
b883ffc31c
|
@ -10,17 +10,16 @@ import { BusterListColumn, BusterListRow } from '@/components/ui/list';
|
||||||
import { ChatSelectedOptionPopup } from './ChatItemsSelectedPopup';
|
import { ChatSelectedOptionPopup } from './ChatItemsSelectedPopup';
|
||||||
import { BusterList, ListEmptyStateWithButton } from '@/components/ui/list';
|
import { BusterList, ListEmptyStateWithButton } from '@/components/ui/list';
|
||||||
import { useCreateListByDate } from '@/components/ui/list/useCreateListByDate';
|
import { useCreateListByDate } from '@/components/ui/list/useCreateListByDate';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export const ChatItemsContainer: React.FC<{
|
export const ChatItemsContainer: React.FC<{
|
||||||
chats: BusterChatListItem[];
|
chats: BusterChatListItem[];
|
||||||
className?: string;
|
className?: string;
|
||||||
openNewMetricModal: () => void;
|
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}> = ({ chats = [], className = '', loading, openNewMetricModal }) => {
|
}> = ({ chats = [], className = '', loading }) => {
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
|
||||||
const renderedDates = useRef<Record<string, string>>({});
|
const renderedDates = useRef<Record<string, string>>({});
|
||||||
const renderedOwners = useRef<Record<string, React.ReactNode>>({});
|
const renderedOwners = useRef<Record<string, React.ReactNode>>({});
|
||||||
const tableContainerRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const onSelectChange = useMemoizedFn((selectedRowKeys: string[]) => {
|
const onSelectChange = useMemoizedFn((selectedRowKeys: string[]) => {
|
||||||
setSelectedRowKeys(selectedRowKeys);
|
setSelectedRowKeys(selectedRowKeys);
|
||||||
|
@ -106,15 +105,13 @@ export const ChatItemsContainer: React.FC<{
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
ref={tableContainerRef}
|
|
||||||
className={`${className} relative flex h-full flex-col items-center`}>
|
|
||||||
<BusterList
|
<BusterList
|
||||||
rows={chatsByDate}
|
rows={chatsByDate}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
onSelectChange={onSelectChange}
|
onSelectChange={onSelectChange}
|
||||||
selectedRowKeys={selectedRowKeys}
|
selectedRowKeys={selectedRowKeys}
|
||||||
emptyState={<EmptyState loading={loading} openNewMetricModal={openNewMetricModal} />}
|
emptyState={<EmptyState loading={loading} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ChatSelectedOptionPopup
|
<ChatSelectedOptionPopup
|
||||||
|
@ -122,31 +119,27 @@ export const ChatItemsContainer: React.FC<{
|
||||||
onSelectChange={onSelectChange}
|
onSelectChange={onSelectChange}
|
||||||
hasSelected={hasSelected}
|
hasSelected={hasSelected}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmptyState: React.FC<{
|
const EmptyState: React.FC<{
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
openNewMetricModal: () => void;
|
}> = React.memo(({ loading }) => {
|
||||||
}> = React.memo(({ loading, openNewMetricModal }) => {
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ChatsEmptyState openNewMetricModal={openNewMetricModal} />;
|
return <ChatsEmptyState />;
|
||||||
});
|
});
|
||||||
EmptyState.displayName = 'EmptyState';
|
EmptyState.displayName = 'EmptyState';
|
||||||
|
|
||||||
const ChatsEmptyState: React.FC<{
|
const ChatsEmptyState: React.FC<{}> = ({}) => {
|
||||||
openNewMetricModal: () => void;
|
|
||||||
}> = ({ openNewMetricModal }) => {
|
|
||||||
return (
|
return (
|
||||||
<ListEmptyStateWithButton
|
<ListEmptyStateWithButton
|
||||||
title="You don't have any chats yet."
|
title="You don't have any chats yet."
|
||||||
description="You don't have any chats. As soon as you do, they will start to appear here."
|
description="You don't have any chats. As soon as you do, they will start to appear here."
|
||||||
buttonText="New chat"
|
buttonText="New chat"
|
||||||
onClick={openNewMetricModal}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,44 +1,15 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { AppContentPage } from '@/components/ui/layouts/AppContentPage';
|
|
||||||
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
|
|
||||||
import { useMemoizedFn, useMount } from 'ahooks';
|
|
||||||
import { useBusterChatListByFilter } from '@/context/Chats';
|
import { useBusterChatListByFilter } from '@/context/Chats';
|
||||||
import { ChatListHeader } from './ChatListHeader';
|
|
||||||
import { ChatItemsContainer } from './ChatItemsContainer';
|
import { ChatItemsContainer } from './ChatItemsContainer';
|
||||||
|
|
||||||
export const ChatListContainer: React.FC<{
|
export const ChatListContainer: React.FC<{}> = ({}) => {
|
||||||
className?: string;
|
|
||||||
}> = ({ className = '' }) => {
|
|
||||||
const onToggleChatsModal = useAppLayoutContextSelector((s) => s.onToggleChatsModal);
|
|
||||||
const [filters, setFilters] = useState<Parameters<typeof useBusterChatListByFilter>[0]>({
|
const [filters, setFilters] = useState<Parameters<typeof useBusterChatListByFilter>[0]>({
|
||||||
admin_view: false
|
admin_view: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const { list, isFetched } = useBusterChatListByFilter(filters);
|
const { list, isFetched } = useBusterChatListByFilter(filters);
|
||||||
|
|
||||||
const onSetFilters = useMemoizedFn(
|
return <ChatItemsContainer chats={list} loading={!isFetched} />;
|
||||||
(newFilters: Parameters<typeof useBusterChatListByFilter>[0]) => {
|
|
||||||
setFilters(newFilters);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
useMount(async () => {
|
|
||||||
onSetFilters({ admin_view: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`${className} flex h-full flex-col`}>
|
|
||||||
<ChatListHeader />
|
|
||||||
<AppContentPage>
|
|
||||||
<ChatItemsContainer
|
|
||||||
chats={list}
|
|
||||||
loading={!isFetched}
|
|
||||||
openNewMetricModal={onToggleChatsModal}
|
|
||||||
className="flex-col overflow-hidden"
|
|
||||||
/>
|
|
||||||
</AppContentPage>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,17 +1,10 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AppContentHeader } from '@/components/ui/layouts/AppContentHeader';
|
import { Text } from '@/components/ui/typography';
|
||||||
import { Text } from '@/components/ui';
|
|
||||||
|
|
||||||
export const ChatListHeader: React.FC<{}> = ({}) => {
|
export const ChatListHeader: React.FC<{}> = ({}) => {
|
||||||
return (
|
return (
|
||||||
<AppContentHeader>
|
<>
|
||||||
<div className="flex w-full items-center justify-between">
|
<Text>{'Chats'}</Text>
|
||||||
<div className="flex items-center space-x-2">
|
</>
|
||||||
<Text>{'Chats'}</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AppContentHeader>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
|
import { AppPageLayout } from '@/components/ui/layouts/AppPageLayout';
|
||||||
import { ChatListContainer } from './_ChatsListContainer';
|
import { ChatListContainer } from './_ChatsListContainer';
|
||||||
|
import { ChatListHeader } from './_ChatsListContainer/ChatListHeader';
|
||||||
|
|
||||||
export default function ChatsPage() {
|
export default function ChatsPage() {
|
||||||
return <ChatListContainer />;
|
return (
|
||||||
|
<AppPageLayout headerVariant="list" header={<ChatListHeader />}>
|
||||||
|
<ChatListContainer />
|
||||||
|
</AppPageLayout>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,9 @@ import { Tooltip } from '@/components/ui/tooltip/Tooltip';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useUserConfigContextSelector } from '@/context/Users';
|
import { useUserConfigContextSelector } from '@/context/Users';
|
||||||
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
|
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
|
||||||
|
import { SupportModal } from '../modal/SupportModal';
|
||||||
|
import { InvitePeopleModal } from '../modal/InvitePeopleModal';
|
||||||
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
|
||||||
const topItems: ISidebarList = {
|
const topItems: ISidebarList = {
|
||||||
items: [
|
items: [
|
||||||
|
@ -126,7 +129,11 @@ export const SidebarPrimary = React.memo(() => {
|
||||||
}, [isAdmin, favorites, currentRoute]);
|
}, [isAdmin, favorites, currentRoute]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar content={sidebarItems} header={<SidebarPrimaryHeader />} activeItem={currentRoute} />
|
<>
|
||||||
|
<Sidebar content={sidebarItems} header={<SidebarPrimaryHeader />} activeItem={currentRoute} />
|
||||||
|
|
||||||
|
<GlobalModals />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -148,7 +155,7 @@ const SidebarPrimaryHeader = React.memo(() => {
|
||||||
size="tall"
|
size="tall"
|
||||||
rounding={'large'}
|
rounding={'large'}
|
||||||
prefix={
|
prefix={
|
||||||
<div className="translate-x-[-1px] translate-y-[-1px]">
|
<div className="translate-x-[0px] translate-y-[-1px]">
|
||||||
<PencilSquareIcon />
|
<PencilSquareIcon />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -162,6 +169,25 @@ const SidebarPrimaryHeader = React.memo(() => {
|
||||||
|
|
||||||
SidebarPrimaryHeader.displayName = 'SidebarPrimaryHeader';
|
SidebarPrimaryHeader.displayName = 'SidebarPrimaryHeader';
|
||||||
|
|
||||||
|
const GlobalModals = React.memo(() => {
|
||||||
|
const onToggleSupportModal = useAppLayoutContextSelector((s) => s.onToggleSupportModal);
|
||||||
|
const onToggleInviteModal = useAppLayoutContextSelector((s) => s.onToggleInviteModal);
|
||||||
|
const openSupportModal = useAppLayoutContextSelector((s) => s.openSupportModal);
|
||||||
|
const openInviteModal = useAppLayoutContextSelector((s) => s.openInviteModal);
|
||||||
|
const isAnonymousUser = useUserConfigContextSelector((state) => state.isAnonymousUser);
|
||||||
|
|
||||||
|
const onCloseInviteModal = useMemoizedFn(() => onToggleInviteModal(false));
|
||||||
|
const onCloseSupportModal = useMemoizedFn(() => onToggleSupportModal(false));
|
||||||
|
|
||||||
|
if (isAnonymousUser) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<InvitePeopleModal open={openInviteModal} onClose={onCloseInviteModal} />
|
||||||
|
<SupportModal open={openSupportModal} onClose={onCloseSupportModal} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
const favoritesDropdown = (favorites: BusterUserFavorite[]): ISidebarGroup => {
|
const favoritesDropdown = (favorites: BusterUserFavorite[]): ISidebarGroup => {
|
||||||
return {
|
return {
|
||||||
label: 'Favorites',
|
label: 'Favorites',
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React, { PropsWithChildren } from 'react';
|
import React, { PropsWithChildren } from 'react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
|
||||||
|
|
||||||
export const AppContentPage: React.FC<
|
export const AppContentPage: React.FC<
|
||||||
PropsWithChildren<{
|
PropsWithChildren<{
|
||||||
|
@ -11,7 +10,8 @@ export const AppContentPage: React.FC<
|
||||||
return (
|
return (
|
||||||
<main
|
<main
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-background app-content max-h-[100%] overflow-hidden p-0',
|
'app-content-page',
|
||||||
|
'bg-background app-content h-full max-h-[100%] overflow-hidden p-0',
|
||||||
scrollable && 'overflow-y-auto',
|
scrollable && 'overflow-y-auto',
|
||||||
className
|
className
|
||||||
)}>
|
)}>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
import { AppContentHeader } from './AppContentHeader';
|
import { AppContentHeader } from './AppContentHeader';
|
||||||
import { AppContentPage } from './AppContentPage';
|
import { AppContentPage } from './AppContentPage';
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ export const AppPageLayout: React.FC<
|
||||||
className?: string;
|
className?: string;
|
||||||
headerVariant?: 'default' | 'list';
|
headerVariant?: 'default' | 'list';
|
||||||
}>
|
}>
|
||||||
> = ({ children, header, scrollable = true, className = '', headerVariant = 'default' }) => {
|
> = ({ children, header, scrollable = false, className = '', headerVariant = 'default' }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
export * from './AppSplitter';
|
|
||||||
export * from './PreventNavigation';
|
export * from './PreventNavigation';
|
||||||
export * from './AppContentHeader_Old';
|
export * from './AppContentHeader_Old';
|
||||||
export * from './AppContentPage';
|
export * from './AppContentPage';
|
||||||
export * from './ClientRedirect';
|
export * from './ClientRedirect';
|
||||||
|
|
||||||
|
//keepers
|
||||||
|
// export * from './AppPageLayout';
|
||||||
|
// export * from './AppSplitter';
|
||||||
|
// export * from './AppLayout';
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AppMaterialIcons, Text, Title } from '@/components/ui';
|
import { AppMaterialIcons, Text, Title } from '@/components/ui';
|
||||||
import { Button } from 'antd';
|
import { Button } from '../buttons/Button';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
export const ListEmptyStateWithButton: React.FC<{
|
export const ListEmptyStateWithButton: React.FC<{
|
||||||
isAdmin?: boolean;
|
isAdmin?: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
onClick: () => void;
|
onClick?: () => void;
|
||||||
buttonText: string;
|
buttonText: string;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
linkButton?: string;
|
||||||
}> = React.memo(({ isAdmin = true, title, buttonText, description, onClick, loading = false }) => {
|
}> = React.memo(({ isAdmin = true, title, buttonText, description, onClick, loading = false }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col">
|
<div className="flex h-full w-full flex-col">
|
||||||
|
@ -27,8 +29,8 @@ export const ListEmptyStateWithButton: React.FC<{
|
||||||
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<Button
|
<Button
|
||||||
type="default"
|
variant="default"
|
||||||
icon={<AppMaterialIcons icon="add" />}
|
prefix={<AppMaterialIcons icon="add" />}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onClick={onClick}>
|
onClick={onClick}>
|
||||||
{buttonText}
|
{buttonText}
|
||||||
|
|
|
@ -30,7 +30,7 @@ type ParagraphProps = {
|
||||||
} & VariantProps<typeof textColorVariants> &
|
} & VariantProps<typeof textColorVariants> &
|
||||||
VariantProps<typeof paragraphVariants>;
|
VariantProps<typeof paragraphVariants>;
|
||||||
|
|
||||||
const Paragraph: React.FC<ParagraphProps> = ({
|
export const Paragraph: React.FC<ParagraphProps> = ({
|
||||||
onClick,
|
onClick,
|
||||||
variant = 'default',
|
variant = 'default',
|
||||||
size = 'base',
|
size = 'base',
|
||||||
|
|
|
@ -33,7 +33,7 @@ type TextProps = {
|
||||||
} & VariantProps<typeof textVariants> &
|
} & VariantProps<typeof textVariants> &
|
||||||
VariantProps<typeof textColorVariants>;
|
VariantProps<typeof textColorVariants>;
|
||||||
|
|
||||||
const Text: React.FC<TextProps> = ({
|
export const Text: React.FC<TextProps> = ({
|
||||||
variant = 'default',
|
variant = 'default',
|
||||||
size = 'base',
|
size = 'base',
|
||||||
truncate,
|
truncate,
|
||||||
|
@ -53,5 +53,3 @@ const Text: React.FC<TextProps> = ({
|
||||||
</TextNode>
|
</TextNode>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Text;
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ type TitleProps = {
|
||||||
} & VariantProps<typeof titleVariants> &
|
} & VariantProps<typeof titleVariants> &
|
||||||
VariantProps<typeof textColorVariants>;
|
VariantProps<typeof textColorVariants>;
|
||||||
|
|
||||||
const Title: React.FC<TitleProps> = ({
|
export const Title: React.FC<TitleProps> = ({
|
||||||
as = 'h1',
|
as = 'h1',
|
||||||
variant = 'default',
|
variant = 'default',
|
||||||
size,
|
size,
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './Text';
|
||||||
|
export * from './Title';
|
||||||
|
export * from './Paragraph';
|
Loading…
Reference in New Issue