Browse Source

试验性修改,用户点击按钮后,向图像上添加L注释

dengdx 3 weeks ago
parent
commit
edf9432e7b

+ 41 - 32
src/pages/view/components/FunctionArea.tsx

@@ -1,104 +1,113 @@
 import React from 'react';
 import React from 'react';
 import { Button, Row, Col } from 'antd';
 import { Button, Row, Col } from 'antd';
+import { useDispatch } from 'react-redux';
+import { setAction } from '@/states/view/functionAreaSlice';
 
 
 const FunctionArea = () => {
 const FunctionArea = () => {
+  const dispatch = useDispatch();
+
+  const handleButtonClick = (action: string) => {
+    dispatch(setAction(action));
+    // dispatch(clearAction());
+  };
+
   return (
   return (
     <Row gutter={[8, 8]}>
     <Row gutter={[8, 8]}>
       <Col>
       <Col>
-        <Button>Add L Mark</Button>
+        <Button onClick={() => handleButtonClick('Add L Mark')}>Add L Mark</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Add R Mark</Button>
+        <Button onClick={() => handleButtonClick('Add R Mark')}>Add R Mark</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Delete Selected Mark</Button>
+        <Button onClick={() => handleButtonClick('Delete Selected Mark')}>Delete Selected Mark</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Horizontal Flip</Button>
+        <Button onClick={() => handleButtonClick('Horizontal Flip')}>Horizontal Flip</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Vertical Flip</Button>
+        <Button onClick={() => handleButtonClick('Vertical Flip')}>Vertical Flip</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Rotate Counterclockwise 90°</Button>
+        <Button onClick={() => handleButtonClick('Rotate Counterclockwise 90°')}>Rotate Counterclockwise 90°</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Rotate Clockwise 90°</Button>
+        <Button onClick={() => handleButtonClick('Rotate Clockwise 90°')}>Rotate Clockwise 90°</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Rotate Any Angle</Button>
+        <Button onClick={() => handleButtonClick('Rotate Any Angle')}>Rotate Any Angle</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Crop Image</Button>
+        <Button onClick={() => handleButtonClick('Crop Image')}>Crop Image</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Delete Digital Mask</Button>
+        <Button onClick={() => handleButtonClick('Delete Digital Mask')}>Delete Digital Mask</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Adjust Brightness and Contrast</Button>
+        <Button onClick={() => handleButtonClick('Adjust Brightness and Contrast')}>Adjust Brightness and Contrast</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Crop Selected Area</Button>
+        <Button onClick={() => handleButtonClick('Crop Selected Area')}>Crop Selected Area</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Delete Mask</Button>
+        <Button onClick={() => handleButtonClick('Delete Mask')}>Delete Mask</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Image Comparison</Button>
+        <Button onClick={() => handleButtonClick('Image Comparison')}>Image Comparison</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Invert Contrast</Button>
+        <Button onClick={() => handleButtonClick('Invert Contrast')}>Invert Contrast</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>1x1 Layout</Button>
+        <Button onClick={() => handleButtonClick('1x1 Layout')}>1x1 Layout</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>1x2 Layout</Button>
+        <Button onClick={() => handleButtonClick('1x2 Layout')}>1x2 Layout</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>2x2 Layout</Button>
+        <Button onClick={() => handleButtonClick('2x2 Layout')}>2x2 Layout</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>4x4 Layout</Button>
+        <Button onClick={() => handleButtonClick('4x4 Layout')}>4x4 Layout</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Magnifier</Button>
+        <Button onClick={() => handleButtonClick('Magnifier')}>Magnifier</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Fit Size</Button>
+        <Button onClick={() => handleButtonClick('Fit Size')}>Fit Size</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Original Size</Button>
+        <Button onClick={() => handleButtonClick('Original Size')}>Original Size</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Zoom Image</Button>
+        <Button onClick={() => handleButtonClick('Zoom Image')}>Zoom Image</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Reset Cursor</Button>
+        <Button onClick={() => handleButtonClick('Reset Cursor')}>Reset Cursor</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Pan</Button>
+        <Button onClick={() => handleButtonClick('Pan')}>Pan</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Invert Image</Button>
+        <Button onClick={() => handleButtonClick('Invert Image')}>Invert Image</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Reset Image</Button>
+        <Button onClick={() => handleButtonClick('Reset Image')}>Reset Image</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Snapshot</Button>
+        <Button onClick={() => handleButtonClick('Snapshot')}>Snapshot</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Advanced Processing</Button>
+        <Button onClick={() => handleButtonClick('Advanced Processing')}>Advanced Processing</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Musician</Button>
+        <Button onClick={() => handleButtonClick('Musician')}>Musician</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>Image Measurement</Button>
+        <Button onClick={() => handleButtonClick('Image Measurement')}>Image Measurement</Button>
       </Col>
       </Col>
       <Col>
       <Col>
-        <Button>More</Button>
+        <Button onClick={() => handleButtonClick('More')}>More</Button>
       </Col>
       </Col>
     </Row>
     </Row>
   );
   );

+ 251 - 47
src/pages/view/components/viewers/stack.image.viewer.tsx

@@ -1,21 +1,97 @@
 import React, { useEffect, useRef } from 'react';
 import React, { useEffect, useRef } from 'react';
 import * as cornerstone from '@cornerstonejs/core';
 import * as cornerstone from '@cornerstonejs/core';
+import type { Types } from '@cornerstonejs/core';
 import * as cornerstoneTools from '@cornerstonejs/tools';
 import * as cornerstoneTools from '@cornerstonejs/tools';
 import * as cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';
 import * as cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';
-// import createImageIdsAndCacheMetaData from '../../createImageIdsAndCacheMetaData';
-import store from '@/states/store';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/states/store';
 import { SystemMode } from '@/states/systemModeSlice';
 import { SystemMode } from '@/states/systemModeSlice';
+import store from '@/states/store';
+import { clearAction } from '@/states/view/functionAreaSlice';
+import { useDispatch } from 'react-redux';
+
+const { PanTool, WindowLevelTool, StackScrollTool, ZoomTool, LabelTool, ToolGroupManager, Enums: csToolsEnums } = cornerstoneTools;
+const { MouseBindings } = csToolsEnums;
+let toolGroup: cornerstoneTools.Types.IToolGroup;
+let currentViewportId: string;
+
+function registerTools(viewportId, renderingEngineId) {
+  // Add tools to Cornerstone3D
+  cornerstoneTools.addTool(PanTool);
+  cornerstoneTools.addTool(WindowLevelTool);
+  cornerstoneTools.addTool(StackScrollTool);
+  cornerstoneTools.addTool(ZoomTool);
+  cornerstoneTools.addTool(LabelTool);
+  // Define a tool group
+  const toolGroupId = 'STACK_TOOL_GROUP_ID';
+  const toolGroupTmp = ToolGroupManager.createToolGroup(toolGroupId);
+  if (!toolGroupTmp) {
+    return;
+  }
+  toolGroup = toolGroupTmp;
+  // Add tools to the tool group
+  toolGroup.addTool(PanTool.toolName);
+  toolGroup.addTool(WindowLevelTool.toolName);
+  toolGroup.addTool(StackScrollTool.toolName);
+  toolGroup.addTool(ZoomTool.toolName);
+  toolGroup.addTool(LabelTool.toolName);
+
+  // Set the LabelTool as active
+  // toolGroup.setToolActive(LabelTool.toolName, {
+  //   bindings: [
+  //     {
+  //       mouseButton: MouseBindings.Primary, // Left Click
+  //     },
+  //   ],
+  // });
+  // Set the initial state of the tools
+  // toolGroup.setToolActive(WindowLevelTool.toolName, {
+  //   bindings: [
+  //     {
+  //       mouseButton: MouseBindings.Primary, // Left Click
+  //     },
+  //   ],
+  // });
+
+  toolGroup.setToolActive(PanTool.toolName, {
+    bindings: [
+      {
+        mouseButton: MouseBindings.Auxiliary, // Middle Click
+      },
+    ],
+  });
+
+  toolGroup.setToolActive(ZoomTool.toolName, {
+    bindings: [
+      {
+        mouseButton: MouseBindings.Secondary, // Right Click
+      },
+    ],
+  });
+
+  toolGroup.setToolActive(StackScrollTool.toolName, {
+    bindings: [
+      {
+        mouseButton: MouseBindings.Wheel, // Mouse Wheel
+      },
+    ],
+  });
+
+
+
+  toolGroup.addViewport(viewportId, renderingEngineId);
+}
 
 
 const StackViewer = ({ imageIndex }) => {
 const StackViewer = ({ imageIndex }) => {
   const elementRef = useRef<HTMLDivElement>(null);
   const elementRef = useRef<HTMLDivElement>(null);
+  const action = useSelector((state: RootState) => state.functionArea.action);
+  const dispatch = useDispatch();
 
 
   useEffect(() => {
   useEffect(() => {
     const setup = async () => {
     const setup = async () => {
       // 初始化 Cornerstone
       // 初始化 Cornerstone
       cornerstone.init();
       cornerstone.init();
       cornerstoneTools.init();
       cornerstoneTools.init();
-      // 注册加载器
-      // cornerstoneDICOMImageLoader.init();
       const state = store.getState();
       const state = store.getState();
       console.log(`当前系统模式:${state.systemMode.mode}`);
       console.log(`当前系统模式:${state.systemMode.mode}`);
 
 
@@ -24,19 +100,15 @@ const StackViewer = ({ imageIndex }) => {
           ? state.product.guest
           ? state.product.guest
           : state.userInfo.token;
           : state.userInfo.token;
       console.log(`token stack.image.viewer: ${token}`);
       console.log(`token stack.image.viewer: ${token}`);
-      // const { productName, language, source } = state.product;
 
 
       cornerstoneDICOMImageLoader.init({
       cornerstoneDICOMImageLoader.init({
         maxWebWorkers: navigator.hardwareConcurrency || 1,
         maxWebWorkers: navigator.hardwareConcurrency || 1,
         errorInterceptor: (error) => {
         errorInterceptor: (error) => {
           if (error.status === 401) {
           if (error.status === 401) {
             console.error('Authentication failed. Please refresh the token.');
             console.error('Authentication failed. Please refresh the token.');
-            // Optionally, trigger token refresh
           }
           }
           console.error(`请求dcm文件出错:${error}`);
           console.error(`请求dcm文件出错:${error}`);
         },
         },
-        //startWebWorkersOnDemand: true,
-
         beforeSend: (xhr, imageId, defaultHeaders) => {
         beforeSend: (xhr, imageId, defaultHeaders) => {
           return {
           return {
             ...defaultHeaders,
             ...defaultHeaders,
@@ -47,61 +119,193 @@ const StackViewer = ({ imageIndex }) => {
           };
           };
         },
         },
       });
       });
-      // // Get Cornerstone imageIds and fetch metadata into RAM
-      // const imageIds = await createImageIdsAndCacheMetaData({
-      //   StudyInstanceUID:
-      //     '1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463',
-      //   SeriesInstanceUID:
-      //     '1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561',
-      //   wadoRsRoot: 'https://d14fa38qiwhyfd.cloudfront.net/dicomweb',
-      // });
+
       // Instantiate a rendering engine
       // Instantiate a rendering engine
       const renderingEngineId = 'myRenderingEngine';
       const renderingEngineId = 'myRenderingEngine';
-      const renderingEngine = new cornerstone.RenderingEngine(
-        renderingEngineId
-      );
+      const renderingEngine = new cornerstone.RenderingEngine(renderingEngineId);
 
 
       const viewportId = 'CT_AXIAL_STACK';
       const viewportId = 'CT_AXIAL_STACK';
-
+      currentViewportId = viewportId;
       const viewportInput: cornerstone.Types.PublicViewportInput = {
       const viewportInput: cornerstone.Types.PublicViewportInput = {
         viewportId,
         viewportId,
-        // @ts-expect-error why error ?
-        element: elementRef.current,
+        element: elementRef.current!,
         type: cornerstone.Enums.ViewportType.STACK,
         type: cornerstone.Enums.ViewportType.STACK,
       };
       };
 
 
       renderingEngine.enableElement(viewportInput);
       renderingEngine.enableElement(viewportInput);
+      registerTools(viewportId, renderingEngineId);
+
       // Get the stack viewport that was created
       // Get the stack viewport that was created
-      const viewport = renderingEngine.getViewport(
-        viewportId
-      ) as cornerstone.Types.IStackViewport;
-      // 给定一个dcm文件路径,加载并显示出来
-      const imageId1 =
-        'dicomweb:https://ohif-assets-new.s3.us-east-1.amazonaws.com/ACRIN-Regular/CT+CT+IMAGES/CT000000.dcm';
-      const imageId2 =
-        'dicomweb:https://ohif-assets-new.s3.us-east-1.amazonaws.com/ACRIN-Regular/CT+CT+IMAGES/CT000005.dcm';
-      const imageId3 =
-        'dicomweb:https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm';
-      const imageId4 =
-        'dicomweb:http://192.168.11.12:6001/dr/api/v1/auth/image/dcm/DemoImage.dcm';
-      const imageId5 =
-        'dicomweb:http://localhost:10086/dr/api/v1/pub/dcm/DemoImage.dcm';
-      const imageId6 =
-        'dicomweb:http://localhost:10086/dr/api/v1/auth/image/dcm/DemoImage.dcm';
-      const imageId7 =
-        'dicomweb:http://localhost:10086/dr/api/v1/pub/dcm/DemoImage.dcm';
-      imageIndex = 5;
-      viewport.setStack(
-        [imageId1, imageId2, imageId3, imageId4, imageId5, imageId6, imageId7],
-        imageIndex
-      );
-      // viewport.setStack(imageIds, imageIndex);
+      const viewport = renderingEngine.getViewport(viewportId) as cornerstone.Types.IStackViewport;
 
 
+      // 给定一个dcm文件路径,加载并显示出来
+      const imageId1 = 'dicomweb:https://ohif-assets-new.s3.us-east-1.amazonaws.com/ACRIN-Regular/CT+CT+IMAGES/CT000000.dcm';
+      const imageId2 = 'dicomweb:https://ohif-assets-new.s3.us-east-1.amazonaws.com/ACRIN-Regular/CT+CT+IMAGES/CT000005.dcm';
+      const imageId3 = 'dicomweb:https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm';
+      const imageId4 = 'dicomweb:http://localhost:10086/dr/api/v1/auth/image/dcm/DemoImage.dcm';
+      const imageId5 = 'dicomweb:http://localhost:10086/dr/api/v1/pub/dcm/DemoImage.dcm';
+      const imageId6 = 'dicomweb:http://localhost:10086/dr/api/v1/auth/image/dcm/DemoImage.dcm';
+      const imageId7 = 'dicomweb:http://localhost:10086/dr/api/v1/pub/dcm/DemoImage.dcm';
+      imageIndex = 6;
+      await viewport.setStack([imageId1, imageId2, imageId3, imageId4, imageId5, imageId6, imageId7], imageIndex);
       viewport.render();
       viewport.render();
     };
     };
 
 
     setup();
     setup();
-  }, [elementRef]);
+  }, [elementRef, imageIndex]);
+
+  useEffect(() => {
+    if (action) {
+      // Handle the action
+      switch (action) {
+        case 'Add L Mark':
+          // Implement the logic to add an L mark
+          console.log('Adding L Mark');
+          toolGroup.setToolActive(LabelTool.toolName, {
+            bindings: [
+              // {
+              //   mouseButton: MouseBindings.Primary, // Left Click
+              // },
+            ],
+          });
+          const position: Types.Point3 = [100, 100, 0]; // Example position
+          const text = 'L'; // Predefined text
+          LabelTool.hydrate(currentViewportId, position, text);
+          toolGroup.setToolPassive(LabelTool.toolName, { removeAllBindings: true });
+          // const enabledElement = cornerstone.getEnabledElementByViewportId(currentViewportId);
+          // cursors.elementCursor.resetElementCursor(elementRef.current as HTMLDivElement);
+          break;
+        case 'Add R Mark':
+          // Implement the logic to add an R mark
+          console.log('Adding R Mark');
+          break;
+        case 'Delete Selected Mark':
+          // Implement the logic to delete the selected mark
+          console.log('Deleting Selected Mark');
+          break;
+        case 'Horizontal Flip':
+          // Implement the logic to flip the image horizontally
+          console.log('Flipping Image Horizontally');
+          break;
+        // case 'Vertical Flip':
+        //   {          // Implement the logic to flip the image vertically
+        //     console.log('Flipping Image Vertically');
+        //   }
+        //   break;
+        case 'Rotate Counterclockwise 90':
+          // Implement the logic to rotate the image counterclockwise
+          console.log('Rotating Image Counterclockwise 90°');
+          break;
+        case 'Rotate Clockwise 90':
+          // Implement the logic to rotate the image clockwise
+          console.log('Rotating Image Clockwise 90°');
+          break;
+        case 'Rotate Any Angle':
+          // Implement the logic to rotate the image by any angle
+          console.log('Rotating Image by Any Angle');
+          break;
+        case 'Crop Image':
+          // Implement the logic to crop the image
+          console.log('Cropping Image');
+          break;
+        case 'Delete Digital Mask':
+          // Implement the logic to delete the digital mask
+          console.log('Deleting Digital Mask');
+          break;
+        case 'Adjust Brightness and Contrast':
+          // Implement the logic to adjust brightness and contrast
+          console.log('Adjusting Brightness and Contrast');
+          break;
+        case 'Crop Selected Area':
+          // Implement the logic to crop the selected area
+          console.log('Cropping Selected Area');
+          break;
+        case 'Delete Mask':
+          // Implement the logic to delete the mask
+          console.log('Deleting Mask');
+          break;
+        case 'Image Comparison':
+          // Implement the logic for image comparison
+          console.log('Comparing Images');
+          break;
+        case 'Invert Contrast':
+          // Implement the logic to invert the contrast
+          console.log('Inverting Contrast');
+          break;
+        case '1x1 Layout':
+          // Implement the logic for 1x1 layout
+          console.log('Setting 1x1 Layout');
+          break;
+        case '1x2 Layout':
+          // Implement the logic for 1x2 layout
+          console.log('Setting 1x2 Layout');
+          break;
+        case '2x2 Layout':
+          // Implement the logic for 2x2 layout
+          console.log('Setting 2x2 Layout');
+          break;
+        case '4x4 Layout':
+          // Implement the logic for 4x4 layout
+          console.log('Setting 4x4 Layout');
+          break;
+        case 'Magnifier':
+          // Implement the logic for magnifier
+          console.log('Activating Magnifier');
+          break;
+        case 'Fit Size':
+          // Implement the logic to fit the image size
+          console.log('Fitting Image Size');
+          break;
+        case 'Original Size':
+          // Implement the logic to set the image to original size
+          console.log('Setting Image to Original Size');
+          break;
+        case 'Zoom Image':
+          // Implement the logic to zoom the image
+          console.log('Zooming Image');
+          break;
+        case 'Reset Cursor':
+          // Implement the logic to reset the cursor
+          console.log('Resetting Cursor');
+          break;
+        case 'Pan':
+          // Implement the logic to pan the image
+          console.log('Panning Image');
+          break;
+        case 'Invert Image':
+          // Implement the logic to invert the image
+          console.log('Inverting Image');
+          break;
+        case 'Reset Image':
+          // Implement the logic to reset the image
+          console.log('Resetting Image');
+          break;
+        case 'Snapshot':
+          // Implement the logic to take a snapshot
+          console.log('Taking Snapshot');
+          break;
+        case 'Advanced Processing':
+          // Implement the logic for advanced processing
+          console.log('Performing Advanced Processing');
+          break;
+        case 'Musician':
+          // Implement the logic for musician
+          console.log('Activating Musician');
+          break;
+        case 'Image Measurement':
+          // Implement the logic for image measurement
+          console.log('Measuring Image');
+          break;
+        case 'More':
+          // Implement the logic for more options
+          console.log('Showing More Options');
+          break;
+        default:
+          break;
+      }
+      dispatch(clearAction());//清理后可连续同一个action触发响应
+    }
+  }, [action]);
 
 
   return (
   return (
     <div
     <div

+ 2 - 0
src/states/store.ts

@@ -11,6 +11,7 @@ import examWorksCacheReducer from './exam/examWorksCacheSlice';
 import bodyPositionListReducer from './exam/bodyPositionListSlice';
 import bodyPositionListReducer from './exam/bodyPositionListSlice';
 import bodyPositionDetailReducer from './exam/bodyPositionDetailSlice';
 import bodyPositionDetailReducer from './exam/bodyPositionDetailSlice';
 import aprReducer from './exam/aprSlice';
 import aprReducer from './exam/aprSlice';
+import functionAreaReducer from './view/functionAreaSlice';
 import {
 import {
   workEntitiesSlice,
   workEntitiesSlice,
   workFiltersSlice,
   workFiltersSlice,
@@ -33,6 +34,7 @@ const store = configureStore({
     bodyPositionList: bodyPositionListReducer,
     bodyPositionList: bodyPositionListReducer,
     bodyPositionDetail: bodyPositionDetailReducer,
     bodyPositionDetail: bodyPositionDetailReducer,
     apr: aprReducer,
     apr: aprReducer,
+    functionArea: functionAreaReducer,
     workEntities: workEntitiesSlice.reducer,
     workEntities: workEntitiesSlice.reducer,
     workFilters: workFiltersSlice.reducer,
     workFilters: workFiltersSlice.reducer,
     workPagination: workPaginationSlice.reducer,
     workPagination: workPaginationSlice.reducer,

+ 25 - 0
src/states/view/functionAreaSlice.ts

@@ -0,0 +1,25 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+
+interface FunctionAreaState {
+  action: string | null;
+}
+
+const initialState: FunctionAreaState = {
+  action: null,
+};
+
+const functionAreaSlice = createSlice({
+  name: 'functionArea',
+  initialState,
+  reducers: {
+    setAction: (state, action: PayloadAction<string>) => {
+      state.action = action.payload;
+    },
+    clearAction: (state) => {
+      state.action = null;
+    },
+  },
+});
+
+export const { setAction, clearAction } = functionAreaSlice.actions;
+export default functionAreaSlice.reducer;