teableio/teable

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

Summary

Maintainability
A
2 hrs
Test Coverage
import { useQuery } from '@tanstack/react-query';
import type { ILinkFieldOptionsRo } from '@teable/core';
import { Relationship } from '@teable/core';
import { ArrowUpRight } from '@teable/icons';
import { getTablePermission } from '@teable/openapi';
import { ReactQueryKeys } from '@teable/sdk/config';
import { useBaseId, useTableId } from '@teable/sdk/hooks';
import { Button, Label, Switch } from '@teable/ui-lib/shadcn';
import Link from 'next/link';
import { Trans, useTranslation } from 'next-i18next';
import { Fragment, useState } from 'react';
import { tableConfig } from '@/features/i18n/table.config';
import { MoreLinkOptions } from './MoreLinkOptions';
import { SelectTable } from './SelectTable';

export const LinkOptions = (props: {
  options: Partial<ILinkFieldOptionsRo> | undefined;
  fieldId?: string;
  isLookup?: boolean;
  onChange?: (options: Partial<ILinkFieldOptionsRo>) => void;
}) => {
  const { fieldId, options, isLookup, onChange } = props;
  const tableId = useTableId();
  const selfBaseId = useBaseId() as string;
  const { t } = useTranslation(tableConfig.i18nNamespaces);

  const isMoreVisible = Boolean(
    options?.filterByViewId || options?.filter || options?.visibleFieldIds
  );

  const [moreVisible, setMoreVisible] = useState(isMoreVisible);

  const relationship = options?.relationship ?? Relationship.ManyOne;
  const foreignTableId = options?.foreignTableId;
  const isOneWay = options?.isOneWay;
  const baseId = options?.baseId ?? selfBaseId;

  const { data: tablePermission } = useQuery({
    refetchOnWindowFocus: false,
    queryKey: ReactQueryKeys.getTablePermission(baseId, foreignTableId!),
    enabled: !!foreignTableId,
    queryFn: ({ queryKey }) =>
      getTablePermission(queryKey[1], queryKey[2])
        .then((res) => res.data)
        .catch(() => ({ field: { create: false } })),
  });

  const canCreateField = tablePermission?.field.create;

  const translation = {
    [Relationship.OneOne]: t('table:field.editor.oneToOne'),
    [Relationship.OneMany]: t('table:field.editor.oneToMany'),
    [Relationship.ManyOne]: t('table:field.editor.manyToOne'),
    [Relationship.ManyMany]: t('table:field.editor.manyToMany'),
  };

  const onSelect = (key: keyof ILinkFieldOptionsRo, value: unknown) => {
    onChange?.({ foreignTableId, relationship, isOneWay, [key]: value });
  };

  const onRelationshipChange = (leftMulti: boolean, rightMulti: boolean) => {
    if (leftMulti && rightMulti) {
      onSelect('relationship', Relationship.ManyMany);
    }
    if (leftMulti && !rightMulti) {
      onSelect('relationship', Relationship.OneMany);
    }
    if (!leftMulti && rightMulti) {
      onSelect('relationship', Relationship.ManyOne);
    }
    if (!leftMulti && !rightMulti) {
      onSelect('relationship', Relationship.OneOne);
    }
  };

  const isLeftMulti = (relationship: Relationship) => {
    return relationship === Relationship.ManyMany || relationship === Relationship.OneMany;
  };
  const isRightMulti = (relationship: Relationship) => {
    return relationship === Relationship.ManyMany || relationship === Relationship.ManyOne;
  };

  if (isLookup) {
    return <></>;
  }

  return (
    <div className="flex w-full flex-col gap-2">
      <SelectTable
        baseId={options?.baseId}
        tableId={options?.foreignTableId}
        onChange={(baseId, tableId) => {
          onChange?.({
            baseId,
            foreignTableId: tableId,
            relationship,
            isOneWay,
            filterByViewId: null,
            visibleFieldIds: null,
            filter: null,
          });
        }}
      />
      {options?.foreignTableId && (
        <Fragment>
          <div className="flex justify-end">
            <Button
              size="xs"
              variant="link"
              className="text-xs text-slate-500 underline"
              onClick={() => setMoreVisible(!moreVisible)}
            >
              {t('table:field.editor.moreOptions')}
            </Button>
          </div>
          {moreVisible && (
            <MoreLinkOptions
              foreignTableId={options?.foreignTableId}
              fieldId={fieldId}
              filterByViewId={options?.filterByViewId}
              visibleFieldIds={options?.visibleFieldIds}
              filter={options?.filter}
              onChange={(partialOptions: Partial<ILinkFieldOptionsRo>) => {
                onChange?.({ ...options, ...partialOptions });
              }}
            />
          )}
        </Fragment>
      )}
      {foreignTableId && (
        <>
          <hr className="my-2" />
          <div className="flex space-x-2 pt-1">
            <Switch
              id="field-options-one-way-link"
              checked={!isOneWay}
              onCheckedChange={(checked) => {
                onSelect('isOneWay', !checked);
              }}
              disabled={!canCreateField}
            />
            <Label htmlFor="field-options-one-way-link" className="font-normal leading-tight">
              {t('table:field.editor.createSymmetricLink')}
            </Label>
          </div>
          <div className="flex space-x-2 pt-1">
            <Switch
              id="field-options-self-multi"
              checked={isLeftMulti(relationship)}
              onCheckedChange={(checked) => {
                onRelationshipChange(checked, isRightMulti(relationship));
              }}
            />
            <Label htmlFor="field-options-self-multi" className="font-normal leading-tight">
              {t('table:field.editor.allowLinkMultipleRecords')}
            </Label>
          </div>
          <div className="flex space-x-2 pt-1">
            <Switch
              id="field-options-sym-multi"
              checked={isRightMulti(relationship)}
              onCheckedChange={(checked) => {
                onRelationshipChange(isLeftMulti(relationship), checked);
              }}
            />
            <Label htmlFor="field-options-sym-multi" className="font-normal leading-tight">
              {isOneWay
                ? t('table:field.editor.allowLinkToDuplicateRecords')
                : t('table:field.editor.allowSymmetricFieldLinkMultipleRecords')}
            </Label>
          </div>
          <p className="pt-2">
            <Trans
              ns="table"
              i18nKey="field.editor.linkTipMessage"
              components={{ b: <b />, span: <span />, br: <br /> }}
              values={{
                relationship: translation[relationship],
                linkType:
                  tableId === foreignTableId
                    ? t('table:field.editor.inSelfLink')
                    : t('table:field.editor.betweenTwoTables'),
              }}
            />
          </p>
        </>
      )}
      <div>
        <Link
          className="mt-4 flex items-center text-xs underline"
          href={t('table:field.editor.linkFieldKnowMoreLink')}
          target="_blank"
        >
          <ArrowUpRight className="size-4" />
          {t('table:field.editor.knowMore')}
        </Link>
      </div>
    </div>
  );
};