Skip to content

Commit

Permalink
feat: Added re-order and move attributes [PT-186024265] [PT-186024290]
Browse files Browse the repository at this point in the history
This adds a draggable table context with a draggable header element that allows users to re-order columns in the collection and move elements to new collections.

This also has some existing missing key cleanup work in it.

NOTE: This does not use DnDKit as that does not work with tables.
  • Loading branch information
dougmartin committed Oct 2, 2023
1 parent 4749525 commit 1ae33df
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 67 deletions.
2 changes: 2 additions & 0 deletions src/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ function App() {
interactiveState={interactiveState}
handleSelectDataSet={handleSelectDataSet}
updateInteractiveState={updateInteractiveState}
handleUpdateAttributePosition={handleUpdateAttributePosition}
handleSetCollections={handleSetCollections}
/>
);

Expand Down
34 changes: 34 additions & 0 deletions src/components/draggable-table-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from "react";
import { useDraggableTableContext } from "../hooks/useDraggableTable";

const border = "3px solid #FBF719";
const borderLeft = border;
const borderRight = border;

interface DraggagleTableHeaderProps {
collectionId: number;
attrTitle: string;
colSpan?: number;
}

export const DraggagleTableHeader: React.FC<DraggagleTableHeaderProps> = ({collectionId, attrTitle, children}) => {
const {dragOverId, dragSide, handleDragStart, handleDragOver, handleOnDrop, handleDragEnter,
handleDragLeave} = useDraggableTableContext();
const id = `${collectionId}-${attrTitle}`;
const style: React.CSSProperties = id === dragOverId ? (dragSide === "left" ? {borderLeft} : {borderRight}) : {};

return (
<th
data-id={id}
style={style}
draggable={true}
onDragStart={handleDragStart}
onDragOver={handleDragOver}
onDrop={handleOnDrop}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
>
{children}
</th>
);
};
14 changes: 11 additions & 3 deletions src/components/flat-table.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import { ITableProps } from "../types";
import { DraggagleTableHeader } from "./draggable-table-header";

import css from "./tables.scss";

Expand All @@ -23,11 +24,18 @@ export const FlatTable = (props: IFlatProps) => {
<th colSpan={items.length}>{collections[0].title}</th>
</tr>}
<tr>
{collection.attrs.map((attr: any) => <th key={attr.title}>{attr.title}</th>)}
{collection.attrs.map((attr: any) =>
<DraggagleTableHeader
key={attr.title}
collectionId={collection.id}
attrTitle={attr.title}
>
{attr.title}
</DraggagleTableHeader>)}
</tr>
{items.length && items.map((item) => {
{items.map((item, index) => {
return (
<tr key={`${item.id}`}>{mapCellsFromValues(item)}</tr>
<tr key={`${index}-${item.id}`}>{mapCellsFromValues(`row-${index}`, item)}</tr>
);
})}
</tbody>
Expand Down
26 changes: 18 additions & 8 deletions src/components/landscape-view.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import { ICollection, IProcessedCaseObj, ITableProps } from "../types";
import { DraggagleTableHeader } from "./draggable-table-header";

import css from "./tables.scss";

Expand All @@ -18,9 +19,9 @@ export const LandscapeView = (props: ITableProps) => {
<th colSpan={valueCount}>{parentColl.name}</th>
</tr> }
<tr className={css[className]}>
{firstRowValues.map(values => mapHeadersFromValues(values))}
{firstRowValues.map(values => mapHeadersFromValues(parentColl.id, "first-row", values))}
</tr>
<tr className={css[className]}>{firstRowValues.map(values => mapCellsFromValues(values))}</tr>
<tr className={css[className]}>{firstRowValues.map(values => mapCellsFromValues("first-row", values))}</tr>
<tr className={css[className]}>
{parentColl.cases.map((caseObj) => {
return (
Expand All @@ -30,7 +31,7 @@ export const LandscapeView = (props: ITableProps) => {
style={{...paddingStyle, verticalAlign: "top"}}
colSpan={Object.values(caseObj.values).length}>
<div style={{width: `100%`, overflow: "scroll"}}>
{renderColFromCaseObj(caseObj)}
{renderColFromCaseObj(parentColl, caseObj)}
</div>
</td>
);
Expand All @@ -40,7 +41,7 @@ export const LandscapeView = (props: ITableProps) => {
);
};

const renderColFromCaseObj = (caseObj: IProcessedCaseObj, index?: number) => {
const renderColFromCaseObj = (collection: ICollection, caseObj: IProcessedCaseObj, index?: number) => {
const {children, values} = caseObj;
const isFirstIndex = index === 0;
if (!children.length) {
Expand All @@ -52,8 +53,12 @@ export const LandscapeView = (props: ITableProps) => {
<th colSpan={Object.keys(values).length}>{caseObj.collection.name}</th>
</tr>
}
{isFirstIndex && <tr className={css[className]}>{mapHeadersFromValues(values)}</tr>}
<tr>{mapCellsFromValues(values)}</tr>
{isFirstIndex &&
<tr className={css[className]}>
{mapHeadersFromValues(collection.id, `first-row-${index}`, values)}
</tr>
}
<tr>{mapCellsFromValues(`row-${index}`, values)}</tr>
</>
);
} else {
Expand All @@ -68,7 +73,7 @@ export const LandscapeView = (props: ITableProps) => {
{anyChildHasChildren ?
renderNestedTable(filteredCollection) :
caseObj.children.map((child: IProcessedCaseObj, i: number) => {
return renderColFromCaseObj(child, i);
return renderColFromCaseObj(collection, child, i);
})
}
</tbody>
Expand All @@ -86,7 +91,12 @@ export const LandscapeView = (props: ITableProps) => {
<table className={`${css.mainTable} ${css.landscapeTable} ${css.landscape} ${css[className]}`}>
<tbody>
<tr className={css.mainHeader}>
<th colSpan={getValueLength(firstRowValues)}>{selectedDataSet.name}</th>
<DraggagleTableHeader
collectionId={parentColl[0].id}
attrTitle={selectedDataSet.name}
colSpan={getValueLength(firstRowValues)}>
{selectedDataSet.name}
</DraggagleTableHeader>
</tr>
{renderNestedTable(parentColl[0])}
</tbody>
Expand Down
48 changes: 31 additions & 17 deletions src/components/nested-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { PortraitView } from "./portrait-view";
import { Menu } from "./menu";
import { LandscapeView } from "./landscape-view";
import { FlatTable } from "./flat-table";
import { DraggableTableContext, useDraggableTable } from "../hooks/useDraggableTable";
import { DraggagleTableHeader } from "./draggable-table-header";

const portrait = "Portrait";
const landscape = "Landscape";
Expand All @@ -18,14 +20,23 @@ interface IProps {
interactiveState: InteractiveState
handleSelectDataSet: (e: React.ChangeEvent<HTMLSelectElement>) => void
updateInteractiveState: (update: Partial<InteractiveState>) => void
handleSetCollections: (collections: Array<ICollection>) => void
handleUpdateAttributePosition: (collection: ICollection, attrName: string, newPosition: number) => void,
}

export const NestedTable = (props: IProps) => {
const {selectedDataSet, dataSets, collections, items, interactiveState,
handleSelectDataSet, updateInteractiveState} = props;
handleSelectDataSet, updateInteractiveState, handleSetCollections,
handleUpdateAttributePosition} = props;
const [collectionClasses, setCollectionClasses] = useState<Array<ICollectionClass>>([]);
const [paddingStyle, setPaddingStyle] = useState<Record<string, string>>({padding: "0px"});

const draggableTable = useDraggableTable({
collections,
handleSetCollections,
handleUpdateAttributePosition
});

useEffect(() => {
if (collections.length) {
const classes = collections.map((coll: ICollection, idx: number) => {
Expand Down Expand Up @@ -72,30 +83,31 @@ export const NestedTable = (props: IProps) => {
updateInteractiveState({displayMode: e.target.value});
}, [updateInteractiveState]);

const mapHeadersFromValues = (values: IValues) => {
const mapHeadersFromValues = (collectionId: number, rowKey: string, values: IValues) => {
return (
<>
{(Object.keys(values)).map((key, index) => {
if (typeof values[key] === "string" || typeof values[key] === "number") {
return (<th key={`${key}-${index}`}>{key}</th>);
}
return (
<DraggagleTableHeader
key={`${collectionId}-${rowKey}-${key}-${index}`}
collectionId={collectionId}
attrTitle={key}
>{key}
</DraggagleTableHeader>
);
}
)}
})}
</>
);
};

const mapCellsFromValues = (values: IValues) => {
return (
<>
{(Object.values(values)).map((val, index) => {
if (typeof val === "string" || typeof val === "number") {
return (<td key={`${val}-${index}}`}>{val}</td>);
}
}
)}
</>
);
const mapCellsFromValues = (rowKey: string, values: IValues) => {
return Object.values(values).map((val, index) => {
if (typeof val === "string" || typeof val === "number") {
return (<td key={`${rowKey}-${val}-${index}}`}>{val}</td>);
}
});
};

const getValueLength = (firstRow: Array<IValues>) => {
Expand Down Expand Up @@ -140,7 +152,9 @@ export const NestedTable = (props: IProps) => {
padding={interactiveState.padding}
displayMode={interactiveState.displayMode}
/>
{selectedDataSet && renderTable()}
<DraggableTableContext.Provider value={draggableTable}>
{selectedDataSet && renderTable()}
</DraggableTableContext.Provider>
</div>
);
};
Expand Down
24 changes: 15 additions & 9 deletions src/components/portrait-view.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import { ICollection, IProcessedCaseObj, ITableProps } from "../types";
import { DraggagleTableHeader } from "./draggable-table-header";

import css from "./tables.scss";

Expand All @@ -17,30 +18,35 @@ export const PortraitView = (props: ITableProps) => {
<th colSpan={valueCount}>{selectedDataSet.name}</th>
</tr>
<tr className={css[className]}>
<th colSpan={valueCount}>{parentColl.name}</th>
<DraggagleTableHeader
collectionId={parentColl.id}
attrTitle={parentColl.name}
>
{parentColl.name}
</DraggagleTableHeader>
</tr>
{parentColl.cases.map((caseObj, index) => renderRowFromCaseObj(caseObj, index))}
{parentColl.cases.map((caseObj, index) => renderRowFromCaseObj(caseObj.collection.id, caseObj, index))}
</>
);
};

const renderRowFromCaseObj = (caseObj: IProcessedCaseObj, index?: null|number) => {
const renderRowFromCaseObj = (collectionId: number, caseObj: IProcessedCaseObj, index?: null|number) => {
const {children, values} = caseObj;
if (!children.length) {
return (
<tr>{mapCellsFromValues(values)}</tr>
<tr>{mapCellsFromValues(`row-${index}`, values)}</tr>
);
} else {
return (
<>
{index === 0 ?
<tr className={`${css[getClassName(caseObj)]}`}>
{mapHeadersFromValues(values)}
{mapHeadersFromValues(collectionId, `first-row-${index}`, values)}
<th>{showHeaders ? children[0].collection.name : ""}</th>
</tr> : ""
}
<tr className={`${css[getClassName(caseObj)]}`}>
{mapCellsFromValues(values)}
{mapCellsFromValues(`parent-row-${index}`, values)}
<td style={paddingStyle}>
<table style={paddingStyle} className={`${css.subTable} ${css[getClassName(children[0])]}`}>
<tbody>
Expand All @@ -49,13 +55,13 @@ export const PortraitView = (props: ITableProps) => {
return (
<>
<tr key={child.collection.name} className={`${css[getClassName(child)]}`}>
{mapHeadersFromValues(child.values)}
{mapHeadersFromValues(child.collection.id, `child-row-${index}-${i}`, child.values)}
</tr>
{renderRowFromCaseObj(child, i)}
{renderRowFromCaseObj(child.collection.id, child, i)}
</>
);
} else {
return (renderRowFromCaseObj(child, i));
return (renderRowFromCaseObj(child.collection.id, child, i));
}
})}
</tbody>
Expand Down
Loading

0 comments on commit 1ae33df

Please sign in to comment.