diff --git a/package-lock.json b/package-lock.json index 39cd228d..9d1374c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4565,7 +4565,7 @@ "version": "1.57.0", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "playwright": "1.57.0" @@ -7263,7 +7263,7 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -8011,7 +8011,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -10158,7 +10158,7 @@ "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "node-gyp-build": "bin.js", @@ -10647,7 +10647,7 @@ "version": "1.57.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "playwright-core": "1.57.0" @@ -10666,7 +10666,7 @@ "version": "1.57.0", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -10898,7 +10898,6 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10918,7 +10917,6 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", - "dev": true, "license": "MIT", "dependencies": { "scheduler": "^0.27.0" @@ -11285,7 +11283,6 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "dev": true, "license": "MIT" }, "node_modules/scroll-into-view-if-needed": { diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout-client.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout-client.tsx index d1cc5b2e..3714949c 100644 --- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout-client.tsx +++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout-client.tsx @@ -1,6 +1,6 @@ 'use client'; -import React from 'react'; +import React, { useMemo, useCallback } from 'react'; import { PropsWithChildren } from 'react'; import { Layout, Tabs, Badge, Button, Popconfirm, Flex } from 'antd'; import { DeleteOutlined } from '@ant-design/icons'; @@ -21,80 +21,109 @@ interface ListLayoutClientProps { boardDetails: BoardDetails; } -const TabsWrapper: React.FC<{ boardDetails: BoardDetails }> = ({ boardDetails }) => { +// Queue tab label - subscribes to queue context for badge count +const QueueTabLabel = React.memo(() => { + const { queue } = useQueueContext(); + return ( + + Queue + + ); +}); +QueueTabLabel.displayName = 'QueueTabLabel'; + +// Queue tab content - handles queue display and clear functionality +const QueueTabContent = React.memo(({ boardDetails }: { boardDetails: BoardDetails }) => { const { queue, setQueue } = useQueueContext(); - const handleClearQueue = () => { + const handleClearQueue = useCallback(() => { + const itemsCleared = queue.length; setQueue([]); track('Queue Cleared', { boardLayout: boardDetails.layout_name || '', - itemsCleared: queue.length, + itemsCleared, }); - }; + }, [setQueue, boardDetails.layout_name, queue.length]); - const tabItems = [ - { - key: 'queue', - label: ( - - Queue - - ), - children: ( -
- {queue.length > 0 && ( - - - - - - )} -
- -
-
- ), - }, - { - key: 'search', - label: 'Search', - children: ( -
-
- -
- -
- ), - }, - { - key: 'holds', - label: 'Search by Hold', - children: ( -
-
- -
- -
- ), - }, - ]; + return ( +
+ {queue.length > 0 && ( + + + + + + )} +
+ +
+
+ ); +}); +QueueTabContent.displayName = 'QueueTabContent'; + +// Search tab content +const SearchTabContent = React.memo(({ boardDetails }: { boardDetails: BoardDetails }) => ( +
+
+ +
+ +
+)); +SearchTabContent.displayName = 'SearchTabContent'; + +// Holds search tab content +const HoldsTabContent = React.memo(({ boardDetails }: { boardDetails: BoardDetails }) => ( +
+
+ +
+ +
+)); +HoldsTabContent.displayName = 'HoldsTabContent'; + +// TabsWrapper - no longer subscribes to queue context directly +// Child components handle their own queue subscriptions +const TabsWrapper: React.FC<{ boardDetails: BoardDetails }> = ({ boardDetails }) => { + // Memoize tabItems to prevent infinite re-render loop + // Each tab's children are memoized components that manage their own state + const tabItems = useMemo( + () => [ + { + key: 'queue', + label: , + children: , + }, + { + key: 'search', + label: 'Search', + children: , + }, + { + key: 'holds', + label: 'Search by Hold', + children: , + }, + ], + [boardDetails], + ); return ; };