diff --git a/app/javascript/src/admin/components/clustering/BrandSelector.jsx b/app/javascript/src/admin/components/clustering/BrandSelector.jsx
new file mode 100644
index 000000000..7e53bdb54
--- /dev/null
+++ b/app/javascript/src/admin/components/clustering/BrandSelector.jsx
@@ -0,0 +1,38 @@
+import React, { useContext } from "react";
+import Select from "react-select";
+import _ from "lodash";
+
+import { DispatchContext, StateContext } from "../../micro-clusters/GenericApp";
+import { UPDATE_SELECTED_BRANDS } from "./actions";
+import { setInBrandSelector } from "./keyDownListener";
+
+export const BrandSelector = ({ field }) => {
+ const dispatch = useContext(DispatchContext);
+ const { microClusters, selectedBrands } = useContext(StateContext);
+ const values = _.countBy(microClusters.map((c) => c[field]));
+ const options = _.sortBy(
+ _.map(values, (value, key) => ({
+ value: key,
+ label: `${key} (${value})`
+ })),
+ "label"
+ );
+ return (
+
+
+ );
+};
diff --git a/app/javascript/src/admin/components/clustering/LoadingOverlay.jsx b/app/javascript/src/admin/components/clustering/LoadingOverlay.jsx
new file mode 100644
index 000000000..5ab9b9a88
--- /dev/null
+++ b/app/javascript/src/admin/components/clustering/LoadingOverlay.jsx
@@ -0,0 +1,18 @@
+import React, { useContext } from "react";
+
+import { StateContext } from "../../micro-clusters/GenericApp";
+
+export const LoadingOverlay = () => {
+ const { updating } = useContext(StateContext);
+ if (!updating) return null;
+ const style = {
+ position: "fixed",
+ top: 0,
+ left: 0,
+ height: "100%",
+ width: "100%",
+ zIndex: 10,
+ backgroundColor: "rgba(0,0,0,0.5)"
+ };
+ return ;
+};
diff --git a/app/javascript/src/admin/components/clustering/Summary.jsx b/app/javascript/src/admin/components/clustering/Summary.jsx
new file mode 100644
index 000000000..bb9bf91c9
--- /dev/null
+++ b/app/javascript/src/admin/components/clustering/Summary.jsx
@@ -0,0 +1,13 @@
+import React, { useContext } from "react";
+
+import { StateContext } from "../../micro-clusters/GenericApp";
+
+export const Summary = () => {
+ const { microClusters, selectedMicroClusters } = useContext(StateContext);
+ return (
+
+ Total: {microClusters.length} In Selection:{" "}
+ {selectedMicroClusters.length}
+
+ );
+};
diff --git a/app/javascript/src/admin/micro-clusters/App.jsx b/app/javascript/src/admin/micro-clusters/App.jsx
index 78def30c6..8a6ebd40c 100644
--- a/app/javascript/src/admin/micro-clusters/App.jsx
+++ b/app/javascript/src/admin/micro-clusters/App.jsx
@@ -1,111 +1,16 @@
-import React, { useEffect, useReducer, useContext } from "react";
-import Select from "react-select";
-import _ from "lodash";
-
-import { Spinner } from "../components/Spinner";
-import { DisplayMicroClusters } from "./DisplayMicroClusters";
-import { reducer, initalState } from "../components/clustering/reducer";
-import { UPDATE_SELECTED_BRANDS } from "../components/clustering/actions";
-import { setInBrandSelector } from "../components/clustering/keyDownListener";
-import { getMacroClusters } from "./macroClusters";
+import React from "react";
+import { getMacroClusters, updateMacroCluster } from "./macroClusters";
import { getMicroClusters } from "./microClusters";
+import { assignCluster } from "./assignCluster";
-export const StateContext = React.createContext();
-export const DispatchContext = React.createContext();
-
-export const App = () => {
- const [state, dispatch] = useReducer(reducer, initalState);
- const { loadingMacroClusters, loadingMicroClusters } = state;
- useEffect(() => {
- getMicroClusters(dispatch);
- }, []);
- useEffect(() => {
- getMacroClusters(dispatch);
- }, []);
- useEffect(() => {
- if (
- loadingMicroClusters ||
- loadingMacroClusters ||
- state.microClusters.length > 0
- )
- return;
- const intervalId = setInterval(() => {
- getMicroClusters(dispatch);
- }, 30 * 1000);
- return () => {
- clearInterval(intervalId);
- };
- }, [loadingMicroClusters, loadingMacroClusters, state.microClusters.length]);
- if (!loadingMicroClusters && !loadingMacroClusters) {
- return (
-
-
-
-
-
-
-
-
-
-
- );
- } else {
- return ;
- }
-};
-
-const LoadingOverlay = () => {
- const { updating } = useContext(StateContext);
- if (!updating) return null;
- const style = {
- position: "fixed",
- top: 0,
- left: 0,
- height: "100%",
- width: "100%",
- zIndex: 10,
- backgroundColor: "rgba(0,0,0,0.5)"
- };
- return ;
-};
-
-const Summary = () => {
- const { microClusters, selectedMicroClusters } = useContext(StateContext);
- return (
-
- Total: {microClusters.length} In Selection:{" "}
- {selectedMicroClusters.length}
-
- );
-};
+import { GenericApp } from "./GenericApp";
-const BrandSelector = () => {
- const dispatch = useContext(DispatchContext);
- const { microClusters, selectedBrands } = useContext(StateContext);
- const values = _.countBy(microClusters.map((c) => c.simplified_brand_name));
- const options = _.sortBy(
- _.map(values, (value, key) => ({
- value: key,
- label: `${key} (${value})`
- })),
- "label"
- );
- return (
-
-
- );
-};
+export const App = () => (
+
+);
diff --git a/app/javascript/src/admin/micro-clusters/CreateRow.jsx b/app/javascript/src/admin/micro-clusters/CreateRow.jsx
index e3a8ad5fe..400e0b012 100644
--- a/app/javascript/src/admin/micro-clusters/CreateRow.jsx
+++ b/app/javascript/src/admin/micro-clusters/CreateRow.jsx
@@ -1,17 +1,16 @@
import React, { useCallback, useContext, useEffect } from "react";
import _ from "lodash";
import { postRequest, putRequest } from "../../fetch";
-import { StateContext, DispatchContext } from "./App";
+import { StateContext, DispatchContext } from "./GenericApp";
import { groupedInks } from "./groupedInks";
import {
UPDATING,
ADD_MACRO_CLUSTER,
REMOVE_MICRO_CLUSTER
} from "../components/clustering/actions";
-import { assignCluster } from "./assignCluster";
import { keyDownListener } from "../components/clustering/keyDownListener";
-export const CreateRow = ({ afterCreate }) => {
+export const CreateRow = ({ afterCreate, assignCluster }) => {
const { updating, activeCluster } = useContext(StateContext);
const dispatch = useContext(DispatchContext);
const values = computeValues(activeCluster);
@@ -20,9 +19,10 @@ export const CreateRow = ({ afterCreate }) => {
values,
activeCluster.id,
dispatch,
- afterCreate
+ afterCreate,
+ assignCluster
);
- }, [activeCluster.id, afterCreate, dispatch, values]);
+ }, [activeCluster.id, afterCreate, dispatch, values, assignCluster]);
const ignore = () => {
ignoreCluster(activeCluster).then(
dispatch({ type: REMOVE_MICRO_CLUSTER, payload: activeCluster })
@@ -89,7 +89,8 @@ const createMacroClusterAndAssign = (
values,
microClusterId,
dispatch,
- afterCreate
+ afterCreate,
+ assignCluster
) => {
dispatch({ type: UPDATING });
setTimeout(() => {
diff --git a/app/javascript/src/admin/micro-clusters/DisplayMacroClusters.jsx b/app/javascript/src/admin/micro-clusters/DisplayMacroClusters.jsx
index 28bd6dded..69d026368 100644
--- a/app/javascript/src/admin/micro-clusters/DisplayMacroClusters.jsx
+++ b/app/javascript/src/admin/micro-clusters/DisplayMacroClusters.jsx
@@ -4,10 +4,9 @@ import levenshtein from "fast-levenshtein";
import ScrollIntoViewIfNeeded from "react-scroll-into-view-if-needed";
import { matchSorter } from "match-sorter";
-import { assignCluster } from "./assignCluster";
import { CollectedInksList } from "./CollectedInksList";
import { SearchLink } from "../components/clustering/SearchLink";
-import { StateContext, DispatchContext } from "./App";
+import { StateContext, DispatchContext } from "./GenericApp";
import {
ASSIGN_TO_MACRO_CLUSTER,
NEXT_MACRO_CLUSTER,
@@ -20,7 +19,7 @@ import {
} from "../components/clustering/keyDownListener";
import { useCallback } from "react";
-export const DisplayMacroClusters = ({ afterAssign }) => {
+export const DisplayMacroClusters = ({ afterAssign, assignCluster }) => {
const dispatch = useContext(DispatchContext);
useEffect(() => {
return keyDownListener(({ keyCode }) => {
@@ -28,10 +27,12 @@ export const DisplayMacroClusters = ({ afterAssign }) => {
if (keyCode == 75) dispatch({ type: PREVIOUS_MACRO_CLUSTER });
});
}, [dispatch]);
- return ;
+ return (
+
+ );
};
-const MacroClusterRows = ({ afterAssign }) => {
+const MacroClusterRows = ({ afterAssign, assignCluster }) => {
const {
macroClusters,
activeCluster,
@@ -73,6 +74,7 @@ const MacroClusterRows = ({ afterAssign }) => {
key={macroCluster.id}
macroCluster={macroCluster}
afterAssign={afterAssign}
+ assignCluster={assignCluster}
selected={index == selectedMacroClusterIndex}
/>
));
@@ -165,7 +167,12 @@ const stripped = (str) => {
.replace(/\s+/i, "");
};
-const MacroClusterRow = ({ macroCluster, afterAssign, selected }) => {
+const MacroClusterRow = ({
+ macroCluster,
+ afterAssign,
+ selected,
+ assignCluster
+}) => {
const { activeCluster, updating } = useContext(StateContext);
const dispatch = useContext(DispatchContext);
const [showInks, setShowInks] = useState(false);
@@ -181,7 +188,7 @@ const MacroClusterRow = ({ macroCluster, afterAssign, selected }) => {
afterAssign(microCluster);
});
}, 10);
- }, [activeCluster.id, afterAssign, dispatch, macroCluster.id]);
+ }, [activeCluster.id, afterAssign, dispatch, macroCluster.id, assignCluster]);
useEffect(() => {
if (!selected) return;
diff --git a/app/javascript/src/admin/micro-clusters/DisplayMicroCluster.jsx b/app/javascript/src/admin/micro-clusters/DisplayMicroCluster.jsx
index 12c9ad8c1..c69ce7a29 100644
--- a/app/javascript/src/admin/micro-clusters/DisplayMicroCluster.jsx
+++ b/app/javascript/src/admin/micro-clusters/DisplayMicroCluster.jsx
@@ -1,11 +1,11 @@
import React, { useContext } from "react";
-import { StateContext } from "./App";
+import { StateContext } from "./GenericApp";
import { DisplayMacroClusters } from "./DisplayMacroClusters";
import { CollectedInksList } from "./CollectedInksList";
import { CreateRow } from "./CreateRow";
-export const DisplayMicroCluster = ({ afterCreate }) => {
+export const DisplayMicroCluster = ({ afterCreate, assignCluster }) => {
const { activeCluster } = useContext(StateContext);
return (
@@ -18,7 +18,10 @@ export const DisplayMicroCluster = ({ afterCreate }) => {
|
-
+
diff --git a/app/javascript/src/admin/micro-clusters/DisplayMicroClusters.jsx b/app/javascript/src/admin/micro-clusters/DisplayMicroClusters.jsx
index a030faa9d..5e2da76b4 100644
--- a/app/javascript/src/admin/micro-clusters/DisplayMicroClusters.jsx
+++ b/app/javascript/src/admin/micro-clusters/DisplayMicroClusters.jsx
@@ -1,7 +1,7 @@
import React, { useEffect, useContext } from "react";
import { DisplayMicroCluster } from "./DisplayMicroCluster";
-import { DispatchContext, StateContext } from "./App";
+import { DispatchContext, StateContext } from "./GenericApp";
import {
PREVIOUS,
NEXT,
@@ -9,16 +9,18 @@ import {
} from "../components/clustering/actions";
import { keyDownListener } from "../components/clustering/keyDownListener";
import { useCallback } from "react";
-import { updateMacroCluster } from "./macroClusters";
-export const DisplayMicroClusters = () => {
+export const DisplayMicroClusters = ({
+ macroClusterUpdater,
+ assignCluster
+}) => {
const dispatch = useContext(DispatchContext);
const { activeCluster } = useContext(StateContext);
const { prev, next } = useNavigation(dispatch);
const afterAssign = (newClusterData) => {
dispatch({ type: REMOVE_MICRO_CLUSTER, payload: newClusterData });
const id = newClusterData.macro_cluster.id;
- updateMacroCluster(id, dispatch);
+ macroClusterUpdater(id, dispatch);
};
if (activeCluster) {
return (
@@ -27,7 +29,10 @@ export const DisplayMicroClusters = () => {
-
+
diff --git a/app/javascript/src/admin/micro-clusters/GenericApp.jsx b/app/javascript/src/admin/micro-clusters/GenericApp.jsx
new file mode 100644
index 000000000..c2438404d
--- /dev/null
+++ b/app/javascript/src/admin/micro-clusters/GenericApp.jsx
@@ -0,0 +1,68 @@
+import React, { useEffect, useReducer } from "react";
+
+import { Spinner } from "../components/Spinner";
+import { DisplayMicroClusters } from "./DisplayMicroClusters";
+import { reducer, initalState } from "../components/clustering/reducer";
+import { Summary } from "../components/clustering/Summary";
+import { LoadingOverlay } from "../components/clustering/LoadingOverlay";
+import { BrandSelector } from "../components/clustering/BrandSelector";
+
+export const StateContext = React.createContext();
+export const DispatchContext = React.createContext();
+
+export const GenericApp = ({
+ brandSelectorField,
+ microClusterLoader,
+ macroClusterLoader,
+ macroClusterUpdater,
+ assignCluster
+}) => {
+ const [state, dispatch] = useReducer(reducer, initalState);
+ const { loadingMacroClusters, loadingMicroClusters } = state;
+ useEffect(() => {
+ microClusterLoader(dispatch);
+ }, [microClusterLoader]);
+ useEffect(() => {
+ macroClusterLoader(dispatch);
+ }, [macroClusterLoader]);
+ useEffect(() => {
+ if (
+ loadingMicroClusters ||
+ loadingMacroClusters ||
+ state.microClusters.length > 0
+ )
+ return;
+ const intervalId = setInterval(() => {
+ microClusterLoader(dispatch);
+ }, 30 * 1000);
+ return () => {
+ clearInterval(intervalId);
+ };
+ }, [
+ loadingMicroClusters,
+ loadingMacroClusters,
+ state.microClusters.length,
+ microClusterLoader
+ ]);
+ if (!loadingMicroClusters && !loadingMacroClusters) {
+ return (
+
+
+
+
+
+
+ {/*** TODO: DisplayMicroClusters is not yet generic ***/}
+
+ {/*** TODO: DisplayMicroClusters is not yet generic ***/}
+
+
+
+ );
+ } else {
+ return
;
+ }
+};