From 976f86093ccfa76c89b90626c7ebd4fc5afb7635 Mon Sep 17 00:00:00 2001 From: Kanav Arora Date: Thu, 30 Nov 2023 00:37:55 +0530 Subject: [PATCH] 2394-feat(front): create new record on click of plus icon (#2660) * 2394-feat(front): create new record on click of plus icon * 2394-feat(front): fix of Icon Button * 2394-fix: PR fixes --------- Co-authored-by: Charles Bochet --- .../files/components/AttachmentRow.tsx | 6 +- .../files/components/Attachments.tsx | 6 +- .../activities/files/hooks/useAttachments.tsx | 4 +- .../components/RecordTableContainer.tsx | 5 +- .../components/RecordTablePage.tsx | 5 +- .../SignInBackgroundMockContainer.tsx | 3 +- .../record-table/components/RecordTable.tsx | 12 +- .../components/RecordTableHeader.tsx | 171 ++------------- .../components/RecordTableHeaderCell.tsx | 200 ++++++++++++++++++ 9 files changed, 238 insertions(+), 174 deletions(-) create mode 100644 front/src/modules/ui/object/record-table/components/RecordTableHeaderCell.tsx diff --git a/front/src/modules/activities/files/components/AttachmentRow.tsx b/front/src/modules/activities/files/components/AttachmentRow.tsx index 4bc3c04c5..15826a696 100644 --- a/front/src/modules/activities/files/components/AttachmentRow.tsx +++ b/front/src/modules/activities/files/components/AttachmentRow.tsx @@ -6,7 +6,7 @@ import { AttachmentDropdown } from '@/activities/files/components/AttachmentDrop import { AttachmentIcon } from '@/activities/files/components/AttachmentIcon'; import { Attachment } from '@/activities/files/types/Attachment'; import { downloadFile } from '@/activities/files/utils/downloadFile'; -import { useDeleteOneObjectRecord } from '@/object-record/hooks/useDeleteOneObjectRecord'; +import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { IconCalendar } from '@/ui/display/icon'; import { FieldContext, @@ -60,8 +60,8 @@ export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => { [attachment?.id], ); - const { deleteOneObject: deleteOneAttachment } = - useDeleteOneObjectRecord({ + const { deleteOneRecord: deleteOneAttachment } = + useDeleteOneRecord({ objectNameSingular: 'attachment', }); diff --git a/front/src/modules/activities/files/components/Attachments.tsx b/front/src/modules/activities/files/components/Attachments.tsx index 9953431e5..1f4cf70e5 100644 --- a/front/src/modules/activities/files/components/Attachments.tsx +++ b/front/src/modules/activities/files/components/Attachments.tsx @@ -8,7 +8,7 @@ import { Attachment } from '@/activities/files/types/Attachment'; import { getFileType } from '@/activities/files/utils/getFileType'; import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord'; +import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { IconPlus } from '@/ui/display/icon'; import { Button } from '@/ui/input/button/components/Button'; import { FileFolder, useUploadFileMutation } from '~/generated/graphql'; @@ -65,8 +65,8 @@ export const Attachments = ({ const [uploadFile] = useUploadFileMutation(); - const { createOneObject: createOneAttachment } = - useCreateOneObjectRecord({ + const { createOneRecord: createOneAttachment } = + useCreateOneRecord({ objectNameSingular: 'attachment', }); diff --git a/front/src/modules/activities/files/hooks/useAttachments.tsx b/front/src/modules/activities/files/hooks/useAttachments.tsx index cbe417b4d..7aa2fd158 100644 --- a/front/src/modules/activities/files/hooks/useAttachments.tsx +++ b/front/src/modules/activities/files/hooks/useAttachments.tsx @@ -1,10 +1,10 @@ import { Attachment } from '@/activities/files/types/Attachment'; -import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords'; +import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { ActivityTargetableEntity } from '../../types/ActivityTargetableEntity'; export const useAttachments = (entity: ActivityTargetableEntity) => { - const { objects: attachments } = useFindManyObjectRecords({ + const { records: attachments } = useFindManyRecords({ objectNamePlural: 'attachments', filter: { [entity.type === 'Company' ? 'companyId' : 'personId']: { eq: entity.id }, diff --git a/front/src/modules/object-record/components/RecordTableContainer.tsx b/front/src/modules/object-record/components/RecordTableContainer.tsx index 4cd25275c..981477221 100644 --- a/front/src/modules/object-record/components/RecordTableContainer.tsx +++ b/front/src/modules/object-record/components/RecordTableContainer.tsx @@ -24,8 +24,10 @@ const StyledContainer = styled.div` export const RecordTableContainer = ({ objectNamePlural, + createRecord, }: { objectNamePlural: string; + createRecord: () => void; }) => { const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem( { @@ -87,7 +89,8 @@ export const RecordTableContainer = ({ ); diff --git a/front/src/modules/object-record/components/RecordTablePage.tsx b/front/src/modules/object-record/components/RecordTablePage.tsx index d77c67ded..9f6bb37b5 100644 --- a/front/src/modules/object-record/components/RecordTablePage.tsx +++ b/front/src/modules/object-record/components/RecordTablePage.tsx @@ -67,7 +67,10 @@ export const RecordTablePage = () => { - + diff --git a/front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx b/front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx index 103e70b96..c3024071f 100644 --- a/front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx +++ b/front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx @@ -33,7 +33,8 @@ export const SignInBackgroundMockContainer = () => { {}} + createRecord={() => {}} + updateRecordMutation={() => {}} /> ); diff --git a/front/src/modules/ui/object/record-table/components/RecordTable.tsx b/front/src/modules/ui/object/record-table/components/RecordTable.tsx index 0f3a67fb5..b124dc48a 100644 --- a/front/src/modules/ui/object/record-table/components/RecordTable.tsx +++ b/front/src/modules/ui/object/record-table/components/RecordTable.tsx @@ -2,6 +2,7 @@ import { useRef } from 'react'; import styled from '@emotion/styled'; import { useRecoilCallback } from 'recoil'; +import { RecordTableHeader } from '@/ui/object/record-table/components/RecordTableHeader'; import { RecordTableInternalEffect } from '@/ui/object/record-table/components/RecordTableInternalEffect'; import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable'; import { RecordTableScope } from '@/ui/object/record-table/scopes/RecordTableScope'; @@ -13,7 +14,6 @@ import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinit import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext'; import { RecordTableBody } from './RecordTableBody'; -import { RecordTableHeader } from './RecordTableHeader'; const StyledTable = styled.table` border-collapse: collapse; @@ -77,13 +77,15 @@ const StyledTableContainer = styled.div` type RecordTableProps = { recordTableId: string; viewBarId: string; - updateEntityMutation: (params: any) => void; + updateRecordMutation: (params: any) => void; + createRecord: () => void; }; export const RecordTable = ({ + updateRecordMutation, + createRecord, recordTableId, viewBarId, - updateEntityMutation, }: RecordTableProps) => { const tableBodyRef = useRef(null); @@ -100,12 +102,12 @@ export const RecordTable = ({ })} > - +
- + ` - ${({ columnWidth }) => ` - min-width: ${columnWidth}px; - width: ${columnWidth}px; - `} - position: relative; - user-select: none; - ${({ theme }) => { - return ` - &:hover { - background: ${theme.background.transparent.light}; - }; - `; - }}; - ${({ isResizing, theme }) => { - if (isResizing) { - return `&:after { - background-color: ${theme.color.blue}; - bottom: 0; - content: ''; - display: block; - position: absolute; - right: -1px; - top: 0; - width: 2px; - }`; - } - }}; -`; - -const StyledResizeHandler = styled.div` - bottom: 0; - cursor: col-resize; - padding: 0 ${({ theme }) => theme.spacing(2)}; - position: absolute; - right: -9px; - top: 0; - width: 3px; - z-index: 1; -`; - const StyledTableHead = styled.thead` cursor: pointer; `; -const StyledColumnHeadContainer = styled.div` - position: relative; - z-index: 1; -`; - const StyledPlusIconHeaderCell = styled.th` ${({ theme }) => { return ` @@ -101,83 +46,17 @@ const HIDDEN_TABLE_COLUMN_DROPDOWN_SCOPE_ID = const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID = 'hidden-table-columns-dropdown-hotkey-scope-id'; -export const RecordTableHeader = () => { - const [resizeFieldOffset, setResizeFieldOffset] = useRecoilState( - resizeFieldOffsetState, - ); +export const RecordTableHeader = ({ + createRecord, +}: { + createRecord: () => void; +}) => { + const { hiddenTableColumnsSelector, visibleTableColumnsSelector } = + useRecordTableScopedStates(); - const { - tableColumnsState, - tableColumnsByKeySelector, - hiddenTableColumnsSelector, - visibleTableColumnsSelector, - } = useRecordTableScopedStates(); - - const tableColumns = useRecoilValue(tableColumnsState); - const tableColumnsByKey = useRecoilValue(tableColumnsByKeySelector); const hiddenTableColumns = useRecoilValue(hiddenTableColumnsSelector); const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector); - const [initialPointerPositionX, setInitialPointerPositionX] = useState< - number | null - >(null); - const [resizedFieldKey, setResizedFieldKey] = useState(null); - - const { handleColumnsChange } = useTableColumns(); - - const handleResizeHandlerStart = useCallback((positionX: number) => { - setInitialPointerPositionX(positionX); - }, []); - - const handleResizeHandlerMove = useCallback( - (positionX: number) => { - if (!initialPointerPositionX) return; - setResizeFieldOffset(positionX - initialPointerPositionX); - }, - [setResizeFieldOffset, initialPointerPositionX], - ); - - const handleResizeHandlerEnd = useRecoilCallback( - ({ snapshot, set }) => - async () => { - if (!resizedFieldKey) return; - - const nextWidth = Math.round( - Math.max( - tableColumnsByKey[resizedFieldKey].size + - snapshot.getLoadable(resizeFieldOffsetState).valueOrThrow(), - COLUMN_MIN_WIDTH, - ), - ); - - set(resizeFieldOffsetState, 0); - setInitialPointerPositionX(null); - setResizedFieldKey(null); - - if (nextWidth !== tableColumnsByKey[resizedFieldKey].size) { - const nextColumns = tableColumns.map((column) => - column.fieldMetadataId === resizedFieldKey - ? { ...column, size: nextWidth } - : column, - ); - - await handleColumnsChange(nextColumns); - } - }, - [resizedFieldKey, tableColumnsByKey, tableColumns, handleColumnsChange], - ); - - useTrackPointer({ - shouldTrackPointer: resizedFieldKey !== null, - onMouseDown: handleResizeHandlerStart, - onMouseMove: handleResizeHandlerMove, - onMouseUp: handleResizeHandlerEnd, - }); - - const primaryColumn = visibleTableColumns.find( - (column) => column.position === 0, - ); - const theme = useTheme(); return ( @@ -193,35 +72,11 @@ export const RecordTableHeader = () => { {visibleTableColumns.map((column) => ( - - - - - { - setResizedFieldKey(column.fieldMetadataId); - }} - /> - + column={column} + createRecord={createRecord} + /> ))} {hiddenTableColumns.length > 0 && ( diff --git a/front/src/modules/ui/object/record-table/components/RecordTableHeaderCell.tsx b/front/src/modules/ui/object/record-table/components/RecordTableHeaderCell.tsx new file mode 100644 index 000000000..45b45999c --- /dev/null +++ b/front/src/modules/ui/object/record-table/components/RecordTableHeaderCell.tsx @@ -0,0 +1,200 @@ +import { useCallback, useState } from 'react'; +import styled from '@emotion/styled'; +import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil'; + +import { IconPlus } from '@/ui/display/icon'; +import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; +import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata'; +import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates'; +import { useTableColumns } from '@/ui/object/record-table/hooks/useTableColumns'; +import { resizeFieldOffsetState } from '@/ui/object/record-table/states/resizeFieldOffsetState'; +import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition'; +import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer'; + +import { ColumnHeadWithDropdown } from './ColumnHeadWithDropdown'; + +const COLUMN_MIN_WIDTH = 104; + +const StyledColumnHeaderCell = styled.th<{ + columnWidth: number; + isResizing?: boolean; +}>` + ${({ columnWidth }) => ` + min-width: ${columnWidth}px; + width: ${columnWidth}px; + `} + position: relative; + user-select: none; + ${({ theme }) => { + return ` + &:hover { + background: ${theme.background.transparent.light}; + }; + `; + }}; + ${({ isResizing, theme }) => { + if (isResizing) { + return `&:after { + background-color: ${theme.color.blue}; + bottom: 0; + content: ''; + display: block; + position: absolute; + right: -1px; + top: 0; + width: 2px; + }`; + } + }}; +`; + +const StyledResizeHandler = styled.div` + bottom: 0; + cursor: col-resize; + padding: 0 ${({ theme }) => theme.spacing(2)}; + position: absolute; + right: -9px; + top: 0; + width: 3px; + z-index: 1; +`; + +const StyledColumnHeadContainer = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + position: relative; + z-index: 1; +`; + +const StyledHeaderIcon = styled.div` + margin-bottom: ${({ theme }) => theme.spacing(1)}; + margin-right: ${({ theme }) => theme.spacing(1)}; + margin-top: ${({ theme }) => theme.spacing(1)}; +`; + +export const RecordTableHeaderCell = ({ + column, + createRecord, +}: { + column: ColumnDefinition; + createRecord: () => void; +}) => { + const [resizeFieldOffset, setResizeFieldOffset] = useRecoilState( + resizeFieldOffsetState, + ); + + const { + tableColumnsState, + tableColumnsByKeySelector, + visibleTableColumnsSelector, + } = useRecordTableScopedStates(); + + const tableColumns = useRecoilValue(tableColumnsState); + const tableColumnsByKey = useRecoilValue(tableColumnsByKeySelector); + const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector); + + const [initialPointerPositionX, setInitialPointerPositionX] = useState< + number | null + >(null); + const [resizedFieldKey, setResizedFieldKey] = useState(null); + + const { handleColumnsChange } = useTableColumns(); + + const handleResizeHandlerStart = useCallback((positionX: number) => { + setInitialPointerPositionX(positionX); + }, []); + + const [iconVisibility, setIconVisibility] = useState(false); + + const primaryColumn = visibleTableColumns.find( + (column) => column.position === 0, + ); + + const handleResizeHandlerMove = useCallback( + (positionX: number) => { + if (!initialPointerPositionX) return; + setResizeFieldOffset(positionX - initialPointerPositionX); + }, + [setResizeFieldOffset, initialPointerPositionX], + ); + + const handleResizeHandlerEnd = useRecoilCallback( + ({ snapshot, set }) => + async () => { + if (!resizedFieldKey) return; + + const nextWidth = Math.round( + Math.max( + tableColumnsByKey[resizedFieldKey].size + + snapshot.getLoadable(resizeFieldOffsetState).valueOrThrow(), + COLUMN_MIN_WIDTH, + ), + ); + + set(resizeFieldOffsetState, 0); + setInitialPointerPositionX(null); + setResizedFieldKey(null); + + if (nextWidth !== tableColumnsByKey[resizedFieldKey].size) { + const nextColumns = tableColumns.map((column) => + column.fieldMetadataId === resizedFieldKey + ? { ...column, size: nextWidth } + : column, + ); + + await handleColumnsChange(nextColumns); + } + }, + [resizedFieldKey, tableColumnsByKey, tableColumns, handleColumnsChange], + ); + + useTrackPointer({ + shouldTrackPointer: resizedFieldKey !== null, + onMouseDown: handleResizeHandlerStart, + onMouseMove: handleResizeHandlerMove, + onMouseUp: handleResizeHandlerEnd, + }); + + return ( + + setIconVisibility(true)} + onMouseLeave={() => setIconVisibility(false)} + > + + {iconVisibility && column.position === 0 && ( + + + + )} + + { + setResizedFieldKey(column.fieldMetadataId); + }} + /> + + ); +};