Skip to content

Commit

Permalink
feat(import-app): folder mapping tree import mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
Plopix committed Feb 13, 2024
1 parent be11346 commit f3dc05b
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ export interface FormSubmission {
folderPath: string;
rows: Record<string, any>[];
mapping: Record<string, string>;
subFolderMapping: {
column: string;
shapeIdentifier: string;
}[];
groupProductsBy?: string;
doPublish: boolean;
channel?: string;
Expand Down
11 changes: 11 additions & 0 deletions components/crystallize-import/src/contracts/ui-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export type Action =
type: 'UPDATE_MAPPING';
mapping: State['mapping'];
}
| {
type: 'UPDATE_SUB_FOLDER_MAPPING';
mapping: State['subFolderMapping'];
}
| {
type: 'UPDATE_PRODUCT_VARIANT_ATTRIBUTES';
attributes: State['attributes'];
Expand All @@ -49,6 +53,7 @@ export type Actions = {
updateGroupProductsBy: (groupProductsBy: State['groupProductsBy']) => void;
updateSpreadsheet: (headers: State['headers'], rows: State['rows']) => void;
updateMapping: (mapping: State['mapping']) => void;
updateSubFolderMapping: (mapping: State['subFolderMapping']) => void;
updateProductVariantAttributes: (attributes: State['attributes']) => void;
updateDone: (done: State['done']) => void;
updateLoading: (loading: State['loading']) => void;
Expand All @@ -72,7 +77,13 @@ export type State = {
headers: string[];
attributes: string[];
rows: Record<string, any>[];
// in the form of `path.to.field: "Spreadsheet Column Name"`
mapping: Record<string, string>;
// categorize/folderize, in the form of `path.to.field`
subFolderMapping: {
column: string;
shapeIdentifier: string;
}[];
groupProductsBy?: string;
errors?: string[];
loading?: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
JSONItem,
type JSONComponentContent,
type JSONImage,
type JSONProduct,
type JSONProductVariant,
type JsonSpec,
} from '@crystallize/import-utilities';
import { v4 as uuidv4 } from 'uuid';
import type { NumericComponentConfig, Shape } from '@crystallize/schema';
Expand Down Expand Up @@ -113,48 +113,93 @@ const mapVariant = (row: Record<string, any>, mapping: Record<string, string>, s
};

export const specFromFormSubmission = async (submission: FormSubmission, shapes: Shape[]) => {
const { shapeIdentifier, folderPath, rows, mapping, groupProductsBy } = submission;
const spec: JsonSpec = {};
const { shapeIdentifier, folderPath, rows, mapping, groupProductsBy, subFolderMapping } = submission;

const shape = shapes.find((s) => s.identifier === shapeIdentifier);
if (!shape) {
throw new Error(`Shape ${shapeIdentifier} not found.`);
}
const buildExternalReference = (name: string) => {
return folderPath.replace(/^\//, '-').replace(/\//g, '-') + '-' + name.replace(/\//g, '-').toLocaleLowerCase();
};

const variants = rows.map((row) => mapVariant(row, mapping, shape));
const mapProduct = (obj: Record<string, JSONProduct>, row: Record<string, any>, i: number) => {
const productName = row[mapping['item.name']];
let product: JSONProduct = {
name: productName || variants[i].name,
shape: shape.identifier,
vatType: 'No Tax',
parentCataloguePath: folderPath,
variants: [variants[i]],
components: mapComponents(row, mapping, 'components', shape),
};
const folders = subFolderMapping
? rows.reduce((memo: JSONItem[], row) => {
const depth = subFolderMapping.length;
for (let d = 0; d < depth; d++) {
const column = subFolderMapping[d].column;
const name = row[column];
const folder = {
name,
shape: subFolderMapping[d].shapeIdentifier,
externalReference: buildExternalReference(name),
...(d === 0
? { parentCataloguePath: folderPath }
: { parentExternalReference: buildExternalReference(row[subFolderMapping[d - 1].column]) }),
};
if (!memo.some((f) => f.externalReference === folder.externalReference)) {
memo.push(folder);
}
}
return memo;
}, [])
: [];

// init spec with folder
const items: JSONItem[] = folders;

if (groupProductsBy) {
if (obj[row[groupProductsBy]]) {
product = obj[row[groupProductsBy]];
product.variants = product.variants.concat(variants[i]);
const findParent = (row: Record<string, any>) => {
if (subFolderMapping) {
const last = subFolderMapping.length - 1;
const folder = folders.find(
(f) => f.externalReference === buildExternalReference(row[subFolderMapping[last].column]),
);
if (folder) {
return {
parentExternalReference: folder.externalReference,
};
}
obj[row[groupProductsBy]] = product;
} else {
obj[uuidv4()] = product;
return { parentCataloguePath: folderPath };
}

return obj;
};

if (shape.type === 'product') {
spec.items = Object.values(rows.reduce(mapProduct, {}));
const variants = rows.map((row) => mapVariant(row, mapping, shape));
const mapProduct = (obj: Record<string, JSONProduct>, row: Record<string, any>, i: number) => {
const productName = row[mapping['item.name']];
let product: JSONProduct = {
name: productName || variants[i].name,
shape: shape.identifier,
vatType: 'No Tax',
variants: [variants[i]],
components: mapComponents(row, mapping, 'components', shape),
...findParent(row),
};

if (groupProductsBy) {
if (obj[row[groupProductsBy]]) {
product = obj[row[groupProductsBy]];
product.variants = product.variants.concat(variants[i]);
}
obj[row[groupProductsBy]] = product;
} else {
obj[uuidv4()] = product;
}

return obj;
};
items.push(...Object.values(rows.reduce(mapProduct, {})));
} else {
spec.items = rows.map((row) => ({
name: row[mapping['item.name']],
shape: shape.identifier,
parentCataloguePath: folderPath,
components: mapComponents(row, mapping, 'components', shape),
}));
items.push(
...rows.map((row) => ({
name: row[mapping['item.name']],
shape: shape.identifier,
components: mapComponents(row, mapping, 'components', shape),
...findParent(row),
})),
);
}
return spec;
return {
items,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const DataMatchingForm = () => {
folderPath: state.selectedFolder.tree?.path ?? '/',
groupProductsBy: state.groupProductsBy,
mapping: state.mapping,
subFolderMapping: state.subFolderMapping,
channel: channelRef.current?.value,
rows: state.rows.filter((row) => row._import),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const Submit = () => {
mapping: state.mapping,
rows: state.rows.filter((row) => row._import),
doPublish: publishRef.current?.checked ?? false,
subFolderMapping: state.subFolderMapping,
validFlowStage: validFlowRef.current?.value ?? undefined,
invalidFlowStage: invalidFlowRef.current?.value ?? undefined,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export const ColumnHeader = ({ title }: ColumnHeaderProps) => {
setIsPopoverOpen(!isPopoverOpen);
};

const subFolderMapped = state.subFolderMapping?.find((folderM) => folderM.column === title);
return (
<>
<span style={{ flexGrow: 1 }}>{title}</span>
Expand All @@ -101,6 +102,56 @@ export const ColumnHeader = ({ title }: ColumnHeaderProps) => {
positions={['bottom']}
content={
<div className="popover">
<>
<h3 className="popover-list-title">Categories</h3>
<p className="italic text-gray-300 text-xs pt-2 pl-2 pr-2 m-0">
Create a subfolder from that column.
</p>
<ul className="popover-list">
{state.shapes
.filter((shape) => shape.type === 'folder')
.map((shape) => (
<li
className={`popover-item ${subFolderMapped?.shapeIdentifier === shape.identifier ? 'font-bold' : ''}`}
key={shape.identifier}
onClick={() => {
const newMapping = [
// we remove the existing one and put it back at the end of the array if so
...(state.subFolderMapping?.filter(
(folderM) => folderM.column !== title,
) ?? []),
{
column: title,
shapeIdentifier: shape.identifier,
},
];
dispatch.updateSubFolderMapping(newMapping);
setIsPopoverOpen(!isPopoverOpen);
}}
>
{shape.name}
</li>
))}
{subFolderMapped && (
<li
className="popover-item popover-item-remove"
onClick={() => {
// just remove the existing one
const newMapping = state.subFolderMapping?.filter(
(folderM) => folderM.column !== title,
);
dispatch.updateSubFolderMapping(newMapping);
setIsPopoverOpen(!isPopoverOpen);
}}
>
<span style={{ flexGrow: 1, textAlign: 'left' }}>Clear</span> <BsTrashFill />
</li>
)}
</ul>
</>
<hr className="w-2/3" />
<h3 className="popover-list-title">Field Mapping</h3>
<p className="italic text-gray-300 text-xs pt-2 pl-2 pr-2 m-0">One to one mapping.</p>
{!!basicItemFields.length && (
<ColumnMapperList
title="Item Details"
Expand Down Expand Up @@ -150,7 +201,7 @@ export const ColumnHeader = ({ title }: ColumnHeaderProps) => {
}
>
<button
className={cn('popover-button', selectedShapeField ? 'mapped' : '')}
className={cn('popover-button', selectedShapeField || subFolderMapped ? 'mapped' : '')}
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
>
<span style={{ flexGrow: 1, textAlign: 'left' }}>
Expand Down
7 changes: 7 additions & 0 deletions components/crystallize-import/src/ui/import/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export const Reducer = (state: State, action: Action): State => {
preflight: undefined,
errors: [],
};
case 'UPDATE_SUB_FOLDER_MAPPING':
return {
...state,
subFolderMapping: action.mapping,
};
case 'UPDATE_PRODUCT_VARIANT_ATTRIBUTES':
return {
...state,
Expand Down Expand Up @@ -90,4 +95,6 @@ export const mapToReducerActions = (dispatch: Dispatch): Actions => ({
updateProductVariantAttributes: (attributes) => dispatch({ type: 'UPDATE_PRODUCT_VARIANT_ATTRIBUTES', attributes }),
updatePreflight: (preflight) => dispatch({ type: 'UPDATE_PREFLIGHT', preflight }),
updateMainErrors: (errors) => dispatch({ type: 'UPDATE_MAIN_ERRORS', errors }),
updateSubFolderMapping: (mapping: State['subFolderMapping']) =>
dispatch({ type: 'UPDATE_SUB_FOLDER_MAPPING', mapping }),
});

0 comments on commit f3dc05b

Please sign in to comment.