## Context - We now display workspace member full name and email in role assignment picker - Replaced Forbidden by "Not shared" with lock icon - Fix Disabled for Danger accent - Fix avatar URL <img width="575" alt="Screenshot 2025-06-23 at 16 38 56" src="https://github.com/user-attachments/assets/08430bfe-29c4-4ac4-821c-9062dfad7150" /> <img width="756" alt="Screenshot 2025-06-23 at 16 21 36" src="https://github.com/user-attachments/assets/c19f31bd-fe9d-415d-aa55-62fa3e228c49" /> <img width="373" alt="Screenshot 2025-06-23 at 17 13 08" src="https://github.com/user-attachments/assets/e2f7878c-7c5a-40b4-a482-8e99292257c3" /> <img width="342" alt="Screenshot 2025-06-23 at 17 37 49" src="https://github.com/user-attachments/assets/04169e85-14dd-4aed-bd71-7aefd601a894" /> <img width="434" alt="Screenshot 2025-06-23 at 17 37 35" src="https://github.com/user-attachments/assets/7caf0967-c4dd-4d0f-90c8-259a85182b19" />
151 lines
6.0 KiB
TypeScript
151 lines
6.0 KiB
TypeScript
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
|
import { getFieldMetadataTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFieldMetadataTypeLabel';
|
|
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
|
import { DO_NOT_IMPORT_OPTION_KEY } from '@/spreadsheet-import/constants/DoNotImportOptionKey';
|
|
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
|
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
|
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
|
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
|
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
|
import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel';
|
|
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
|
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
|
import styled from '@emotion/styled';
|
|
import { useLingui } from '@lingui/react/macro';
|
|
import { isNonEmptyString } from '@sniptt/guards';
|
|
import { useState } from 'react';
|
|
import { IconForbid, IconX, useIcons } from 'twenty-ui/display';
|
|
import { SelectOption } from 'twenty-ui/input';
|
|
import { MenuItemSelect } from 'twenty-ui/navigation';
|
|
import { ReadonlyDeep } from 'type-fest';
|
|
|
|
const StyledContainer = styled.div`
|
|
max-height: 360px;
|
|
`;
|
|
|
|
export const MatchColumnSelectFieldSelectDropdownContent = ({
|
|
selectedValue,
|
|
onSelectFieldMetadataItem,
|
|
onSelectSuggestedOption,
|
|
onCancelSelect,
|
|
onDoNotImportSelect,
|
|
suggestedOptions,
|
|
}: {
|
|
selectedValue: SelectOption | undefined;
|
|
onSelectFieldMetadataItem: (
|
|
selectedFieldMetadataItem: FieldMetadataItem,
|
|
) => void;
|
|
onSelectSuggestedOption: (selectedSuggestedOption: SelectOption) => void;
|
|
onCancelSelect: () => void;
|
|
onDoNotImportSelect: () => void;
|
|
suggestedOptions: readonly ReadonlyDeep<
|
|
SelectOption & { fieldMetadataTypeLabel?: string }
|
|
>[];
|
|
}) => {
|
|
const [searchFilter, setSearchFilter] = useState('');
|
|
|
|
const handleFilterChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
const value = event.currentTarget.value;
|
|
|
|
setSearchFilter(value);
|
|
};
|
|
|
|
const { availableFieldMetadataItems } = useSpreadsheetImportInternal();
|
|
|
|
const filteredAvailableFieldMetadataItems =
|
|
availableFieldMetadataItems.filter(
|
|
(field) =>
|
|
field.label.toLowerCase().includes(searchFilter.toLowerCase()) ||
|
|
field.name.toLowerCase().includes(searchFilter.toLowerCase()),
|
|
);
|
|
|
|
const { getIcon } = useIcons();
|
|
|
|
const handleFieldClick = (fieldMetadataItem: FieldMetadataItem) => {
|
|
onSelectFieldMetadataItem(fieldMetadataItem);
|
|
};
|
|
|
|
const handleSuggestedOptionClick = (suggestedOption: SelectOption) => {
|
|
onSelectSuggestedOption(suggestedOption);
|
|
};
|
|
|
|
const handleCancelClick = () => {
|
|
onCancelSelect();
|
|
};
|
|
|
|
const { t } = useLingui();
|
|
|
|
return (
|
|
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
|
|
<DropdownMenuHeader
|
|
StartComponent={
|
|
<DropdownMenuHeaderLeftComponent
|
|
onClick={handleCancelClick}
|
|
Icon={IconX}
|
|
/>
|
|
}
|
|
>
|
|
Select matching field
|
|
</DropdownMenuHeader>
|
|
<DropdownMenuSearchInput
|
|
value={searchFilter}
|
|
onChange={handleFilterChange}
|
|
autoFocus
|
|
placeholder={t`Search fields`}
|
|
/>
|
|
<DropdownMenuSeparator />
|
|
<StyledContainer>
|
|
<ScrollWrapper componentInstanceId="match-column-select-field-select-dropdown-content">
|
|
{!isNonEmptyString(searchFilter) && (
|
|
<>
|
|
<DropdownMenuItemsContainer scrollable={false}>
|
|
<MenuItemSelect
|
|
selected={selectedValue?.value === DO_NOT_IMPORT_OPTION_KEY}
|
|
onClick={onDoNotImportSelect}
|
|
LeftIcon={IconForbid}
|
|
text={t`Do not import`}
|
|
/>
|
|
</DropdownMenuItemsContainer>
|
|
{suggestedOptions.length > 0 && (
|
|
<>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuSectionLabel label={t`Suggested`} />
|
|
<DropdownMenuItemsContainer scrollable={false}>
|
|
{suggestedOptions.map((option) => (
|
|
<MenuItemSelect
|
|
key={option.value}
|
|
selected={selectedValue?.value === option.value}
|
|
onClick={() => handleSuggestedOptionClick(option)}
|
|
LeftIcon={option.Icon}
|
|
text={option.label}
|
|
contextualText={option.fieldMetadataTypeLabel}
|
|
/>
|
|
))}
|
|
</DropdownMenuItemsContainer>
|
|
</>
|
|
)}
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuSectionLabel label={t`All fields`} />
|
|
</>
|
|
)}
|
|
<DropdownMenuItemsContainer scrollable={false}>
|
|
{filteredAvailableFieldMetadataItems.map((field) => (
|
|
<MenuItemSelect
|
|
key={field.id}
|
|
selected={selectedValue?.value === field.name}
|
|
onClick={() => handleFieldClick(field)}
|
|
LeftIcon={getIcon(field.icon)}
|
|
text={field.label}
|
|
contextualText={getFieldMetadataTypeLabel(field.type)}
|
|
hasSubMenu={isCompositeFieldType(field.type)}
|
|
/>
|
|
))}
|
|
</DropdownMenuItemsContainer>
|
|
</ScrollWrapper>
|
|
</StyledContainer>
|
|
</DropdownContent>
|
|
);
|
|
};
|