From 26ae3867318ec2a617df8454fea770df53ad4a67 Mon Sep 17 00:00:00 2001 From: Boris Date: Thu, 23 May 2024 14:27:04 +0800 Subject: [PATCH] feat: add record use modal ui (#618) --- .../app/blocks/view/AddRecordModal.tsx | 160 ++++++++++++++++++ .../components/KanbanStackContainer.tsx | 37 ++-- .../app/blocks/view/tool-bar/GridToolBar.tsx | 50 ++---- 3 files changed, 182 insertions(+), 65 deletions(-) create mode 100644 apps/nextjs-app/src/features/app/blocks/view/AddRecordModal.tsx diff --git a/apps/nextjs-app/src/features/app/blocks/view/AddRecordModal.tsx b/apps/nextjs-app/src/features/app/blocks/view/AddRecordModal.tsx new file mode 100644 index 000000000..daed7e969 --- /dev/null +++ b/apps/nextjs-app/src/features/app/blocks/view/AddRecordModal.tsx @@ -0,0 +1,160 @@ +import { useMutation } from '@tanstack/react-query'; +import { FieldKeyType } from '@teable/core'; +import { createRecords } from '@teable/openapi'; +import { RecordEditor } from '@teable/sdk/components/expand-record/RecordEditor'; +import { useFields, useTableId, useViewId } from '@teable/sdk/hooks'; +import type { IFieldInstance, Record } from '@teable/sdk/model'; +import { createRecordInstance, recordInstanceFieldMap } from '@teable/sdk/model'; +import { Spin } from '@teable/ui-lib/base'; +import { Button, Dialog, DialogContent, DialogTrigger } from '@teable/ui-lib/shadcn'; +import { isEqual } from 'lodash'; +import { useTranslation } from 'next-i18next'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCounter } from 'react-use'; + +interface IAddRecordModalProps { + children?: React.ReactNode; + callback?: (recordId: string) => void; +} + +export const AddRecordModal = (props: IAddRecordModalProps) => { + const { children, callback } = props; + const tableId = useTableId(); + const viewId = useViewId(); + const showFields = useFields(); + const [open, setOpen] = useState(false); + const [version, updateVersion] = useCounter(0); + const { t } = useTranslation('common'); + const allFields = useFields({ withHidden: true, withDenied: true }); + const [record, setRecord] = useState(undefined); + + const { mutate: createRecord, isLoading } = useMutation({ + mutationFn: (fields: { [fieldId: string]: unknown }) => + createRecords(tableId!, { + records: [{ fields }], + fieldKeyType: FieldKeyType.Id, + }), + onSuccess: (data) => { + setOpen(false); + callback?.(data.data.records[0].id); + }, + }); + + const newRecord = useCallback( + (version: number = 0) => { + setRecord((preRecord) => { + const record = createRecordInstance({ + id: '', + fields: version > 0 && preRecord?.fields ? preRecord.fields : {}, + }); + record.updateCell = (fieldId: string, newValue: unknown) => { + record.fields[fieldId] = newValue; + updateVersion.inc(); + return Promise.resolve(); + }; + return record; + }); + }, + [updateVersion] + ); + + useEffect(() => { + if (!open) { + updateVersion.reset(); + newRecord(); + } + }, [newRecord, open, updateVersion]); + + useEffect(() => { + // init record + newRecord(); + }, [newRecord]); + + useEffect(() => { + if (version > 0) { + newRecord(version); + } + }, [version, newRecord]); + + useEffect(() => { + if (!allFields.length) { + return; + } + setRecord((record) => + record + ? recordInstanceFieldMap( + record, + allFields.reduce( + (acc, field) => { + acc[field.id] = field; + return acc; + }, + {} as { [fieldId: string]: IFieldInstance } + ) + ) + : record + ); + }, [allFields, record]); + + 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 onChange = (newValue: unknown, fieldId: string) => { + if (isEqual(record?.getCellValue(fieldId), newValue)) { + return; + } + record?.updateCell(fieldId, newValue); + }; + + return ( + + {children} + e.stopPropagation()} + onInteractOutside={(e) => e.preventDefault()} + onKeyDown={(e) => e.stopPropagation()} + > +
+ +
+
+ + +
+
+
+ ); +}; diff --git a/apps/nextjs-app/src/features/app/blocks/view/kanban/components/KanbanStackContainer.tsx b/apps/nextjs-app/src/features/app/blocks/view/kanban/components/KanbanStackContainer.tsx index ef7638840..7aadef1da 100644 --- a/apps/nextjs-app/src/features/app/blocks/view/kanban/components/KanbanStackContainer.tsx +++ b/apps/nextjs-app/src/features/app/blocks/view/kanban/components/KanbanStackContainer.tsx @@ -1,20 +1,18 @@ /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ import { Draggable } from '@hello-pangea/dnd'; -import { FieldKeyType } from '@teable/core'; import { Plus } from '@teable/icons'; -import { createRecords } from '@teable/openapi'; import { generateLocalId } from '@teable/sdk/components'; import { useTableId, useViewId } from '@teable/sdk/hooks'; import type { Record } from '@teable/sdk/model'; import { Button, cn } from '@teable/ui-lib'; import { useRef, useState } from 'react'; import type { VirtuosoHandle } from 'react-virtuoso'; +import { AddRecordModal } from '../../AddRecordModal'; import { UNCATEGORIZED_STACK_ID } from '../constant'; import type { IKanbanContext } from '../context'; import { useInView, useKanban } from '../hooks'; import { useKanbanStackCollapsedStore } from '../store'; import type { IStackData } from '../type'; -import { getCellValueByStack } from '../utils'; import type { ICardMap } from './interface'; import { KanbanStack } from './KanbanStack'; import { KanbanStackHeader } from './KanbanStackHeader'; @@ -34,34 +32,17 @@ export const KanbanStackContainer = (props: IKanbanStackContainerProps) => { const tableId = useTableId(); const viewId = useViewId(); const { collapsedStackMap, setCollapsedStackMap } = useKanbanStackCollapsedStore(); - const { permission, stackField, setExpandRecordId } = useKanban() as Required; + const { permission } = useKanban() as Required; const [ref, isInView] = useInView(); const [editMode, setEditMode] = useState(false); const virtuosoRef = useRef(null); const { id: stackId } = stack; - const { id: fieldId, type: fieldType } = stackField; const { stackDraggable, cardCreatable } = permission; const isUncategorized = stackId === UNCATEGORIZED_STACK_ID; const draggable = stackDraggable && !disabled && !editMode && !isUncategorized; - const onAppend = async () => { - if (tableId == null) return; - const cellValue = getCellValueByStack(fieldType, stack); - const res = await createRecords(tableId, { - fieldKeyType: FieldKeyType.Id, - records: [ - { - fields: { [fieldId]: cellValue }, - }, - ], - }); - const record = res.data.records[0]; - - if (record != null) { - setExpandRecordId(record.id); - } - + const onAppendCallback = () => { setTimeout(() => { virtuosoRef.current?.scrollToIndex({ index: 'LAST', @@ -130,11 +111,13 @@ export const KanbanStackContainer = (props: IKanbanStackContainerProps) => { {cardCreatable && ( -
- -
+ +
+ +
+
)} diff --git a/apps/nextjs-app/src/features/app/blocks/view/tool-bar/GridToolBar.tsx b/apps/nextjs-app/src/features/app/blocks/view/tool-bar/GridToolBar.tsx index 44cb6e657..277c93b9e 100644 --- a/apps/nextjs-app/src/features/app/blocks/view/tool-bar/GridToolBar.tsx +++ b/apps/nextjs-app/src/features/app/blocks/view/tool-bar/GridToolBar.tsx @@ -1,51 +1,25 @@ import { Plus } from '@teable/icons'; -import { useTable, useTablePermission } from '@teable/sdk/hooks'; +import { useTablePermission } from '@teable/sdk/hooks'; import { Button } from '@teable/ui-lib/shadcn/ui/button'; -import { useRouter } from 'next/router'; -import { useCallback } from 'react'; +import { AddRecordModal } from '../AddRecordModal'; import { GridViewOperators } from './components'; import { Others } from './Others'; export const GridToolBar: React.FC = () => { - const table = useTable(); - const router = useRouter(); const permission = useTablePermission(); - const addRecord = useCallback(async () => { - if (!table) { - return; - } - await table.createRecord({}).then((res) => { - const record = res.data.records[0]; - - if (record == null) return; - - const recordId = record.id; - - router.push( - { - pathname: router.pathname, - query: { ...router.query, recordId }, - }, - undefined, - { - shallow: true, - } - ); - }); - }, [router, table]); - return (
- + + +