Import company and person from csv file (#1236)

* feat: wip implement back-end call csv import

* fix: rebase IconBrandTwitter missing

* feat: person and company csv import

* fix: test & clean

* fix: clean & test
This commit is contained in:
Jérémy M
2023-08-16 23:18:16 +02:00
committed by GitHub
parent 5890354d21
commit 8863bb0035
74 changed files with 950 additions and 312 deletions

View File

@ -19,7 +19,11 @@ export function DialogProvider({ children }: React.PropsWithChildren) {
<>
{children}
{dialogState.queue.map((dialog) => (
<Dialog {...dialog} onClose={() => handleDialogClose(dialog.id)} />
<Dialog
key={dialog.id}
{...dialog}
onClose={() => handleDialogClose(dialog.id)}
/>
))}
</>
);

View File

@ -11,6 +11,7 @@ export {
IconBrandGithub,
IconBrandGoogle,
IconBrandLinkedin,
IconBrandTwitter,
IconBrandX,
IconBriefcase,
IconBuildingSkyscraper,

View File

@ -30,7 +30,7 @@ const Container = styled.div<{ labelPosition?: LabelPosition }>`
`;
type RadioInputProps = {
radioSize?: RadioSize;
'radio-size'?: RadioSize;
};
const RadioInput = styled(motion.input)<RadioInputProps>`
@ -60,13 +60,13 @@ const RadioInput = styled(motion.input)<RadioInputProps>`
background-color: ${({ theme }) => theme.grayScale.gray0};
border-radius: 50%;
content: '';
height: ${({ radioSize }) =>
height: ${({ 'radio-size': radioSize }) =>
radioSize === RadioSize.Large ? '8px' : '6px'};
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: ${({ radioSize }) =>
width: ${({ 'radio-size': radioSize }) =>
radioSize === RadioSize.Large ? '8px' : '6px'};
}
}
@ -74,10 +74,10 @@ const RadioInput = styled(motion.input)<RadioInputProps>`
cursor: not-allowed;
opacity: 0.12;
}
height: ${({ radioSize }) =>
height: ${({ 'radio-size': radioSize }) =>
radioSize === RadioSize.Large ? '18px' : '16px'};
position: relative;
width: ${({ radioSize }) =>
width: ${({ 'radio-size': radioSize }) =>
radioSize === RadioSize.Large ? '18px' : '16px'};
`;
@ -134,7 +134,7 @@ export function Radio({
data-testid="input-radio"
checked={checked}
value={value}
radioSize={size}
radio-size={size}
disabled={disabled}
onChange={handleChange}
initial={{ scale: 0.95 }}

View File

@ -10,7 +10,7 @@ const Container = styled.div<{ isLast: boolean }>`
flex-grow: ${({ isLast }) => (isLast ? '0' : '1')};
`;
const StepCircle = styled(motion.div)<{ isCurrent: boolean }>`
const StepCircle = styled(motion.div)`
align-items: center;
border-radius: 50%;
border-style: solid;
@ -40,7 +40,7 @@ const StepLabel = styled.span<{ isActive: boolean }>`
white-space: nowrap;
`;
const StepLine = styled(motion.div)<{ isActive: boolean }>`
const StepLine = styled(motion.div)`
height: 2px;
margin-left: ${({ theme }) => theme.spacing(2)};
margin-right: ${({ theme }) => theme.spacing(2)};
@ -92,7 +92,6 @@ export const Step = ({
return (
<Container isLast={isLast}>
<StepCircle
isCurrent={isActive}
variants={variantsCircle}
animate={isActive ? 'active' : 'inactive'}
>
@ -107,7 +106,6 @@ export const Step = ({
<StepLabel isActive={isActive}>{label}</StepLabel>
{!isLast && (
<StepLine
isActive={isActive}
variants={variantsLine}
animate={isActive ? 'active' : 'inactive'}
/>

View File

@ -99,6 +99,7 @@ type OwnProps<SortField> = {
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
onViewsChange?: (views: TableView[]) => void;
onImport?: () => void;
updateEntityMutation: any;
};
@ -108,6 +109,7 @@ export function EntityTable<SortField>({
onColumnsChange,
onSortsUpdate,
onViewsChange,
onImport,
updateEntityMutation,
}: OwnProps<SortField>) {
const tableBodyRef = useRef<HTMLDivElement>(null);
@ -136,6 +138,7 @@ export function EntityTable<SortField>({
onColumnsChange={onColumnsChange}
onSortsUpdate={onSortsUpdate}
onViewsChange={onViewsChange}
onImport={onImport}
/>
<StyledTableWrapper>
<StyledTable>

View File

@ -0,0 +1,34 @@
import { useRecoilCallback } from 'recoil';
import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState';
export function useUpsertEntityTableItems() {
return useRecoilCallback(
({ set, snapshot }) =>
<T extends { id: string }>(entities: T[]) => {
// Create a map of new entities for quick lookup.
const newEntityMap = new Map(
entities.map((entity) => [entity.id, entity]),
);
// Filter out entities that are already the same in the state.
const entitiesToUpdate = entities.filter((entity) => {
const currentEntity = snapshot
.getLoadable(tableEntitiesFamilyState(entity.id))
.valueMaybe();
return (
!currentEntity ||
JSON.stringify(currentEntity) !==
JSON.stringify(newEntityMap.get(entity.id))
);
});
// Batch set state for the filtered entities.
for (const entity of entitiesToUpdate) {
set(tableEntitiesFamilyState(entity.id), entity);
}
},
[],
);
}

View File

@ -0,0 +1,18 @@
import { useRecoilCallback } from 'recoil';
import { tableRowIdsState } from '../states/tableRowIdsState';
export function useUpsertTableRowIds() {
return useRecoilCallback(
({ set, snapshot }) =>
(rowIds: string[]) => {
const currentRowIds = snapshot
.getLoadable(tableRowIdsState)
.valueOrThrow();
const uniqueRowIds = Array.from(new Set([...rowIds, ...currentRowIds]));
set(tableRowIdsState, uniqueRowIds);
},
[],
);
}

View File

@ -9,7 +9,6 @@ import { useTheme } from '@emotion/react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { useSpreadsheetImport } from '@/spreadsheet-import/hooks/useSpreadsheetImport';
import { IconButton } from '@/ui/button/components/IconButton';
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
@ -51,6 +50,7 @@ import { TableOptionsDropdownSection } from './TableOptionsDropdownSection';
type TableOptionsDropdownButtonProps = {
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
onViewsChange?: (views: TableView[]) => void;
onImport?: () => void;
HotkeyScope: TableOptionsHotkeyScope;
};
@ -61,12 +61,11 @@ enum Option {
export const TableOptionsDropdownButton = ({
onColumnsChange,
onViewsChange,
onImport,
HotkeyScope,
}: TableOptionsDropdownButtonProps) => {
const theme = useTheme();
const { openSpreadsheetImport } = useSpreadsheetImport();
const [isUnfolded, setIsUnfolded] = useState(false);
const [selectedOption, setSelectedOption] = useState<Option | undefined>(
undefined,
@ -94,16 +93,6 @@ export const TableOptionsDropdownButton = ({
setHotkeyScopeAndMemorizePreviousScope,
} = usePreviousHotkeyScope();
function handleImport() {
openSpreadsheetImport({
onSubmit: (datam, file) => {
console.log('datam', datam);
console.log('file', file);
},
fields: [],
});
}
const handleColumnVisibilityChange = useCallback(
(columnId: string, nextIsVisible: boolean) => {
const nextColumns = columns.map((column) =>
@ -245,8 +234,8 @@ export const TableOptionsDropdownButton = ({
<IconTag size={theme.icon.size.md} />
Properties
</DropdownMenuItem>
{false && (
<DropdownMenuItem onClick={handleImport}>
{onImport && (
<DropdownMenuItem onClick={onImport}>
<IconFileImport size={theme.icon.size.md} />
Import
</DropdownMenuItem>

View File

@ -26,6 +26,7 @@ type OwnProps<SortField> = {
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
onViewsChange?: (views: TableView[]) => void;
onImport?: () => void;
};
export function TableHeader<SortField>({
@ -34,6 +35,7 @@ export function TableHeader<SortField>({
onColumnsChange,
onSortsUpdate,
onViewsChange,
onImport,
}: OwnProps<SortField>) {
const [sorts, setSorts] = useRecoilScopedState<SelectedSortType<SortField>[]>(
sortScopedState,
@ -82,6 +84,7 @@ export function TableHeader<SortField>({
isPrimaryButton
/>
<TableOptionsDropdownButton
onImport={onImport}
onColumnsChange={onColumnsChange}
onViewsChange={onViewsChange}
HotkeyScope={TableOptionsHotkeyScope.Dropdown}