teableio/teable

View on GitHub
apps/nextjs-app/src/features/app/components/field-setting/options/LinkOptions/SelectTable.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import { useQuery } from '@tanstack/react-query';
import { ArrowUpRight, Database, Table2 } from '@teable/icons';
import { getBaseAll } from '@teable/openapi';
import { ReactQueryKeys } from '@teable/sdk/config';
import { AnchorContext, TableProvider } from '@teable/sdk/context';
import { useBaseId, useTableId, useTables } from '@teable/sdk/hooks';
import { Button } from '@teable/ui-lib/shadcn';
import Link from 'next/link';
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
import { Selector } from '@/components/Selector';
import { tableConfig } from '@/features/i18n/table.config';

interface ISelectTableProps {
  baseId?: string;
  tableId?: string;
  onChange?: (baseId?: string, tableId?: string) => void;
}

export const SelectTable = ({ baseId, tableId, onChange }: ISelectTableProps) => {
  const { t } = useTranslation(tableConfig.i18nNamespaces);
  const [enableSelectBase, setEnableSelectBase] = useState(Boolean(baseId));
  const selfTableId = useTableId();
  const selfBaseId = useBaseId();
  const selectedBaseId = baseId || selfBaseId!;

  return (
    <div className="flex flex-col gap-1">
      {enableSelectBase && (
        <>
          <div className="neutral-content label-text flex h-7 items-center justify-between">
            {t('table:field.editor.linkBase')}
            <Button
              size="xs"
              variant="link"
              onClick={() => {
                setEnableSelectBase(false);
                onChange?.(undefined, undefined);
              }}
              className="text-xs text-slate-500 underline"
            >
              {t('common:actions.cancel')}
            </Button>
          </div>
          <BasePicker
            baseId={selectedBaseId}
            onChange={(baseId) => {
              if (baseId === selfBaseId) {
                onChange?.(undefined, undefined);
              } else {
                onChange?.(baseId);
              }
            }}
          />
        </>
      )}
      <AnchorContext.Provider value={{ baseId: selectedBaseId }}>
        <div className="neutral-content label-text flex h-7 items-center justify-between">
          <span className="flex items-center gap-1">
            {t('table:field.editor.linkTable')}
            {tableId && (
              <Link href={`/base/${selectedBaseId}/${tableId}`} target="_blank">
                <ArrowUpRight className="size-4 shrink-0" />
              </Link>
            )}
          </span>
          {!enableSelectBase && (
            <Button
              size="xs"
              variant="link"
              onClick={() => setEnableSelectBase(true)}
              className="text-xs text-slate-500 underline"
            >
              {t('table:field.editor.linkFromExternalBase')}
            </Button>
          )}
        </div>
        <TableProvider>
          <TablePicker
            tableId={tableId}
            selfTableId={selfTableId}
            onChange={(tableId) => onChange?.(baseId!, tableId)}
          />
        </TableProvider>
      </AnchorContext.Provider>
    </div>
  );
};

interface ITablePickerProps {
  tableId: string | undefined;
  readonly?: boolean;
  selfTableId?: string;
  onChange?: (tableId: string) => void;
}

const TablePicker = ({ tableId, selfTableId, readonly, onChange }: ITablePickerProps) => {
  const { t } = useTranslation(tableConfig.i18nNamespaces);
  let tables = useTables() as { id: string; name: string; icon?: string }[];

  if (tableId && !tables.find((table) => table.id === tableId)) {
    tables = tables.concat({
      id: tableId!,
      name: t('table:field.editor.tableNoPermission'),
    });
  }

  return (
    <Selector
      className="w-full"
      readonly={readonly}
      selectedId={tableId}
      onChange={(tableId) => onChange?.(tableId)}
      candidates={tables.map((table) => ({
        id: table.id,
        name: table.name + (selfTableId === table.id ? ` (${t('table:field.editor.self')})` : ''),
        icon: table.icon || <Table2 className="size-4 shrink-0" />,
      }))}
      placeholder={t('table:field.editor.selectTable')}
    />
  );
};

interface IBasePickerProps {
  baseId: string;
  readonly?: boolean;
  onChange?: (baseId: string) => void;
}

const BasePicker = ({ baseId, onChange }: IBasePickerProps) => {
  const { t } = useTranslation(tableConfig.i18nNamespaces);
  let { data: bases } = useQuery({
    queryKey: ReactQueryKeys.baseAll(),
    queryFn: () =>
      getBaseAll().then((data) => data.data) as Promise<
        { id: string; name: string; icon?: string }[]
      >,
  });

  if (baseId && !bases?.find((base) => base.id === baseId)) {
    bases = bases?.concat({
      id: baseId!,
      name: t('table:field.editor.baseNoPermission'),
    });
  }

  return (
    <Selector
      className="w-full"
      selectedId={baseId}
      onChange={(baseId) => onChange?.(baseId)}
      candidates={bases?.map((base) => ({
        id: base.id,
        name: base.name,
        icon: base.icon || <Database className="size-4 shrink-0" />,
      }))}
      placeholder={t('table:field.editor.selectBase')}
    />
  );
};