diff --git a/Binner/Binner.Web/ClientApp/src/common/UserTokenType.js b/Binner/Binner.Web/ClientApp/src/common/UserTokenType.js
index 2f69471e..1ce4e5c1 100644
--- a/Binner/Binner.Web/ClientApp/src/common/UserTokenType.js
+++ b/Binner/Binner.Web/ClientApp/src/common/UserTokenType.js
@@ -1,3 +1,4 @@
export const UserTokenType = {
KiCadApiToken: { value: 5, name: 'KiCad Api', icon: 'microchip', description: 'Link KiCad to Binner using an HTTP Library' },
+ BinnerBinApiToken: { value: 6, name: 'Binner Bin Api', icon: 'microchip', description: 'Link a Binner Bin' },
};
\ No newline at end of file
diff --git a/Binner/Binner.Web/ClientApp/src/components/PartsGrid2Memoized.js b/Binner/Binner.Web/ClientApp/src/components/PartsGrid2Memoized.js
index cbe33e42..32447422 100644
--- a/Binner/Binner.Web/ClientApp/src/components/PartsGrid2Memoized.js
+++ b/Binner/Binner.Web/ClientApp/src/components/PartsGrid2Memoized.js
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useCallback, useMemo } from "react";
+import React, { useState, useEffect, useCallback, useMemo, useRef } from "react";
import { useNavigate, useLocation, useSearchParams } from "react-router-dom";
import { useTranslation, Trans } from "react-i18next";
import { createMedia } from "@artsy/fresnel";
@@ -69,6 +69,11 @@ export default function PartsGrid2Memoized({
return result;
};
+ const [showPopup, setShowPopup] = useState(false);
+ const [popupContent, setPopupContent] = useState(null);
+ const [partToLocate, setPartToLocate] = useState("");
+ let locationKeepaliveTimer = useRef(null);
+
const [_parts, setParts] = useState(parts);
const [_page, setPage] = useState(page);
const [pageSize, setPageSize] = useState(getViewPreference('pageSize') || 25);
@@ -130,6 +135,7 @@ export default function PartsGrid2Memoized({
useEffect(() => {
setDisabledPartIds(disabledPartIds);
+ stopLocatingPart();
}, [disabledPartIds]);
const handlePageChange = (e, control) => {
@@ -143,6 +149,70 @@ export default function PartsGrid2Memoized({
window.open(url, "_blank");
};
+ const locateKeepalive = async (part) => {
+ // update the location with our partnumber
+ return fetchApi(`/api/highlight/update?partNumber=${encodeURIComponent(part)}`, { method: "POST" });
+ }
+
+ const startLocatingPart = async (e, part) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ // get the partnumber we can use to locate the part
+ const p = part.partNumber.trim();
+
+ // set the part to locate for the modal
+ setPartToLocate(p);
+
+ // clear any keepalives
+ if (locationKeepaliveTimer.current) {
+ clearInterval(locationKeepaliveTimer.current);
+ }
+
+ // do a keepalive straight away to check if we have a good
+ // response and for the color
+ const res = await locateKeepalive(p);
+
+ // check for a 200 response
+ if (res?.responseObject?.status === 200 && res?.data?.color !== null) {
+ // send a locate to the server every second
+ locationKeepaliveTimer.current = setInterval(() => {
+ locateKeepalive(p);
+ }, 2500);
+
+ // get the color we need to show
+ const result = res.data.color;
+
+ // show the popup
+ setPopupContent({
+ title: "Part Location",
+ footerColor: result,
+ message: (
+ <>
+
Part is now highlighted in {result}.
+ Press close to stop highlighting {part.partNumber}
+ >
+ )
+ });
+ setShowPopup(true);
+ }
+ };
+
+ const stopLocatingPart = async () => {
+ if (locationKeepaliveTimer.current) {
+ clearInterval(locationKeepaliveTimer.current);
+ locationKeepaliveTimer.current = null;
+
+ // stop the highlighting straight away when we cancel
+ if (partToLocate !== undefined) {
+ fetchApi(`/api/highlight/stop?partNumber=${encodeURIComponent(partToLocate)}`, { method: "POST" });
+ }
+
+ setShowPopup(false);
+ setPartToLocate("")
+ }
+ }
+
const handlePrintLabel = async (e, part) => {
e.preventDefault();
e.stopPropagation();
@@ -344,6 +414,7 @@ export default function PartsGrid2Memoized({
case 'actions':
return {...def, Header: , columnDefType: 'display', Cell: ({row}) => (
<>
+ {