feat: detach records from Relation field card in Show Page (#3350)
* feat: detach records from Relation field card in Show Page Closes #3128 * Fix TS --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -30,7 +30,7 @@ module.exports = {
|
|||||||
patterns: [
|
patterns: [
|
||||||
{
|
{
|
||||||
group: ['@tabler/icons-react'],
|
group: ['@tabler/icons-react'],
|
||||||
message: 'Icon imports are only allowed for `@/ui/icon`',
|
message: 'Icon imports are only allowed for `@/ui/display/icon`',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
group: ['react-hotkeys-web-hook'],
|
group: ['react-hotkeys-web-hook'],
|
||||||
|
|||||||
@ -1,52 +1,146 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { LightIconButton, MenuItem } from 'tsup.ui.index';
|
||||||
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { FieldDisplay } from '@/object-record/field/components/FieldDisplay';
|
import { FieldDisplay } from '@/object-record/field/components/FieldDisplay';
|
||||||
import { FieldContext } from '@/object-record/field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/field/contexts/FieldContext';
|
||||||
|
import { usePersistField } from '@/object-record/field/hooks/usePersistField';
|
||||||
import { FieldRelationMetadata } from '@/object-record/field/types/FieldMetadata';
|
import { FieldRelationMetadata } from '@/object-record/field/types/FieldMetadata';
|
||||||
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
||||||
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
import { IconDotsVertical, IconUnlink } from '@/ui/display/icon';
|
||||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
||||||
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
|
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||||
|
|
||||||
const StyledCardContent = styled(CardContent)`
|
const StyledCardContent = styled(CardContent)<{
|
||||||
|
isDropdownOpen?: boolean;
|
||||||
|
}>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
display: flex;
|
display: flex;
|
||||||
height: ${({ theme }) => theme.spacing(10)};
|
height: ${({ theme }) => theme.spacing(10)};
|
||||||
padding: ${({ theme }) => theme.spacing(0, 2, 0, 3)};
|
padding: ${({ theme }) => theme.spacing(0, 2, 0, 3)};
|
||||||
|
|
||||||
|
${({ isDropdownOpen, theme }) =>
|
||||||
|
isDropdownOpen
|
||||||
|
? ''
|
||||||
|
: css`
|
||||||
|
.displayOnHover {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity ${theme.animation.duration.instant}s ease;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.displayOnHover {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type RecordRelationFieldCardContentProps = {
|
type RecordRelationFieldCardContentProps = {
|
||||||
divider?: boolean;
|
divider?: boolean;
|
||||||
relationRecordId: string;
|
relationRecord: ObjectRecord;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordRelationFieldCardContent = ({
|
export const RecordRelationFieldCardContent = ({
|
||||||
divider,
|
divider,
|
||||||
relationRecordId,
|
relationRecord,
|
||||||
}: RecordRelationFieldCardContentProps) => {
|
}: RecordRelationFieldCardContentProps) => {
|
||||||
const { fieldDefinition } = useContext(FieldContext);
|
const { fieldDefinition } = useContext(FieldContext);
|
||||||
const { relationObjectMetadataNameSingular } =
|
const {
|
||||||
fieldDefinition.metadata as FieldRelationMetadata;
|
relationFieldMetadataId,
|
||||||
const { labelIdentifierFieldMetadata: relationLabelIdentifierFieldMetadata } =
|
relationObjectMetadataNameSingular,
|
||||||
useObjectMetadataItem({
|
relationType,
|
||||||
objectNameSingular: relationObjectMetadataNameSingular,
|
} = fieldDefinition.metadata as FieldRelationMetadata;
|
||||||
});
|
const isToOneObject = relationType === 'TO_ONE_OBJECT';
|
||||||
|
const {
|
||||||
|
labelIdentifierFieldMetadata: relationLabelIdentifierFieldMetadata,
|
||||||
|
objectMetadataItem: relationObjectMetadataItem,
|
||||||
|
} = useObjectMetadataItem({
|
||||||
|
objectNameSingular: relationObjectMetadataNameSingular,
|
||||||
|
});
|
||||||
|
const persistField = usePersistField();
|
||||||
|
const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({
|
||||||
|
objectNameSingular: relationObjectMetadataNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
const { FieldContextProvider } = useFieldContext({
|
const { FieldContextProvider } = useFieldContext({
|
||||||
fieldMetadataName: relationLabelIdentifierFieldMetadata?.name || '',
|
fieldMetadataName: relationLabelIdentifierFieldMetadata?.name || '',
|
||||||
fieldPosition: 0,
|
fieldPosition: 0,
|
||||||
isLabelIdentifier: true,
|
isLabelIdentifier: true,
|
||||||
objectNameSingular: relationObjectMetadataNameSingular,
|
objectNameSingular: relationObjectMetadataNameSingular,
|
||||||
objectRecordId: relationRecordId,
|
objectRecordId: relationRecord.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dropdownScopeId = `record-field-card-menu-${relationRecord.id}`;
|
||||||
|
|
||||||
|
const { closeDropdown, isDropdownOpen } = useDropdown(dropdownScopeId);
|
||||||
|
|
||||||
if (!FieldContextProvider) return null;
|
if (!FieldContextProvider) return null;
|
||||||
|
|
||||||
|
const handleDetach = () => {
|
||||||
|
closeDropdown();
|
||||||
|
|
||||||
|
const relationFieldMetadataItem = relationObjectMetadataItem.fields.find(
|
||||||
|
({ id }) => id === relationFieldMetadataId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!relationFieldMetadataItem?.name) return;
|
||||||
|
|
||||||
|
if (isToOneObject) {
|
||||||
|
persistField(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOneRelationRecord({
|
||||||
|
idToUpdate: relationRecord.id,
|
||||||
|
updateOneRecordInput: {
|
||||||
|
[`${relationFieldMetadataItem.name}Id`]: null,
|
||||||
|
[relationFieldMetadataItem.name]: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledCardContent divider={divider}>
|
<StyledCardContent isDropdownOpen={isDropdownOpen} divider={divider}>
|
||||||
<FieldContextProvider>
|
<FieldContextProvider>
|
||||||
<FieldDisplay />
|
<FieldDisplay />
|
||||||
</FieldContextProvider>
|
</FieldContextProvider>
|
||||||
|
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
||||||
|
<Dropdown
|
||||||
|
dropdownId={dropdownScopeId}
|
||||||
|
dropdownPlacement="right-start"
|
||||||
|
clickableComponent={
|
||||||
|
<LightIconButton
|
||||||
|
className="displayOnHover"
|
||||||
|
Icon={IconDotsVertical}
|
||||||
|
accent="tertiary"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
dropdownComponents={
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
<MenuItem
|
||||||
|
LeftIcon={IconUnlink}
|
||||||
|
text="Detach"
|
||||||
|
onClick={handleDetach}
|
||||||
|
/>
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
}
|
||||||
|
dropdownHotkeyScope={{
|
||||||
|
scope: dropdownScopeId,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DropdownScope>
|
||||||
</StyledCardContent>
|
</StyledCardContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -183,7 +183,7 @@ export const RecordRelationFieldCardSection = () => {
|
|||||||
}, [setRelationPickerSearchFilter]);
|
}, [setRelationPickerSearchFilter]);
|
||||||
|
|
||||||
const persistField = usePersistField();
|
const persistField = usePersistField();
|
||||||
const { updateOneRecord } = useUpdateOneRecord({
|
const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({
|
||||||
objectNameSingular: relationObjectMetadataNameSingular,
|
objectNameSingular: relationObjectMetadataNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -201,7 +201,7 @@ export const RecordRelationFieldCardSection = () => {
|
|||||||
|
|
||||||
if (!relationFieldMetadataItem?.name) return;
|
if (!relationFieldMetadataItem?.name) return;
|
||||||
|
|
||||||
updateOneRecord({
|
updateOneRelationRecord({
|
||||||
idToUpdate: selectedRelationEntity.id,
|
idToUpdate: selectedRelationEntity.id,
|
||||||
updateOneRecordInput: {
|
updateOneRecordInput: {
|
||||||
[`${relationFieldMetadataItem.name}Id`]: entityId,
|
[`${relationFieldMetadataItem.name}Id`]: entityId,
|
||||||
@ -267,7 +267,7 @@ export const RecordRelationFieldCardSection = () => {
|
|||||||
<RecordRelationFieldCardContent
|
<RecordRelationFieldCardContent
|
||||||
key={`${relationRecord.id}${relationLabelIdentifierFieldMetadata?.id}`}
|
key={`${relationRecord.id}${relationLabelIdentifierFieldMetadata?.id}`}
|
||||||
divider={index < relationRecords.length - 1}
|
divider={index < relationRecords.length - 1}
|
||||||
relationRecordId={relationRecord.id}
|
relationRecord={relationRecord}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -108,6 +108,7 @@ export {
|
|||||||
IconTextSize,
|
IconTextSize,
|
||||||
IconTimelineEvent,
|
IconTimelineEvent,
|
||||||
IconTrash,
|
IconTrash,
|
||||||
|
IconUnlink,
|
||||||
IconUpload,
|
IconUpload,
|
||||||
IconUser,
|
IconUser,
|
||||||
IconUserCircle,
|
IconUserCircle,
|
||||||
|
|||||||
Reference in New Issue
Block a user