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

@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const INSERT_MANY_COMPANY = gql`
mutation InsertManyCompany($data: [CompanyCreateManyInput!]!) {
createManyCompany(data: $data) {
count
}
}
`;

View File

@ -0,0 +1,69 @@
import { v4 as uuidv4 } from 'uuid';
import { useSpreadsheetImport } from '@/spreadsheet-import/hooks/useSpreadsheetImport';
import { SpreadsheetOptions } from '@/spreadsheet-import/types';
import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
import { useUpsertEntityTableItems } from '@/ui/table/hooks/useUpsertEntityTableItems';
import { useUpsertTableRowIds } from '@/ui/table/hooks/useUpsertTableRowIds';
import {
GetPeopleDocument,
useInsertManyCompanyMutation,
} from '~/generated/graphql';
import { fieldsForCompany } from '../utils/fieldsForCompany';
export type FieldCompanyMapping = (typeof fieldsForCompany)[number]['key'];
export function useSpreadsheetCompanyImport() {
const { openSpreadsheetImport } = useSpreadsheetImport<FieldCompanyMapping>();
const upsertEntityTableItems = useUpsertEntityTableItems();
const upsertTableRowIds = useUpsertTableRowIds();
const { enqueueSnackBar } = useSnackBar();
const [createManyCompany] = useInsertManyCompanyMutation();
const openCompanySpreadsheetImport = (
options?: Omit<
SpreadsheetOptions<FieldCompanyMapping>,
'fields' | 'isOpen' | 'onClose'
>,
) => {
openSpreadsheetImport({
...options,
async onSubmit(data) {
// TODO: Add better type checking in spreadsheet import later
const createInputs = data.validData.map((company) => ({
id: uuidv4(),
name: company.name as string,
domainName: company.domainName as string,
address: company.address as string,
employees: parseInt(company.employees as string, 10),
linkedinUrl: company.linkedinUrl as string | undefined,
}));
try {
const result = await createManyCompany({
variables: {
data: createInputs,
},
refetchQueries: [GetPeopleDocument],
});
if (result.errors) {
throw result.errors;
}
upsertTableRowIds(createInputs.map((company) => company.id));
upsertEntityTableItems(createInputs);
} catch (error: any) {
enqueueSnackBar(error?.message || 'Something went wrong', {
variant: 'error',
});
}
},
fields: fieldsForCompany,
});
};
return { openCompanySpreadsheetImport };
}

View File

@ -3,6 +3,7 @@ import { useMemo } from 'react';
import { companyViewFields } from '@/companies/constants/companyViewFields';
import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries';
import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries';
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
import { sortsOrderByScopedState } from '@/ui/filter-n-sort/states/sortScopedState';
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
@ -43,6 +44,7 @@ export function CompanyTable() {
viewFieldDefinitions: companyViewFields,
});
const { updateSorts } = useViewSorts({ availableSorts });
const { openCompanySpreadsheetImport } = useSpreadsheetCompanyImport();
const filters = useRecoilScopedValue(
filtersScopedState,
@ -56,6 +58,10 @@ export function CompanyTable() {
const { setContextMenuEntries } = useCompanyTableContextMenuEntries();
const { setActionBarEntries } = useCompanyTableActionBarEntries();
function handleImport() {
openCompanySpreadsheetImport();
}
return (
<>
<GenericEntityTableData
@ -81,6 +87,7 @@ export function CompanyTable() {
onColumnsChange={handleColumnsChange}
onSortsUpdate={currentViewId ? updateSorts : undefined}
onViewsChange={handleViewsChange}
onImport={handleImport}
updateEntityMutation={({
variables,
}: {

View File

@ -0,0 +1,80 @@
import {
IconBrandLinkedin,
IconBuildingSkyscraper,
IconMail,
IconMap,
IconUsers,
} from '@/ui/icon';
export const fieldsForCompany = [
{
icon: <IconBuildingSkyscraper />,
label: 'Name',
key: 'name',
alternateMatches: ['name', 'company name', 'company'],
fieldType: {
type: 'input',
},
example: 'Tim',
validations: [
{
rule: 'required',
errorMessage: 'Name is required',
level: 'error',
},
],
},
{
icon: <IconMail />,
label: 'Domain name',
key: 'domainName',
alternateMatches: ['domain', 'domain name'],
fieldType: {
type: 'input',
},
example: 'apple.dev',
validations: [
{
rule: 'required',
errorMessage: 'Domain name is required',
level: 'error',
},
],
},
{
icon: <IconBrandLinkedin />,
label: 'Linkedin URL',
key: 'linkedinUrl',
alternateMatches: ['linkedIn', 'linkedin', 'linkedin url'],
fieldType: {
type: 'input',
},
example: 'https://www.linkedin.com/in/apple',
},
{
icon: <IconMap />,
label: 'Address',
key: 'address',
fieldType: {
type: 'input',
},
example: 'Maple street',
validations: [
{
rule: 'required',
errorMessage: 'Address is required',
level: 'error',
},
],
},
{
icon: <IconUsers />,
label: 'Employees',
key: 'employees',
alternateMatches: ['employees', 'total employees', 'number of employees'],
fieldType: {
type: 'input',
},
example: '150',
},
] as const;