teableio/teable

View on GitHub
apps/nextjs-app/src/features/app/blocks/table/table-header/TableHeader.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import { HelpCircle, History, MoreHorizontal, Settings, UserPlus } from '@teable/icons';
import { RecordHistory } from '@teable/sdk/components/expand-record/RecordHistory';
import { useBase, useBasePermission, useTableId } from '@teable/sdk/hooks';
import {
  Button,
  cn,
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
  Popover,
  PopoverContent,
  PopoverTrigger,
  Sheet,
  SheetContent,
  SheetHeader,
  SheetTrigger,
} from '@teable/ui-lib/shadcn';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { BaseCollaboratorModalTrigger } from '@/features/app/components/collaborator-manage/base/BaseCollaboratorModal';
import { tableConfig } from '@/features/i18n/table.config';
import { ExpandViewList } from '../../view/list/ExpandViewList';
import { ViewList } from '../../view/list/ViewList';

import { AddView } from './AddView';
import { Collaborators } from './Collaborators';
import { TableInfo } from './TableInfo';

const RightList = ({
  className,
  buttonClassName,
}: {
  className?: string;
  buttonClassName?: string;
}) => {
  const router = useRouter();
  const base = useBase();
  const tableId = useTableId();
  const { t } = useTranslation(tableConfig.i18nNamespaces);
  const basePermission = useBasePermission();

  const onRecordClick = (recordId: string) => {
    router.push(
      {
        pathname: router.pathname,
        query: { ...router.query, recordId },
      },
      undefined,
      {
        shallow: true,
      }
    );
  };

  return (
    <div className={cn('flex', className)}>
      <Collaborators className="flex" />
      <div className="flex">
        {basePermission?.['table_record_history|read'] && (
          <Dialog>
            <DialogTrigger asChild>
              <Button
                variant="ghost"
                size="xs"
                className={cn('flex', buttonClassName)}
                title={t('table:table.tableRecordHistory')}
              >
                <History className="size-4" />
              </Button>
            </DialogTrigger>
            <DialogContent className="flex h-[90%] max-w-4xl flex-col gap-0 p-0">
              <DialogHeader className="border-b p-4">
                <DialogTitle>{t('table:table.tableRecordHistory')}</DialogTitle>
              </DialogHeader>
              <RecordHistory onRecordClick={onRecordClick} />
            </DialogContent>
          </Dialog>
        )}

        <Button asChild variant="ghost" size="xs" className={cn('flex', buttonClassName)}>
          <Link
            href={{
              pathname: '/base/[baseId]/[tableId]/design',
              query: { baseId: base.id, tableId },
            }}
            title={t('table:table.design')}
          >
            <Settings className="size-4" />
          </Link>
        </Button>
        <Button asChild variant="ghost" size="xs" className={cn('flex', buttonClassName)}>
          <a href={t('help.mainLink')} title={t('help.title')} target="_blank" rel="noreferrer">
            <HelpCircle className="size-4" />
          </a>
        </Button>
      </div>
      <BaseCollaboratorModalTrigger
        base={{
          name: base.name,
          role: base.role,
          id: base.id,
        }}
      >
        <Button variant="default" size="xs" className="flex">
          <UserPlus className="size-4" />{' '}
          <span className="hidden @md/view-header:inline">{t('space:action.invite')}</span>
        </Button>
      </BaseCollaboratorModalTrigger>
    </div>
  );
};

const RightMenu = ({ className }: { className?: string }) => {
  const router = useRouter();
  const base = useBase();
  const tableId = useTableId();
  const basePermission = useBasePermission();
  const { t } = useTranslation(tableConfig.i18nNamespaces);

  // eslint-disable-next-line sonarjs/no-identical-functions
  const onRecordClick = (recordId: string) => {
    router.push(
      {
        pathname: router.pathname,
        query: { ...router.query, recordId },
      },
      undefined,
      {
        shallow: true,
      }
    );
  };

  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button
          variant={'ghost'}
          size={'xs'}
          className={cn('font-normal shrink-0 truncate', className)}
        >
          <MoreHorizontal className="size-4" />
        </Button>
      </PopoverTrigger>
      <PopoverContent side="bottom" align="start" className="w-32 p-0">
        <div className="flex flex-col">
          <Collaborators className="flex p-2" />
          <BaseCollaboratorModalTrigger
            base={{
              name: base.name,
              role: base.role,
              id: base.id,
            }}
          >
            <Button variant="ghost" size="xs" className="flex justify-start">
              <UserPlus className="size-4" /> {t('space:action.invite')}
            </Button>
          </BaseCollaboratorModalTrigger>
          {basePermission?.['table_record_history|read'] && (
            <Sheet modal={true}>
              <SheetTrigger asChild>
                <Button size="xs" variant="ghost" className="flex justify-start">
                  <History className="size-4" />
                  {t('table:table.tableRecordHistory')}
                </Button>
              </SheetTrigger>
              <SheetContent
                className="h-5/6 overflow-hidden rounded-t-lg p-0"
                side="bottom"
                closeable={false}
              >
                <SheetHeader className="h-16 justify-center border-b text-2xl">
                  {t('table:table.tableRecordHistory')}
                </SheetHeader>
                <RecordHistory onRecordClick={onRecordClick} />
              </SheetContent>
            </Sheet>
          )}
          <Button asChild variant="ghost" size="xs" className="flex justify-start">
            <Link
              href={{
                pathname: '/base/[baseId]/[tableId]/design',
                query: { baseId: base.id, tableId },
              }}
              title={t('table:table.design')}
            >
              <Settings className="size-4" /> {t('table:table.design')}
            </Link>
          </Button>
          <Button asChild variant="ghost" size="xs" className="flex justify-start">
            <a href={t('help.mainLink')} title={t('help.title')} target="_blank" rel="noreferrer">
              <HelpCircle className="size-4" /> {t('help.title')}
            </a>
          </Button>
        </div>
      </PopoverContent>
    </Popover>
  );
};

export const TableHeader: React.FC = () => {
  return (
    <div className="flex h-[42px] shrink-0 flex-row items-center gap-2 px-4 @container/view-header">
      <TableInfo className="shrink-0 grow-0" />
      <ExpandViewList />
      <div className="flex h-full items-center gap-2 overflow-x-auto">
        <ViewList />
      </div>
      <AddView />
      <div className="grow basis-0"></div>
      <RightList className="hidden gap-2 @md/view-header:flex" />
      <RightMenu className="flex @md/view-header:hidden" />
    </div>
  );
};