teableio/teable

View on GitHub
packages/sdk/src/components/expand-record/ExpandRecord.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import type { IRecord } from '@teable/core';
import { Skeleton, cn } from '@teable/ui-lib';
import { isEqual } from 'lodash';
import { useCallback, useMemo } from 'react';
import {
  useFieldCellEditable,
  useFields,
  useIsTouchDevice,
  useRecord,
  useViewId,
  useViews,
  useTableId,
  useBaseId,
} from '../../hooks';
import type { GridView, IFieldInstance } from '../../model';
import { CommentPanel } from '../comment';
import { ExpandRecordHeader } from './ExpandRecordHeader';
import { ExpandRecordWrap } from './ExpandRecordWrap';
import { RecordEditor } from './RecordEditor';
import { RecordHistory } from './RecordHistory';
import { ExpandRecordModel } from './type';

interface IExpandRecordProps {
  recordId: string;
  recordIds?: string[];
  commentId?: string;
  visible?: boolean;
  model?: ExpandRecordModel;
  serverData?: IRecord;
  recordHistoryVisible?: boolean;
  commentVisible?: boolean;
  onClose?: () => void;
  onPrev?: (recordId: string) => void;
  onNext?: (recordId: string) => void;
  onCopyUrl?: () => void;
  onRecordHistoryToggle?: () => void;
  onCommentToggle?: () => void;
  onDelete?: () => Promise<void>;
}

export const ExpandRecord = (props: IExpandRecordProps) => {
  const {
    model,
    visible,
    recordId,
    commentId,
    recordIds,
    serverData,
    recordHistoryVisible,
    commentVisible,
    onPrev,
    onNext,
    onClose,
    onCopyUrl,
    onRecordHistoryToggle,
    onCommentToggle,
    onDelete,
  } = props;
  const views = useViews() as (GridView | undefined)[];
  const tableId = useTableId();
  const defaultViewId = views?.[0]?.id;
  const viewId = useViewId() ?? defaultViewId;
  const baseId = useBaseId();
  const allFields = useFields({ withHidden: true, withDenied: true });
  const showFields = useFields();
  const record = useRecord(recordId, serverData);
  const isTouchDevice = useIsTouchDevice();
  const fieldCellEditable = useFieldCellEditable();

  const fieldCellReadonly = useCallback(
    (field: IFieldInstance) => {
      return !fieldCellEditable(field);
    },
    [fieldCellEditable]
  );

  const showFieldsId = useMemo(() => new Set(showFields.map((field) => field.id)), [showFields]);

  const fields = useMemo(
    () => (viewId ? allFields.filter((field) => showFieldsId.has(field.id)) : []),
    [allFields, showFieldsId, viewId]
  );

  const hiddenFields = useMemo(
    () => (viewId ? allFields.filter((field) => !showFieldsId.has(field.id)) : []),
    [allFields, showFieldsId, viewId]
  );

  const nextRecordIndex = useMemo(() => {
    return recordIds?.length ? recordIds.findIndex((id) => recordId === id) + 1 : -1;
  }, [recordId, recordIds]);

  const prevRecordIndex = useMemo(() => {
    return recordIds?.length ? recordIds.findIndex((id) => recordId === id) - 1 : -1;
  }, [recordId, recordIds]);

  const onChange = useCallback(
    (newValue: unknown, fieldId: string) => {
      if (isEqual(record?.getCellValue(fieldId), newValue)) {
        return;
      }
      if (Array.isArray(newValue) && newValue.length === 0) {
        return record?.updateCell(fieldId, null);
      }
      record?.updateCell(fieldId, newValue);
    },
    [record]
  );

  const onPrevInner = () => {
    if (!recordIds?.length || prevRecordIndex === -1) {
      return;
    }
    onPrev?.(recordIds[prevRecordIndex]);
  };

  const onNextInner = () => {
    if (!recordIds?.length || nextRecordIndex === -1) {
      return;
    }
    onNext?.(recordIds[nextRecordIndex]);
  };

  const disabledPrev = prevRecordIndex < 0;
  const disabledNext = !recordIds?.length || nextRecordIndex >= recordIds.length;

  return (
    <ExpandRecordWrap
      model={isTouchDevice ? ExpandRecordModel.Drawer : model ?? ExpandRecordModel.Modal}
      visible={visible}
      onClose={onClose}
      className={cn({ 'max-w-5xl': commentVisible })}
    >
      <div className="flex h-full flex-col">
        {tableId && recordId && (
          <ExpandRecordHeader
            title={record?.title}
            recordHistoryVisible={recordHistoryVisible}
            commentVisible={commentVisible}
            disabledPrev={disabledPrev}
            disabledNext={disabledNext}
            onClose={onClose}
            onPrev={onPrevInner}
            onNext={onNextInner}
            onCopyUrl={onCopyUrl}
            onRecordHistoryToggle={onRecordHistoryToggle}
            onCommentToggle={onCommentToggle}
            onDelete={onDelete}
            recordId={recordId}
            tableId={tableId}
          />
        )}
        <div className="relative flex flex-1 overflow-hidden">
          {recordHistoryVisible ? (
            <div className="flex size-full overflow-hidden rounded-b bg-background">
              <RecordHistory recordId={recordId} />
            </div>
          ) : (
            <div className="relative flex w-full flex-1 justify-between overflow-y-auto">
              {fields.length > 0 ? (
                <div className="size-full overflow-auto p-9">
                  <RecordEditor
                    record={record}
                    fields={fields}
                    hiddenFields={hiddenFields}
                    onChange={onChange}
                    readonly={fieldCellReadonly}
                  />
                </div>
              ) : (
                <Skeleton className="h-10 w-full rounded" />
              )}

              {commentVisible && baseId && tableId && recordId && (
                <div className="w-80 shrink-0">
                  <CommentPanel
                    tableId={tableId}
                    recordId={recordId}
                    baseId={baseId}
                    commentId={commentId}
                  />
                </div>
              )}
            </div>
          )}
        </div>
      </div>
    </ExpandRecordWrap>
  );
};