[{"data":1,"prerenderedAt":3724},["ShallowReactive",2],{"blog-\u002Fblog\u002F2025\u002F06\u002Fbuilding-andon-task-manager-dashboard-with-ff":3},{"id":4,"title":5,"body":6,"description":3711,"extension":3712,"meta":3713,"navigation":462,"path":3720,"seo":3721,"stem":3722,"__hash__":3723},"blog\u002Fblog\u002F2025\u002F06\u002Fbuilding-andon-task-manager-dashboard-with-ff.md","Part 2: Building an Andon Task Manager with FlowFuse",{"type":7,"value":8,"toc":3693},"minimark",[9,19,34,39,42,73,82,91,96,99,164,167,170,290,301,308,311,314,319,329,336,339,342,376,1216,1221,1224,1232,1241,1250,1253,1256,1259,1262,1273,1278,1281,1330,1339,1346,1350,1353,1356,1370,1375,1527,1530,1553,1558,1616,1619,1628,1636,1650,1659,1663,1666,1902,1911,1947,1956,1960,1963,2086,2095,2098,2101,2120,2502,2565,2571,2592,2601,2610,2613,2627,2795,2804,2808,2948,2968,2980,2983,2990,3088,3097,3100,3103,3234,3470,3521,3530,3537,3573,3589,3600,3625,3643,3659,3668,3671,3680,3684,3687,3690],[10,11,12,13,18],"p",{},"In ",[14,15,17],"a",{"href":16},"\u002Fblog\u002F2025\u002F05\u002Fbuilding-andon-task-manager-with-ff\u002F","Part 1",", we introduced the concept of an Andon Task Manager—designed to streamline issue reporting and resolution on the factory floor—and outlined the system’s key features, user roles, and dashboard layout.",[10,20,21,22,28,29,33],{},"In this part 2, we move from planning to implementation. The focus now shifts to building the actual system using ",[14,23,27],{"href":24,"rel":25},"https:\u002F\u002Fdashboard.flowfuse.com",[26],"nofollow","FlowFuse Dashboard"," (Node-RED Dashboard 2.0), hosted on the FlowFuse platform. We will begin by developing the ",[30,31,32],"strong",{},"Lines view"," for regular users, along with a line selection menu. The Department View and Admin interface will follow in a later part of the series.",[35,36,38],"h2",{"id":37},"getting-started","Getting Started",[10,40,41],{},"To simplify the development process, the implementation is divided into the following key sections:",[43,44,45,49,52,55,58,61,64,67,70],"ul",{},[46,47,48],"li",{},"Initialize SQLite Database",[46,50,51],{},"Seed Demo Data: Departments & Lines",[46,53,54],{},"Build Line Selection Menu",[46,56,57],{},"Enable URL-Based Dashboard Access",[46,59,60],{},"Create Live Request Fetch Flow (Per Line)",[46,62,63],{},"Render Request Data in a Table",[46,65,66],{},"Setting Up Visual Alerts and Timestamp Formatting",[46,68,69],{},"Highlight Requests with CSS & Add Buttons to Update Request Status",[46,71,72],{},"Create New Request Submission Flow",[10,74,75,76,81],{},"Before proceeding, a basic understanding of Node-RED is recommended. If you are new to Node-RED, consider going through this ",[14,77,80],{"href":78,"rel":79},"https:\u002F\u002Fnode-red-academy.learnworlds.com\u002Fcourse\u002Fnode-red-getting-started",[26],"free Node-RED Fundamentals Course"," to get started.",[83,84,85],"blockquote",{},[10,86,87,90],{},[30,88,89],{},"Tip:"," Organize your flows into clearly defined groups. For reference, images of each flow are provided. Please use the exact names given to each flow—this will help ensure consistency and make it easier to navigate back to specific flows when referenced later.",[92,93,95],"h3",{"id":94},"prerequisites","Prerequisites",[10,97,98],{},"Before you begin building the Andon Task Manager with FlowFuse, make sure you have the following:",[43,100,101,118,124,135,144,155],{},[46,102,103,106,107,112,113,117],{},[30,104,105],{},"Running FlowFuse Instance:"," Make sure you have a FlowFuse instance set up and running. If you don't have an account, check out the ",[14,108,111],{"href":109,"rel":110},"https:\u002F\u002Fapp.flowfuse.com\u002Faccount\u002Fcreate",[26],"free trial"," and ",[14,114,116],{"href":115},"\u002Fdocs\u002Fuser\u002Fintroduction\u002F#creating-a-node-red-instance","learn"," how to create an instance.",[46,119,120,123],{},[30,121,122],{},"@flowfuse\u002Fnode-red-dashboard:"," Ensure you have FlowFuse Dashboard (also known as Node-RED Dashboard 2.0 in the community) installed.",[46,125,126,129,130,134],{},[30,127,128],{},"SQLite Contrib Node:"," Install ",[131,132,133],"code",{},"node-red-contrib-sqlite"," to handle local data storage.",[46,136,137,129,140,143],{},[30,138,139],{},"FlowFuse Multi-user Andon:",[131,141,142],{},"@flowfuse\u002Fnode-red-dashboard-2-user-addon"," to enable multi-user support.",[46,145,146,149,150,154],{},[30,147,148],{},"Enable FlowFuse User Authentication:"," ",[14,151,153],{"href":152},"\u002Fblog\u002F2024\u002F04\u002Fdisplaying-logged-in-users-on-dashboard\u002F#enabling-flowfuse-user-authentication","Enable FlowFuse User Authentication"," on your FlowFuse instance.",[46,156,157,129,160,163],{},[30,158,159],{},"Moment Contrib Node:",[131,161,162],{},"node-red-contrib-moment"," for date and time formatting.",[92,165,48],{"id":166},"initialize-sqlite-database",[10,168,169],{},"The first step is to set up a database to store requests and their updates.",[171,172,173,180,190,201,277,283],"ol",{},[46,174,175,176,179],{},"Drag an ",[30,177,178],{},"Inject"," node onto the canvas and configure it to trigger on Deploy, after a delay of 0.1 seconds.",[46,181,175,182,185,186,189],{},[30,183,184],{},"SQLite"," node onto the canvas. Double-click and click the ",[30,187,188],{},"+"," icon to add a new database configuration.",[46,191,192,193,196,197,200],{},"Give the database a name and set the mode to ",[30,194,195],{},"Read-Write-Create",". Click ",[30,198,199],{},"Add"," to save.",[46,202,203,204,207,208,211,212],{},"Set ",[30,205,206],{},"SQL Query"," to ",[30,209,210],{},"Fixed statement"," and enter:",[213,214,219],"pre",{"className":215,"code":216,"language":217,"meta":218,"style":218},"language-sql shiki shiki-themes github-light github-dark","CREATE TABLE IF NOT EXISTS requests (\n  rowid INTEGER PRIMARY KEY,\n  line TEXT NOT NULL,\n  support TEXT NOT NULL,\n  requested TEXT NOT NULL,\n  acknowledged TEXT,\n  resolved TEXT,\n  notes TEXT\n);\n","sql","",[131,220,221,229,235,241,247,253,259,265,271],{"__ignoreMap":218},[222,223,226],"span",{"class":224,"line":225},"line",1,[222,227,228],{},"CREATE TABLE IF NOT EXISTS requests (\n",[222,230,232],{"class":224,"line":231},2,[222,233,234],{},"  rowid INTEGER PRIMARY KEY,\n",[222,236,238],{"class":224,"line":237},3,[222,239,240],{},"  line TEXT NOT NULL,\n",[222,242,244],{"class":224,"line":243},4,[222,245,246],{},"  support TEXT NOT NULL,\n",[222,248,250],{"class":224,"line":249},5,[222,251,252],{},"  requested TEXT NOT NULL,\n",[222,254,256],{"class":224,"line":255},6,[222,257,258],{},"  acknowledged TEXT,\n",[222,260,262],{"class":224,"line":261},7,[222,263,264],{},"  resolved TEXT,\n",[222,266,268],{"class":224,"line":267},8,[222,269,270],{},"  notes TEXT\n",[222,272,274],{"class":224,"line":273},9,[222,275,276],{},");\n",[46,278,279,280,282],{},"Connect the ",[30,281,178],{}," node to the SQLite node.",[46,284,285,286,289],{},"Click ",[30,287,288],{},"Deploy",".",[10,291,292,297],{},[293,294],"img",{"alt":295,"dataZoomable":218,"src":296},"Node-RED flow showing an Inject node connected to an SQLite node to create a requests table in the database.","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Fsqlite-create-flow.png",[298,299,300],"em",{},"Flow to initialize the SQLite database and create the requests table.",[10,302,303,304,307],{},"Once deployed, this will create the SQLite database and ",[131,305,306],{},"requests"," table if it does not already exist.",[92,309,51],{"id":310},"seed-demo-data:-departments-&-lines",[10,312,313],{},"As discussed in the planning section, only the admin role will have the ability to add new departments and lines. Since the admin feature is not yet available, we will populate demo data using a predefined flow to allow ourself to test the application while the standard user interface is being developed.",[315,316],"render-flow",{":height":317,"flow":318},"300","W3siaWQiOiJkNWRkOWJmYzU5OTM3NGQ0IiwidHlwZSI6InRhYiIsImxhYmVsIjoiUG9wdWxhdGUgd2l0aCBkZW1vIHN1cHBvcnQgYXJlYXMgYW5kIGxpbmVzIiwiZGlzYWJsZWQiOmZhbHNlLCJpbmZvIjoiIiwiZW52IjpbXX0seyJpZCI6IjY0NzE4MjRlMjRmMTg5MzkiLCJ0eXBlIjoiZ3JvdXAiLCJ6IjoiZDVkZDliZmM1OTkzNzRkNCIsIm5hbWUiOiJBZGQgZGVtbyBwcm9kdWN0aW9uIGxpbmUgYW5kIGRlcGFydG1lbnQiLCJzdHlsZSI6eyJsYWJlbCI6dHJ1ZX0sIm5vZGVzIjpbIjM0YzczM2I0ODBlNDFhMTMiLCI0ZTQ2YWUyNWQxOThmZDFiIiwiODgyZTI5ZDJkY2M5MzI2YyIsIjM5OWJkMTU5ZjQ2Yzc0MjciLCI1MDAxYzFjZGY2NjYxZjg4Il0sIngiOjM0LCJ5IjozOSwidyI6MTMwMiwiaCI6MTIyfSx7ImlkIjoiMzRjNzMzYjQ4MGU0MWExMyIsInR5cGUiOiJpbmplY3QiLCJ6IjoiZDVkZDliZmM1OTkzNzRkNCIsImciOiI2NDcxODI0ZTI0ZjE4OTM5IiwibmFtZSI6IkFkZCBwcm9kdWN0aW9uIGxpbmVzIGFuZCBkZXBhcnRtZW50IGZvciB0ZXN0aW5nIiwicHJvcHMiOltdLCJyZXBlYXQiOiIiLCJjcm9udGFiIjoiIiwib25jZSI6dHJ1ZSwib25jZURlbGF5IjowLjEsInRvcGljIjoiIiwieCI6MjYwLCJ5IjoxMDAsIndpcmVzIjpbWyI0ZTQ2YWUyNWQxOThmZDFiIiwiMzk5YmQxNTlmNDZjNzQyNyJdXX0seyJpZCI6IjRlNDZhZTI1ZDE5OGZkMWIiLCJ0eXBlIjoic3dpdGNoIiwieiI6ImQ1ZGQ5YmZjNTk5Mzc0ZDQiLCJnIjoiNjQ3MTgyNGUyNGYxODkzOSIsIm5hbWUiOiJJcyBsaW5lcyB1bmRlZmluZWQ\u002FIiwicHJvcGVydHkiOiIjOihwZXJzaXN0ZW50KTo6bGluZXMiLCJwcm9wZXJ0eVR5cGUiOiJnbG9iYWwiLCJydWxlcyI6W3sidCI6ImlzdHlwZSIsInYiOiJ1bmRlZmluZWQiLCJ2dCI6InVuZGVmaW5lZCJ9XSwiY2hlY2thbGwiOiJ0cnVlIiwicmVwYWlyIjpmYWxzZSwib3V0cHV0cyI6MSwieCI6NzcwLCJ5Ijo4MCwid2lyZXMiOltbIjg4MmUyOWQyZGNjOTMyNmMiXV19LHsiaWQiOiI4ODJlMjlkMmRjYzkzMjZjIiwidHlwZSI6ImNoYW5nZSIsInoiOiJkNWRkOWJmYzU5OTM3NGQ0IiwiZyI6IjY0NzE4MjRlMjRmMTg5MzkiLCJuYW1lIjoiU3RvcmUgbGluZXMgdG8gY29udGV4dCBzdG9yZSIsInJ1bGVzIjpbeyJ0Ijoic2V0IiwicCI6IiM6KHBlcnNpc3RlbnQpOjpsaW5lcyIsInB0IjoiZ2xvYmFsIiwidG8iOiJbe1widmFsdWVcIjpcIlQxXCIsXCJsYWJlbFwiOlwiVDFcIn0se1widmFsdWVcIjpcIlQyXCIsXCJsYWJlbFwiOlwiVDJcIn1dIiwidG90IjoianNvbiJ9XSwiYWN0aW9uIjoiIiwicHJvcGVydHkiOiIiLCJmcm9tIjoiIiwidG8iOiIiLCJyZWciOmZhbHNlLCJ4IjoxMTQwLCJ5Ijo4MCwid2lyZXMiOltbXV19LHsiaWQiOiIzOTliZDE1OWY0NmM3NDI3IiwidHlwZSI6InN3aXRjaCIsInoiOiJkNWRkOWJmYzU5OTM3NGQ0IiwiZyI6IjY0NzE4MjRlMjRmMTg5MzkiLCJuYW1lIjoiSXMgZGVwYXJ0bWVudHMgdW5kZWZpbmVkPyIsInByb3BlcnR5IjoiIzoocGVyc2lzdGVudCk6OmRlcGFydG1lbnRzIiwicHJvcGVydHlUeXBlIjoiZ2xvYmFsIiwicnVsZXMiOlt7InQiOiJpc3R5cGUiLCJ2IjoidW5kZWZpbmVkIiwidnQiOiJ1bmRlZmluZWQifV0sImNoZWNrYWxsIjoidHJ1ZSIsInJlcGFpciI6ZmFsc2UsIm91dHB1dHMiOjEsIngiOjgwMCwieSI6MTIwLCJ3aXJlcyI6W1siNTAwMWMxY2RmNjY2MWY4OCJdXX0seyJpZCI6IjUwMDFjMWNkZjY2NjFmODgiLCJ0eXBlIjoiY2hhbmdlIiwieiI6ImQ1ZGQ5YmZjNTk5Mzc0ZDQiLCJnIjoiNjQ3MTgyNGUyNGYxODkzOSIsIm5hbWUiOiJTdG9yZSBkZXBhcnRtZW50cyB0byBjb250ZXh0IHN0b3JlIiwicnVsZXMiOlt7InQiOiJzZXQiLCJwIjoiIzoocGVyc2lzdGVudCk6OmRlcGFydG1lbnRzIiwicHQiOiJnbG9iYWwiLCJ0byI6Ilt7XCJ2YWx1ZVwiOlwiTWFpbnRlbmFuY2VcIixcImxhYmVsXCI6XCJNYWludGVuYW5jZVwifSx7XCJ2YWx1ZVwiOlwiU3RvcmVzXCIsXCJsYWJlbFwiOlwiU3RvcmVzXCJ9LHtcInZhbHVlXCI6XCJRdWFsaXR5XCIsXCJsYWJlbFwiOlwiUXVhbGl0eVwifV0iLCJ0b3QiOiJqc29uIn1dLCJhY3Rpb24iOiIiLCJwcm9wZXJ0eSI6IiIsImZyb20iOiIiLCJ0byI6IiIsInJlZyI6ZmFsc2UsIngiOjExNzAsInkiOjEyMCwid2lyZXMiOltbXV19XQ==",[171,320,321,324],{},[46,322,323],{},"Import the provided demo flow.",[46,325,326,328],{},[30,327,288],{}," the flow.",[10,330,331,332,335],{},"This will store demo lines and departments in the global context as ",[131,333,334],{},"global.lines"," if not already present.",[92,337,54],{"id":338},"build-line-selection-menu",[10,340,341],{},"Now, let us create a new page and menu item for Production Lines. This page will list all currently available production lines, making it easier to navigate through them.",[171,343,344,351,367,373],{},[46,345,346,347,350],{},"Drag a ",[30,348,349],{},"ui-event"," node onto the canvas to detect page navigation.",[46,352,346,353,356,357],{},[30,354,355],{},"change"," node and configure it to:\n",[43,358,359],{},[46,360,203,361,207,364],{},[131,362,363],{},"msg.payload.lines",[131,365,366],{},"global.get(\"lines\")",[46,368,346,369,372],{},[30,370,371],{},"ui-template"," node onto the canvas. Create a new page with name “Line Menu” and a group.",[46,374,375],{},"Paste the following into the template:",[213,377,381],{"className":378,"code":379,"language":380,"meta":218,"style":218},"language-html shiki shiki-themes github-light github-dark","\u003Ctemplate>\n  \u003Cv-container>\n    \u003Cv-row>\n      \u003Cv-col>\n        \u003Ch3>Production Lines\u003C\u002Fh3>\n      \u003C\u002Fv-col>\n    \u003C\u002Fv-row>\n\n    \u003Cv-row class=\"scrollable-row\" no-gutters>\n      \u003Cv-col v-for=\"(btn, index) in lines\" :key=\"index\" cols=\"auto\">\n        \u003Cv-btn :href=\"`\u002Fdashboard\u002Flines?line=${btn.value}`\" class=\"custom-btn\" rounded>\n          {{ btn.label }}\n        \u003C\u002Fv-btn>\n      \u003C\u002Fv-col>\n    \u003C\u002Fv-row>\n\n    \u003Cv-alert v-if=\"selectedLine\" type=\"success\" class=\"mt-3\">\n      Selected Line: {{ selectedLine }}\n    \u003C\u002Fv-alert>\n  \u003C\u002Fv-container>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nexport default {\n  data() {\n    return {\n      selectedLine: '',\n      lines: []\n    };\n  },\n  methods: {\n    updateButtonContent(data) {\n      this.lines = (data.lines || []).sort((a, b) => a.label.localeCompare(b.label));\n    }\n  },\n  mounted() {\n    this.lines = [];\n    this.$socket.on('msg-input:' + this.id, (msg) => {\n      this.updateButtonContent(msg.payload);\n    });\n  }\n};\n\u003C\u002Fscript>\n\n\u003Cstyle scoped>\n.scrollable-row {\n  display: flex;\n  overflow-x: auto;\n  padding: 10px 0;\n  flex-wrap: wrap;\n}\n.scrollable-row .v-col {\n  flex-shrink: 0;\n  margin-bottom: 10px;\n}\n.scrollable-row {\n  min-height: 60px;\n}\n.custom-btn {\n  background-color: rgb(32, 44, 52) !important;\n  color: white !important;\n  margin-right: 12px;\n  padding: 8px 16px;\n}\n.custom-btn:hover {\n  background-color: rgb(54, 70, 86) !important;\n}\n\u003C\u002Fstyle>\n","html",[131,382,383,396,406,416,426,440,449,458,464,486,519,547,553,563,572,581,586,619,625,634,644,654,659,669,682,691,699,711,717,723,729,735,751,801,807,812,820,833,867,880,886,892,898,907,912,925,933,948,961,980,993,999,1009,1022,1036,1041,1048,1063,1068,1076,1109,1125,1140,1159,1164,1172,1202,1207],{"__ignoreMap":218},[222,384,385,389,393],{"class":224,"line":225},[222,386,388],{"class":387},"sVt8B","\u003C",[222,390,392],{"class":391},"s9eBZ","template",[222,394,395],{"class":387},">\n",[222,397,398,401,404],{"class":224,"line":231},[222,399,400],{"class":387},"  \u003C",[222,402,403],{"class":391},"v-container",[222,405,395],{"class":387},[222,407,408,411,414],{"class":224,"line":237},[222,409,410],{"class":387},"    \u003C",[222,412,413],{"class":391},"v-row",[222,415,395],{"class":387},[222,417,418,421,424],{"class":224,"line":243},[222,419,420],{"class":387},"      \u003C",[222,422,423],{"class":391},"v-col",[222,425,395],{"class":387},[222,427,428,431,433,436,438],{"class":224,"line":249},[222,429,430],{"class":387},"        \u003C",[222,432,92],{"class":391},[222,434,435],{"class":387},">Production Lines\u003C\u002F",[222,437,92],{"class":391},[222,439,395],{"class":387},[222,441,442,445,447],{"class":224,"line":255},[222,443,444],{"class":387},"      \u003C\u002F",[222,446,423],{"class":391},[222,448,395],{"class":387},[222,450,451,454,456],{"class":224,"line":261},[222,452,453],{"class":387},"    \u003C\u002F",[222,455,413],{"class":391},[222,457,395],{"class":387},[222,459,460],{"class":224,"line":267},[222,461,463],{"emptyLinePlaceholder":462},true,"\n",[222,465,466,468,470,474,477,481,484],{"class":224,"line":273},[222,467,410],{"class":387},[222,469,413],{"class":391},[222,471,473],{"class":472},"sScJk"," class",[222,475,476],{"class":387},"=",[222,478,480],{"class":479},"sZZnC","\"scrollable-row\"",[222,482,483],{"class":472}," no-gutters",[222,485,395],{"class":387},[222,487,489,491,493,496,498,501,504,506,509,512,514,517],{"class":224,"line":488},10,[222,490,420],{"class":387},[222,492,423],{"class":391},[222,494,495],{"class":472}," v-for",[222,497,476],{"class":387},[222,499,500],{"class":479},"\"(btn, index) in lines\"",[222,502,503],{"class":472}," :key",[222,505,476],{"class":387},[222,507,508],{"class":479},"\"index\"",[222,510,511],{"class":472}," cols",[222,513,476],{"class":387},[222,515,516],{"class":479},"\"auto\"",[222,518,395],{"class":387},[222,520,522,524,527,530,532,535,537,539,542,545],{"class":224,"line":521},11,[222,523,430],{"class":387},[222,525,526],{"class":391},"v-btn",[222,528,529],{"class":472}," :href",[222,531,476],{"class":387},[222,533,534],{"class":479},"\"`\u002Fdashboard\u002Flines?line=${btn.value}`\"",[222,536,473],{"class":472},[222,538,476],{"class":387},[222,540,541],{"class":479},"\"custom-btn\"",[222,543,544],{"class":472}," rounded",[222,546,395],{"class":387},[222,548,550],{"class":224,"line":549},12,[222,551,552],{"class":387},"          {{ btn.label }}\n",[222,554,556,559,561],{"class":224,"line":555},13,[222,557,558],{"class":387},"        \u003C\u002F",[222,560,526],{"class":391},[222,562,395],{"class":387},[222,564,566,568,570],{"class":224,"line":565},14,[222,567,444],{"class":387},[222,569,423],{"class":391},[222,571,395],{"class":387},[222,573,575,577,579],{"class":224,"line":574},15,[222,576,453],{"class":387},[222,578,413],{"class":391},[222,580,395],{"class":387},[222,582,584],{"class":224,"line":583},16,[222,585,463],{"emptyLinePlaceholder":462},[222,587,589,591,594,597,599,602,605,607,610,612,614,617],{"class":224,"line":588},17,[222,590,410],{"class":387},[222,592,593],{"class":391},"v-alert",[222,595,596],{"class":472}," v-if",[222,598,476],{"class":387},[222,600,601],{"class":479},"\"selectedLine\"",[222,603,604],{"class":472}," type",[222,606,476],{"class":387},[222,608,609],{"class":479},"\"success\"",[222,611,473],{"class":472},[222,613,476],{"class":387},[222,615,616],{"class":479},"\"mt-3\"",[222,618,395],{"class":387},[222,620,622],{"class":224,"line":621},18,[222,623,624],{"class":387},"      Selected Line: {{ selectedLine }}\n",[222,626,628,630,632],{"class":224,"line":627},19,[222,629,453],{"class":387},[222,631,593],{"class":391},[222,633,395],{"class":387},[222,635,637,640,642],{"class":224,"line":636},20,[222,638,639],{"class":387},"  \u003C\u002F",[222,641,403],{"class":391},[222,643,395],{"class":387},[222,645,647,650,652],{"class":224,"line":646},21,[222,648,649],{"class":387},"\u003C\u002F",[222,651,392],{"class":391},[222,653,395],{"class":387},[222,655,657],{"class":224,"line":656},22,[222,658,463],{"emptyLinePlaceholder":462},[222,660,662,664,667],{"class":224,"line":661},23,[222,663,388],{"class":387},[222,665,666],{"class":391},"script",[222,668,395],{"class":387},[222,670,672,676,679],{"class":224,"line":671},24,[222,673,675],{"class":674},"szBVR","export",[222,677,678],{"class":674}," default",[222,680,681],{"class":387}," {\n",[222,683,685,688],{"class":224,"line":684},25,[222,686,687],{"class":472},"  data",[222,689,690],{"class":387},"() {\n",[222,692,694,697],{"class":224,"line":693},26,[222,695,696],{"class":674},"    return",[222,698,681],{"class":387},[222,700,702,705,708],{"class":224,"line":701},27,[222,703,704],{"class":387},"      selectedLine: ",[222,706,707],{"class":479},"''",[222,709,710],{"class":387},",\n",[222,712,714],{"class":224,"line":713},28,[222,715,716],{"class":387},"      lines: []\n",[222,718,720],{"class":224,"line":719},29,[222,721,722],{"class":387},"    };\n",[222,724,726],{"class":224,"line":725},30,[222,727,728],{"class":387},"  },\n",[222,730,732],{"class":224,"line":731},31,[222,733,734],{"class":387},"  methods: {\n",[222,736,738,741,744,748],{"class":224,"line":737},32,[222,739,740],{"class":472},"    updateButtonContent",[222,742,743],{"class":387},"(",[222,745,747],{"class":746},"s4XuR","data",[222,749,750],{"class":387},") {\n",[222,752,754,758,761,763,766,769,772,775,778,780,783,786,789,792,795,798],{"class":224,"line":753},33,[222,755,757],{"class":756},"sj4cs","      this",[222,759,760],{"class":387},".lines ",[222,762,476],{"class":674},[222,764,765],{"class":387}," (data.lines ",[222,767,768],{"class":674},"||",[222,770,771],{"class":387}," []).",[222,773,774],{"class":472},"sort",[222,776,777],{"class":387},"((",[222,779,14],{"class":746},[222,781,782],{"class":387},", ",[222,784,785],{"class":746},"b",[222,787,788],{"class":387},") ",[222,790,791],{"class":674},"=>",[222,793,794],{"class":387}," a.label.",[222,796,797],{"class":472},"localeCompare",[222,799,800],{"class":387},"(b.label));\n",[222,802,804],{"class":224,"line":803},34,[222,805,806],{"class":387},"    }\n",[222,808,810],{"class":224,"line":809},35,[222,811,728],{"class":387},[222,813,815,818],{"class":224,"line":814},36,[222,816,817],{"class":472},"  mounted",[222,819,690],{"class":387},[222,821,823,826,828,830],{"class":224,"line":822},37,[222,824,825],{"class":756},"    this",[222,827,760],{"class":387},[222,829,476],{"class":674},[222,831,832],{"class":387}," [];\n",[222,834,836,838,841,844,846,849,852,855,858,861,863,865],{"class":224,"line":835},38,[222,837,825],{"class":756},[222,839,840],{"class":387},".$socket.",[222,842,843],{"class":472},"on",[222,845,743],{"class":387},[222,847,848],{"class":479},"'msg-input:'",[222,850,851],{"class":674}," +",[222,853,854],{"class":756}," this",[222,856,857],{"class":387},".id, (",[222,859,860],{"class":746},"msg",[222,862,788],{"class":387},[222,864,791],{"class":674},[222,866,681],{"class":387},[222,868,870,872,874,877],{"class":224,"line":869},39,[222,871,757],{"class":756},[222,873,289],{"class":387},[222,875,876],{"class":472},"updateButtonContent",[222,878,879],{"class":387},"(msg.payload);\n",[222,881,883],{"class":224,"line":882},40,[222,884,885],{"class":387},"    });\n",[222,887,889],{"class":224,"line":888},41,[222,890,891],{"class":387},"  }\n",[222,893,895],{"class":224,"line":894},42,[222,896,897],{"class":387},"};\n",[222,899,901,903,905],{"class":224,"line":900},43,[222,902,649],{"class":387},[222,904,666],{"class":391},[222,906,395],{"class":387},[222,908,910],{"class":224,"line":909},44,[222,911,463],{"emptyLinePlaceholder":462},[222,913,915,917,920,923],{"class":224,"line":914},45,[222,916,388],{"class":387},[222,918,919],{"class":391},"style",[222,921,922],{"class":472}," scoped",[222,924,395],{"class":387},[222,926,928,931],{"class":224,"line":927},46,[222,929,930],{"class":472},".scrollable-row",[222,932,681],{"class":387},[222,934,936,939,942,945],{"class":224,"line":935},47,[222,937,938],{"class":756},"  display",[222,940,941],{"class":387},": ",[222,943,944],{"class":756},"flex",[222,946,947],{"class":387},";\n",[222,949,951,954,956,959],{"class":224,"line":950},48,[222,952,953],{"class":756},"  overflow-x",[222,955,941],{"class":387},[222,957,958],{"class":756},"auto",[222,960,947],{"class":387},[222,962,964,967,969,972,975,978],{"class":224,"line":963},49,[222,965,966],{"class":756},"  padding",[222,968,941],{"class":387},[222,970,971],{"class":756},"10",[222,973,974],{"class":674},"px",[222,976,977],{"class":756}," 0",[222,979,947],{"class":387},[222,981,983,986,988,991],{"class":224,"line":982},50,[222,984,985],{"class":756},"  flex-wrap",[222,987,941],{"class":387},[222,989,990],{"class":756},"wrap",[222,992,947],{"class":387},[222,994,996],{"class":224,"line":995},51,[222,997,998],{"class":387},"}\n",[222,1000,1002,1004,1007],{"class":224,"line":1001},52,[222,1003,930],{"class":472},[222,1005,1006],{"class":472}," .v-col",[222,1008,681],{"class":387},[222,1010,1012,1015,1017,1020],{"class":224,"line":1011},53,[222,1013,1014],{"class":756},"  flex-shrink",[222,1016,941],{"class":387},[222,1018,1019],{"class":756},"0",[222,1021,947],{"class":387},[222,1023,1025,1028,1030,1032,1034],{"class":224,"line":1024},54,[222,1026,1027],{"class":756},"  margin-bottom",[222,1029,941],{"class":387},[222,1031,971],{"class":756},[222,1033,974],{"class":674},[222,1035,947],{"class":387},[222,1037,1039],{"class":224,"line":1038},55,[222,1040,998],{"class":387},[222,1042,1044,1046],{"class":224,"line":1043},56,[222,1045,930],{"class":472},[222,1047,681],{"class":387},[222,1049,1051,1054,1056,1059,1061],{"class":224,"line":1050},57,[222,1052,1053],{"class":756},"  min-height",[222,1055,941],{"class":387},[222,1057,1058],{"class":756},"60",[222,1060,974],{"class":674},[222,1062,947],{"class":387},[222,1064,1066],{"class":224,"line":1065},58,[222,1067,998],{"class":387},[222,1069,1071,1074],{"class":224,"line":1070},59,[222,1072,1073],{"class":472},".custom-btn",[222,1075,681],{"class":387},[222,1077,1079,1082,1084,1087,1089,1092,1094,1097,1099,1102,1104,1107],{"class":224,"line":1078},60,[222,1080,1081],{"class":756},"  background-color",[222,1083,941],{"class":387},[222,1085,1086],{"class":756},"rgb",[222,1088,743],{"class":387},[222,1090,1091],{"class":756},"32",[222,1093,782],{"class":387},[222,1095,1096],{"class":756},"44",[222,1098,782],{"class":387},[222,1100,1101],{"class":756},"52",[222,1103,788],{"class":387},[222,1105,1106],{"class":674},"!important",[222,1108,947],{"class":387},[222,1110,1112,1115,1117,1120,1123],{"class":224,"line":1111},61,[222,1113,1114],{"class":756},"  color",[222,1116,941],{"class":387},[222,1118,1119],{"class":756},"white",[222,1121,1122],{"class":674}," !important",[222,1124,947],{"class":387},[222,1126,1128,1131,1133,1136,1138],{"class":224,"line":1127},62,[222,1129,1130],{"class":756},"  margin-right",[222,1132,941],{"class":387},[222,1134,1135],{"class":756},"12",[222,1137,974],{"class":674},[222,1139,947],{"class":387},[222,1141,1143,1145,1147,1150,1152,1155,1157],{"class":224,"line":1142},63,[222,1144,966],{"class":756},[222,1146,941],{"class":387},[222,1148,1149],{"class":756},"8",[222,1151,974],{"class":674},[222,1153,1154],{"class":756}," 16",[222,1156,974],{"class":674},[222,1158,947],{"class":387},[222,1160,1162],{"class":224,"line":1161},64,[222,1163,998],{"class":387},[222,1165,1167,1170],{"class":224,"line":1166},65,[222,1168,1169],{"class":472},".custom-btn:hover",[222,1171,681],{"class":387},[222,1173,1175,1177,1179,1181,1183,1186,1188,1191,1193,1196,1198,1200],{"class":224,"line":1174},66,[222,1176,1081],{"class":756},[222,1178,941],{"class":387},[222,1180,1086],{"class":756},[222,1182,743],{"class":387},[222,1184,1185],{"class":756},"54",[222,1187,782],{"class":387},[222,1189,1190],{"class":756},"70",[222,1192,782],{"class":387},[222,1194,1195],{"class":756},"86",[222,1197,788],{"class":387},[222,1199,1106],{"class":674},[222,1201,947],{"class":387},[222,1203,1205],{"class":224,"line":1204},67,[222,1206,998],{"class":387},[222,1208,1210,1212,1214],{"class":224,"line":1209},68,[222,1211,649],{"class":387},[222,1213,919],{"class":391},[222,1215,395],{"class":387},[171,1217,1218],{"start":249},[46,1219,1220],{},"Deploy the flow.",[10,1222,1223],{},"Once deployed, the dashboard will show buttons for each production line. Clicking a line redirects the user to a page with following url:",[213,1225,1230],{"className":1226,"code":1228,"language":1229},[1227],"language-text","https:\u002F\u002F\u003Cyour-instance-name>\u002Fdashboard\u002Flines?line=T1\n","text",[131,1231,1228],{"__ignoreMap":218},[10,1233,1234,1238],{},[293,1235],{"alt":1236,"dataZoomable":218,"src":1237},"Node-RED editor showing the flow setup for generating a menu of production lines using ui-event, change, and ui-template nodes.","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Fmenu-for-lines-flow.png",[298,1239,1240],{},"Node-RED flow to create a dynamic menu for production lines on the dashboard.",[10,1242,1243,1247],{},[293,1244],{"alt":1245,"dataZoomable":218,"src":1246},"FlowFuse Dashboard displaying styled buttons for each production line, enabling quick navigation to specific line pages.","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Fmenu-for-lines.png",[298,1248,1249],{},"Dashboard view showing production line buttons generated from the flow.",[10,1251,1252],{},"The \"line\" URL parameter will be used in the next section to store the user's selected production line.",[92,1254,57],{"id":1255},"enable-url-based-dashboard-access",[10,1257,1258],{},"In this section, we’ll build a flow that allows users to directly access the dashboard using a URL with a line parameter (e.g., ?line=T1). The flow will validate this parameter and store the selected line for each user session. If the parameter is missing or invalid, the user will be redirected to a Not Found page.",[10,1260,1261],{},"To achieve this, we need to:",[43,1263,1264,1267,1270],{},[46,1265,1266],{},"Configure the dashboard to expose client-specific metadata.",[46,1268,1269],{},"Create a flow that validates the line parameter from the URL.",[46,1271,1272],{},"Store the user’s selected line using session-aware context data.",[1274,1275,1277],"h4",{"id":1276},"configuring-dashboard-widgets-to-include-client-information","Configuring Dashboard Widgets to Include Client Information",[10,1279,1280],{},"To ensure client data is available in your flows, follow these steps:",[171,1282,1283,1290,1297,1303,1325],{},[46,1284,1285,1286,1289],{},"Open the ",[30,1287,1288],{},"Dashboard 2.0"," sidebar.",[46,1291,1292,1293,1296],{},"Switch to the ",[30,1294,1295],{},"Client Data"," tab.",[46,1298,1299,1300,289],{},"Enable the option ",[30,1301,1302],{},"“Include client data”",[46,1304,1305,1306],{},"Tick the checkbox in front of:\n",[43,1307,1308,1311,1313,1316,1319,1322],{},[46,1309,1310],{},"ui-control",[46,1312,371],{},[46,1314,1315],{},"ui-button",[46,1317,1318],{},"ui-text-input",[46,1320,1321],{},"ui-dropdown",[46,1323,1324],{},"ui-notification",[46,1326,1327,1329],{},[30,1328,288],{}," the updated configuration.",[10,1331,1332,1336],{},[293,1333],{"alt":1334,"dataZoomable":218,"src":1335},"Enabling client data in the Dashboard 2.0 settings to include metadata from specific widgets.","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Fclient-data-configuration.png",[298,1337,1338],{},"Dashboard 2.0 settings to include client metadata from selected widgets like buttons and templates.",[10,1340,1341,1342,1345],{},"With this setting enabled, the selected widgets will include client-related metadata in their output messages under the ",[131,1343,1344],{},"msg._client"," property. This metadata is essential for building session-aware features in the Dashboard.",[1274,1347,1349],{"id":1348},"building-a-dashboard-flow-for-url-access,-line-validation,-and-user-selection","Building a Dashboard Flow for URL Access, Line Validation, and User Selection",[10,1351,1352],{},"In this section, we'll build a Node-RED flow that handles dashboard access via direct URLs, validates the line parameter, and stores the selected line per user session using client metadata. This ensures that each user's line selection is tracked independently and that invalid or missing parameters are handled gracefully.",[10,1354,1355],{},"The flow performs the following key tasks:",[43,1357,1358,1361,1364,1367],{},[46,1359,1360],{},"Detects when a user accesses the dashboard using a URL containing a line parameter.",[46,1362,1363],{},"Validates the parameter against a list of predefined production lines.",[46,1365,1366],{},"Stores the selected line using session-aware (client-specific) data.",[46,1368,1369],{},"Redirects the user appropriately based on the validity of the parameter.",[10,1371,1372],{},[30,1373,1374],{},"Steps to Build the Flow:",[171,1376,1377,1383,1400,1420,1425],{},[46,1378,346,1379,1382],{},[30,1380,1381],{},"ui-event node"," onto the canvas and configure it with the correct UI base path.",[46,1384,346,1385,1388,1389,1392,1393],{},[30,1386,1387],{},"switch node"," and name it \"Is it a pageview event?\" and configure it with the property ",[131,1390,1391],{},"msg.topic"," and add the following condition:",[43,1394,1395],{},[46,1396,1397],{},[131,1398,1399],{},"== $pageview",[46,1401,1402,1403,1405,1406,1409,1410],{},"Drag another ",[30,1404,1387],{}," and name it \"Has line key?\" and configure it with the property ",[131,1407,1408],{},"msg.payload.page.params.line",", with two conditions:",[43,1411,1412,1417],{},[46,1413,1414,1415],{},"has key ",[131,1416,224],{},[46,1418,1419],{},"is empty",[46,1421,279,1422,1424],{},[30,1423,1381],{}," to the first switch node (labeled Is it a pageview event?). Then, connect the first output of this switch node to the second switch node (labeled Has line key?). This checks whether the page was accessed via URL and if a line parameter is present.",[46,1426,346,1427,1430,1431],{},[131,1428,1429],{},"function"," node and name it \"Extract Labels from Lines\" and paste the following code:",[213,1432,1436],{"className":1433,"code":1434,"language":1435,"meta":218,"style":218},"language-javascript shiki shiki-themes github-light github-dark","let lines = global.get('lines', 'persistent') || [];\nlet labels = [\n   ...lines.map(obj => obj.label),\n];\nmsg.payload.labels = labels;\nreturn msg;\n","javascript",[131,1437,1438,1470,1482,1504,1509,1519],{"__ignoreMap":218},[222,1439,1440,1443,1446,1448,1451,1454,1456,1459,1461,1464,1466,1468],{"class":224,"line":225},[222,1441,1442],{"class":674},"let",[222,1444,1445],{"class":387}," lines ",[222,1447,476],{"class":674},[222,1449,1450],{"class":387}," global.",[222,1452,1453],{"class":472},"get",[222,1455,743],{"class":387},[222,1457,1458],{"class":479},"'lines'",[222,1460,782],{"class":387},[222,1462,1463],{"class":479},"'persistent'",[222,1465,788],{"class":387},[222,1467,768],{"class":674},[222,1469,832],{"class":387},[222,1471,1472,1474,1477,1479],{"class":224,"line":231},[222,1473,1442],{"class":674},[222,1475,1476],{"class":387}," labels ",[222,1478,476],{"class":674},[222,1480,1481],{"class":387}," [\n",[222,1483,1484,1487,1490,1493,1495,1498,1501],{"class":224,"line":237},[222,1485,1486],{"class":674},"   ...",[222,1488,1489],{"class":387},"lines.",[222,1491,1492],{"class":472},"map",[222,1494,743],{"class":387},[222,1496,1497],{"class":746},"obj",[222,1499,1500],{"class":674}," =>",[222,1502,1503],{"class":387}," obj.label),\n",[222,1505,1506],{"class":224,"line":243},[222,1507,1508],{"class":387},"];\n",[222,1510,1511,1514,1516],{"class":224,"line":249},[222,1512,1513],{"class":387},"msg.payload.labels ",[222,1515,476],{"class":674},[222,1517,1518],{"class":387}," labels;\n",[222,1520,1521,1524],{"class":224,"line":255},[222,1522,1523],{"class":674},"return",[222,1525,1526],{"class":387}," msg;\n",[10,1528,1529],{},"This retrieves the list of available lines from the persistent global context, extracts their labels, and creates an array for easier verification of whether the user selected line is present.",[171,1531,1532,1548],{"start":255},[46,1533,346,1534,1537,1538],{},[30,1535,1536],{},"change node"," and name it \"Store line selection\":\n",[43,1539,1540],{},[46,1541,203,1542,207,1545,289],{},[131,1543,1544],{},"global.store[msg._client.socketId].line",[131,1546,1547],{},"msg.payload",[46,1549,1402,1550,1552],{},[30,1551,1387],{}," and name it \"Is the currently accessed page 'Lines'?\":",[43,1554,1555],{},[46,1556,1557],{},"is equal to \"Lines\"",[171,1559,1560,1572,1589,1607],{"start":267},[46,1561,279,1562,1565,1566,1568,1569,1571],{},[30,1563,1564],{},"function node"," (Extract Labels from Lines) and the ",[30,1567,1536],{}," (Store line selection) to the first output of the ",[30,1570,1387],{}," (Has line key?).",[46,1573,1402,1574,1577,1578,1581,1582],{},[131,1575,1576],{},"switch"," node and name it \"Is line valid?\" and set the property to ",[131,1579,1580],{},"msg.payload.labels"," and add following conditions:\n",[43,1583,1584],{},[46,1585,1586,1587],{},"contains ",[131,1588,1544],{},[46,1590,1591,1592,1594,1595,1597,1598],{},"For the first output of the ",[131,1593,1576],{}," node (Is the currently accessed page 'Lines'?), drag a ",[30,1596,355],{}," node and name it \"Redirect the user to the All Lines menu\" and Configure it to:\n",[43,1599,1600],{},[46,1601,203,1602,207,1604],{},[131,1603,1547],{},[131,1605,1606],{},"\"All Lines\"",[46,1608,346,1609,1612,1613,1615],{},[30,1610,1611],{},"ui-control node"," onto the canvas and connect it to the ",[30,1614,1536],{}," (Redirect the user to the All Lines menu). This node will handle the redirection or display feedback on the dashboard.",[10,1617,1618],{},"This checks whether the selected line is valid by comparing it with the list of known line labels.",[171,1620,1621],{"start":549},[46,1622,1623,1624,1627],{},"For the second output of the switch node (Is line valid?), drag a switch node and give it name \"Is the currently accessed page 'Lines'?\" and Set property to ",[131,1625,1626],{},"msg.payload.page.name"," and add following condition to check against:",[43,1629,1630,1633],{},[46,1631,1632],{},"== \"Lines\"",[46,1634,1635],{},"Otherwise",[171,1637,1638,1644,1647],{"start":555},[46,1639,1640,1641,1643],{},"For the first output of the switch node ((Is the currently accessed page 'Lines'?), drag a change node and configure it to:\n- Set ",[131,1642,1547],{}," to \"Incorrect Link\"",[46,1645,1646],{},"Drag a ui-control node onto the canvas and configure it with the correct UI base path and connect it the change node (Redirect to Not found page).",[46,1648,1649],{},"Deploy the changes.",[10,1651,1652,1656],{},[293,1653],{"alt":1654,"dataZoomable":218,"src":1655},"Flow for handling dashboard navigation, validating URL parameters, and assigning selected production lines to individual client sessions.","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Faccessing-production-lines.png",[298,1657,1658],{},"Flow for managing URL-based access, validating line parameters, and storing client-specific selections on the OEE dashboard.",[92,1660,1662],{"id":1661},"create-live-request-flow-(per-line)","Create Live Request Flow (Per Line)",[10,1664,1665],{},"Let’s build the flow to retrieve the data now based on the user selection.",[171,1667,1668,1674,1677,1684,1815,1821,1841,1846,1851,1863,1869,1872],{},[46,1669,346,1670,1673],{},[131,1671,1672],{},"Template"," widget onto the canvas.",[46,1675,1676],{},"Double-click the Template widget to open its configuration.",[46,1678,1679,1680,1683],{},"Set the scope to ",[131,1681,1682],{},"ui"," and select the appropriate UI Base.",[46,1685,1686,1687],{},"Paste the following script into the content field. This script triggers every second and sends a message that includes the current user's client data:",[213,1688,1690],{"className":378,"code":1689,"language":380,"meta":218,"style":218},"\u003Cscript>\n  export default {\n    mounted() {\n      \u002F\u002F Set an interval to update the message every second\n      this.intervalId = setInterval(() => {\n        this.send('Component has loaded');\n      }, 1000);\n    },\n    beforeUnmount() {\n      \u002F\u002F Clear the interval when the component is about to be destroyed\n      clearInterval(this.intervalId);\n    },\n  };\n\u003C\u002Fscript>\n",[131,1691,1692,1700,1709,1716,1722,1741,1758,1768,1773,1780,1785,1798,1802,1807],{"__ignoreMap":218},[222,1693,1694,1696,1698],{"class":224,"line":225},[222,1695,388],{"class":387},[222,1697,666],{"class":391},[222,1699,395],{"class":387},[222,1701,1702,1705,1707],{"class":224,"line":231},[222,1703,1704],{"class":674},"  export",[222,1706,678],{"class":674},[222,1708,681],{"class":387},[222,1710,1711,1714],{"class":224,"line":237},[222,1712,1713],{"class":472},"    mounted",[222,1715,690],{"class":387},[222,1717,1718],{"class":224,"line":243},[222,1719,1721],{"class":1720},"sJ8bj","      \u002F\u002F Set an interval to update the message every second\n",[222,1723,1724,1726,1729,1731,1734,1737,1739],{"class":224,"line":249},[222,1725,757],{"class":756},[222,1727,1728],{"class":387},".intervalId ",[222,1730,476],{"class":674},[222,1732,1733],{"class":472}," setInterval",[222,1735,1736],{"class":387},"(() ",[222,1738,791],{"class":674},[222,1740,681],{"class":387},[222,1742,1743,1746,1748,1751,1753,1756],{"class":224,"line":255},[222,1744,1745],{"class":756},"        this",[222,1747,289],{"class":387},[222,1749,1750],{"class":472},"send",[222,1752,743],{"class":387},[222,1754,1755],{"class":479},"'Component has loaded'",[222,1757,276],{"class":387},[222,1759,1760,1763,1766],{"class":224,"line":261},[222,1761,1762],{"class":387},"      }, ",[222,1764,1765],{"class":756},"1000",[222,1767,276],{"class":387},[222,1769,1770],{"class":224,"line":267},[222,1771,1772],{"class":387},"    },\n",[222,1774,1775,1778],{"class":224,"line":273},[222,1776,1777],{"class":472},"    beforeUnmount",[222,1779,690],{"class":387},[222,1781,1782],{"class":224,"line":488},[222,1783,1784],{"class":1720},"      \u002F\u002F Clear the interval when the component is about to be destroyed\n",[222,1786,1787,1790,1792,1795],{"class":224,"line":521},[222,1788,1789],{"class":472},"      clearInterval",[222,1791,743],{"class":387},[222,1793,1794],{"class":756},"this",[222,1796,1797],{"class":387},".intervalId);\n",[222,1799,1800],{"class":224,"line":549},[222,1801,1772],{"class":387},[222,1803,1804],{"class":224,"line":555},[222,1805,1806],{"class":387},"  };\n",[222,1808,1809,1811,1813],{"class":224,"line":565},[222,1810,649],{"class":387},[222,1812,666],{"class":391},[222,1814,395],{"class":387},[46,1816,346,1817,1820],{},[131,1818,1819],{},"Change"," node and name it \"Set params\".",[46,1822,1823,1824],{},"Configure the Change node with the following rules:",[43,1825,1826,1833],{},[46,1827,203,1828,207,1831],{},[131,1829,1830],{},"msg.params.$line",[131,1832,1544],{},[46,1834,203,1835,207,1838],{},[131,1836,1837],{},"msg.query",[131,1839,1840],{},"\"line\"",[46,1842,346,1843,1845],{},[131,1844,1672],{}," node onto the canvas.",[46,1847,1848,1849,289],{},"Double-click the Template node and set the property to ",[131,1850,1391],{},[46,1852,1853,1854],{},"Paste the following SQL query into the content field:",[213,1855,1857],{"className":215,"code":1856,"language":217,"meta":218,"style":218},"SELECT * FROM requests WHERE \"{{query}}\" = \"{{line}}\" AND resolved IS NULL\n",[131,1858,1859],{"__ignoreMap":218},[222,1860,1861],{"class":224,"line":225},[222,1862,1856],{},[46,1864,346,1865,1868],{},[131,1866,1867],{},"Markdown"," widget and name it \"Show currently selected line\".",[46,1870,1871],{},"Create a new group in the UI for the Markdown widget to render into.",[46,1873,1874,1875],{},"Enter the following content into the Markdown widget:",[213,1876,1878],{"className":378,"code":1877,"language":380,"meta":218,"style":218},"\u003Ch1 style=\"text-align: center;\">Line: {{ msg?.line }}\u003C\u002Fh1>\n",[131,1879,1880],{"__ignoreMap":218},[222,1881,1882,1884,1887,1890,1892,1895,1898,1900],{"class":224,"line":225},[222,1883,388],{"class":387},[222,1885,1886],{"class":391},"h1",[222,1888,1889],{"class":472}," style",[222,1891,476],{"class":387},[222,1893,1894],{"class":479},"\"text-align: center;\"",[222,1896,1897],{"class":387},">Line: {{ msg?.line }}\u003C\u002F",[222,1899,1886],{"class":391},[222,1901,395],{"class":387},[10,1903,1904,1908],{},[293,1905],{"alt":1906,"dataZoomable":218,"src":1907},"Dashboard view showing the title of the selected production line centered at the top.","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Flines-page-with-tittle-of-selected-line.png",[298,1909,1910],{},"The dashboard displays the selected production line name, retrieved from the client context, rendered using a Markdown widget.",[171,1912,1913,1917,1920,1923,1937,1945],{"start":555},[46,1914,346,1915,1845],{},[131,1916,184],{},[46,1918,1919],{},"Select the appropriate database configuration.",[46,1921,1922],{},"Set the node to use the SQL query via \"Prepared Statement\".",[46,1924,279,1925,1927,1928,1930,1931,1933,1934,1936],{},[131,1926,1672],{}," widget to ",[131,1929,1819],{}," node and ",[131,1932,1672],{}," node to ",[131,1935,184],{}," node.",[46,1938,346,1939,1942,1943,1936],{},[131,1940,1941],{},"link-out"," node onto the canvas and connect it to the ",[131,1944,184],{},[46,1946,1649],{},[10,1948,1949,1953],{},[293,1950],{"alt":1951,"dataZoomable":218,"src":1952},"Node-RED flow to fetch live requests for a selected production line using client context and SQLite query.","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Fretrive-data.png",[298,1954,1955],{},"Flow that triggers periodic queries for open requests specific to the user’s selected line using the client session and a SQLite database.",[92,1957,1959],{"id":1958},"preparing-data-rendering-it-in-a-table","Preparing Data Rendering it in a Table",[10,1961,1962],{},"Once the data is retrieved, it needs to be validated, formatted, and routed appropriately for display. In this section, a flow will be built to check whether any unresolved requests exist for the selected production line. If there are no requests, a message will be shown to the user. Otherwise, the data will be processed and rendered in a table format using Dashboard widgets.",[171,1964,1965,1974,1999,2013,2019,2034,2040,2046,2055,2063,2069,2077,2084],{},[46,1966,346,1967,1970,1971,289],{},[30,1968,1969],{},"link-in node"," onto the canvas and connect it to the last ",[30,1972,1973],{},"link-out node",[46,1975,1976,1977,1979,1980,1982,1983],{},"Add a ",[30,1978,1387],{}," to check whether the ",[131,1981,1547],{}," is empty, name it \"Is Payload empty?\".",[43,1984,1985],{},[46,1986,1987,1988],{},"Configure the switch with the following conditions:\n",[43,1989,1990,1995],{},[46,1991,1992],{},[131,1993,1994],{},"msg.payload is empty",[46,1996,1997],{},[131,1998,1635],{},[46,2000,346,2001,2003,2004],{},[30,2002,1536],{},", name it \"Show 'no outstanding request' message\" and configure it to set a message when the payload is empty:",[43,2005,2006],{},[46,2007,203,2008,207,2010],{},[131,2009,1547],{},[131,2011,2012],{},"\"There are no outstanding requests\"",[46,2014,2015,2016,2018],{},"Connect this ",[30,2017,1536],{}," to the first output of the switch node (\"Is Payload empty?\").",[46,2020,1402,2021,2023,2024],{},[30,2022,1536],{},", name it \"Remove 'no outstanding request' message\" and configure it as follows:",[43,2025,2026],{},[46,2027,203,2028,2030,2031],{},[131,2029,1547],{}," to an empty string ",[131,2032,2033],{},"\"\"",[46,2035,2036,2037,2039],{},"Connect this second ",[30,2038,1536],{}," to the second output of the switch node (\"Is Payload empty?\").",[46,2041,346,2042,2045],{},[30,2043,2044],{},"text widget",", name it \"Text Widget for Message\", onto the canvas, double-click it, and add a new group in the \"Lines\" page to render the message.",[46,2047,279,2048,2050,2051,2054],{},[30,2049,2044],{}," (\"Text Widget for Message\") to both ",[30,2052,2053],{},"change nodes"," that are setting the text message (\"Show 'no outstanding request' message\" and \"Remove 'no outstanding request' message\").",[46,2056,1402,2057,2059,2060,2062],{},[30,2058,1973],{}," onto the canvas and connect it to the second output of the switch node (\"Is Payload empty?\") that checks whether ",[131,2061,1547],{}," is empty.",[46,2064,1402,2065,2068],{},[30,2066,2067],{},"link-in"," onto the canvas.",[46,2070,346,2071,2074,2075,289],{},[30,2072,2073],{},"split node",", name it \"Split Node\", onto the canvas and connect it to the ",[30,2076,1969],{},[46,2078,346,2079,2081,2082,289],{},[30,2080,1973],{}," and connect it to the ",[30,2083,2073],{},[46,2085,1649],{},[10,2087,2088,2092],{},[293,2089],{"alt":2090,"dataZoomable":218,"src":2091},"Flow that checks if unresolved requests exist, sends an appropriate message when none are found, or prepares the data for tabular rendering","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Fpreparing-data.png",[298,2093,2094],{},"Flow that checks if unresolved requests exist, sends an appropriate message when none are found, or prepares the data for tabular rendering.",[92,2096,66],{"id":2097},"setting-up-visual-alerts-and-timestamp-formatting",[10,2099,2100],{},"To enhance the visibility of production line requests, this section focuses on setting up visual alerts based on the age of each request and formatting timestamps in a user-friendly way. The created timestamp is always shown using relative time (e.g., \"5 minutes ago\"). For acknowledged and resolved, relative formatting is applied only when those timestamps are available. This improves readability and makes it easier to identify requests that are pending action.",[171,2102,2103,2114],{},[46,2104,346,2105,2107,2108,2110,2111,2113],{},[30,2106,1969],{}," and a ",[30,2109,1564],{}," onto the canvas. Name the ",[30,2112,1564],{}," as \"Highlight Old Requests\" and open it.",[46,2115,2116,2117,2119],{},"Paste the following JavaScript code into the ",[30,2118,1564],{},":",[213,2121,2123],{"className":1433,"code":2122,"language":1435,"meta":218,"style":218},"const requested = msg.payload.requested;\nconst now = Date.now();\n\nconst requestedTime = new Date(requested).getTime();\nconst difference = now - requestedTime;\n\nconst oldRequestThreshold = global.get('oldRequestThreshold');\nconst veryOldRequestThreshold = global.get('veryOldRequestThreshold');\nconst alertActivationThreshold = global.get('alertActivationThreshold');\n\nif (difference > (veryOldRequestThreshold * 60 * 1000)) {\n   msg.payload.class = 'older';\n   if (difference > (alertActivationThreshold * 60 * 1000)) {\n       msg.payload.alert = true;\n   }\n   return msg;\n}\nelse if (difference > (oldRequestThreshold * 60 * 1000)) {\n   msg.payload.class = 'old';\n   if (difference > (alertActivationThreshold * 60 * 1000)) {\n       msg.payload.alert = true;\n   }\n   return msg;\n}\nelse {\n   if (difference > (alertActivationThreshold * 60 * 1000)) {\n       msg.payload.alert = true;\n   }\n   msg.payload.class = 'normal';\n   return msg;\n}\n",[131,2124,2125,2139,2157,2161,2184,2202,2206,2226,2246,2266,2270,2299,2311,2333,2345,2350,2357,2361,2386,2397,2417,2427,2431,2437,2441,2447,2467,2477,2481,2492,2498],{"__ignoreMap":218},[222,2126,2127,2130,2133,2136],{"class":224,"line":225},[222,2128,2129],{"class":674},"const",[222,2131,2132],{"class":756}," requested",[222,2134,2135],{"class":674}," =",[222,2137,2138],{"class":387}," msg.payload.requested;\n",[222,2140,2141,2143,2146,2148,2151,2154],{"class":224,"line":231},[222,2142,2129],{"class":674},[222,2144,2145],{"class":756}," now",[222,2147,2135],{"class":674},[222,2149,2150],{"class":387}," Date.",[222,2152,2153],{"class":472},"now",[222,2155,2156],{"class":387},"();\n",[222,2158,2159],{"class":224,"line":237},[222,2160,463],{"emptyLinePlaceholder":462},[222,2162,2163,2165,2168,2170,2173,2176,2179,2182],{"class":224,"line":243},[222,2164,2129],{"class":674},[222,2166,2167],{"class":756}," requestedTime",[222,2169,2135],{"class":674},[222,2171,2172],{"class":674}," new",[222,2174,2175],{"class":472}," Date",[222,2177,2178],{"class":387},"(requested).",[222,2180,2181],{"class":472},"getTime",[222,2183,2156],{"class":387},[222,2185,2186,2188,2191,2193,2196,2199],{"class":224,"line":249},[222,2187,2129],{"class":674},[222,2189,2190],{"class":756}," difference",[222,2192,2135],{"class":674},[222,2194,2195],{"class":387}," now ",[222,2197,2198],{"class":674},"-",[222,2200,2201],{"class":387}," requestedTime;\n",[222,2203,2204],{"class":224,"line":255},[222,2205,463],{"emptyLinePlaceholder":462},[222,2207,2208,2210,2213,2215,2217,2219,2221,2224],{"class":224,"line":261},[222,2209,2129],{"class":674},[222,2211,2212],{"class":756}," oldRequestThreshold",[222,2214,2135],{"class":674},[222,2216,1450],{"class":387},[222,2218,1453],{"class":472},[222,2220,743],{"class":387},[222,2222,2223],{"class":479},"'oldRequestThreshold'",[222,2225,276],{"class":387},[222,2227,2228,2230,2233,2235,2237,2239,2241,2244],{"class":224,"line":267},[222,2229,2129],{"class":674},[222,2231,2232],{"class":756}," veryOldRequestThreshold",[222,2234,2135],{"class":674},[222,2236,1450],{"class":387},[222,2238,1453],{"class":472},[222,2240,743],{"class":387},[222,2242,2243],{"class":479},"'veryOldRequestThreshold'",[222,2245,276],{"class":387},[222,2247,2248,2250,2253,2255,2257,2259,2261,2264],{"class":224,"line":273},[222,2249,2129],{"class":674},[222,2251,2252],{"class":756}," alertActivationThreshold",[222,2254,2135],{"class":674},[222,2256,1450],{"class":387},[222,2258,1453],{"class":472},[222,2260,743],{"class":387},[222,2262,2263],{"class":479},"'alertActivationThreshold'",[222,2265,276],{"class":387},[222,2267,2268],{"class":224,"line":488},[222,2269,463],{"emptyLinePlaceholder":462},[222,2271,2272,2275,2278,2281,2284,2287,2290,2293,2296],{"class":224,"line":521},[222,2273,2274],{"class":674},"if",[222,2276,2277],{"class":387}," (difference ",[222,2279,2280],{"class":674},">",[222,2282,2283],{"class":387}," (veryOldRequestThreshold ",[222,2285,2286],{"class":674},"*",[222,2288,2289],{"class":756}," 60",[222,2291,2292],{"class":674}," *",[222,2294,2295],{"class":756}," 1000",[222,2297,2298],{"class":387},")) {\n",[222,2300,2301,2304,2306,2309],{"class":224,"line":549},[222,2302,2303],{"class":387},"   msg.payload.class ",[222,2305,476],{"class":674},[222,2307,2308],{"class":479}," 'older'",[222,2310,947],{"class":387},[222,2312,2313,2316,2318,2320,2323,2325,2327,2329,2331],{"class":224,"line":555},[222,2314,2315],{"class":674},"   if",[222,2317,2277],{"class":387},[222,2319,2280],{"class":674},[222,2321,2322],{"class":387}," (alertActivationThreshold ",[222,2324,2286],{"class":674},[222,2326,2289],{"class":756},[222,2328,2292],{"class":674},[222,2330,2295],{"class":756},[222,2332,2298],{"class":387},[222,2334,2335,2338,2340,2343],{"class":224,"line":565},[222,2336,2337],{"class":387},"       msg.payload.alert ",[222,2339,476],{"class":674},[222,2341,2342],{"class":756}," true",[222,2344,947],{"class":387},[222,2346,2347],{"class":224,"line":574},[222,2348,2349],{"class":387},"   }\n",[222,2351,2352,2355],{"class":224,"line":583},[222,2353,2354],{"class":674},"   return",[222,2356,1526],{"class":387},[222,2358,2359],{"class":224,"line":588},[222,2360,998],{"class":387},[222,2362,2363,2366,2369,2371,2373,2376,2378,2380,2382,2384],{"class":224,"line":621},[222,2364,2365],{"class":674},"else",[222,2367,2368],{"class":674}," if",[222,2370,2277],{"class":387},[222,2372,2280],{"class":674},[222,2374,2375],{"class":387}," (oldRequestThreshold ",[222,2377,2286],{"class":674},[222,2379,2289],{"class":756},[222,2381,2292],{"class":674},[222,2383,2295],{"class":756},[222,2385,2298],{"class":387},[222,2387,2388,2390,2392,2395],{"class":224,"line":627},[222,2389,2303],{"class":387},[222,2391,476],{"class":674},[222,2393,2394],{"class":479}," 'old'",[222,2396,947],{"class":387},[222,2398,2399,2401,2403,2405,2407,2409,2411,2413,2415],{"class":224,"line":636},[222,2400,2315],{"class":674},[222,2402,2277],{"class":387},[222,2404,2280],{"class":674},[222,2406,2322],{"class":387},[222,2408,2286],{"class":674},[222,2410,2289],{"class":756},[222,2412,2292],{"class":674},[222,2414,2295],{"class":756},[222,2416,2298],{"class":387},[222,2418,2419,2421,2423,2425],{"class":224,"line":646},[222,2420,2337],{"class":387},[222,2422,476],{"class":674},[222,2424,2342],{"class":756},[222,2426,947],{"class":387},[222,2428,2429],{"class":224,"line":656},[222,2430,2349],{"class":387},[222,2432,2433,2435],{"class":224,"line":661},[222,2434,2354],{"class":674},[222,2436,1526],{"class":387},[222,2438,2439],{"class":224,"line":671},[222,2440,998],{"class":387},[222,2442,2443,2445],{"class":224,"line":684},[222,2444,2365],{"class":674},[222,2446,681],{"class":387},[222,2448,2449,2451,2453,2455,2457,2459,2461,2463,2465],{"class":224,"line":693},[222,2450,2315],{"class":674},[222,2452,2277],{"class":387},[222,2454,2280],{"class":674},[222,2456,2322],{"class":387},[222,2458,2286],{"class":674},[222,2460,2289],{"class":756},[222,2462,2292],{"class":674},[222,2464,2295],{"class":756},[222,2466,2298],{"class":387},[222,2468,2469,2471,2473,2475],{"class":224,"line":701},[222,2470,2337],{"class":387},[222,2472,476],{"class":674},[222,2474,2342],{"class":756},[222,2476,947],{"class":387},[222,2478,2479],{"class":224,"line":713},[222,2480,2349],{"class":387},[222,2482,2483,2485,2487,2490],{"class":224,"line":719},[222,2484,2303],{"class":387},[222,2486,476],{"class":674},[222,2488,2489],{"class":479}," 'normal'",[222,2491,947],{"class":387},[222,2493,2494,2496],{"class":224,"line":725},[222,2495,2354],{"class":674},[222,2497,1526],{"class":387},[222,2499,2500],{"class":224,"line":731},[222,2501,998],{"class":387},[171,2503,2504,2507,2521,2524,2527,2541,2544,2555,2558],{"start":237},[46,2505,2506],{},"Connect the link-in node to the function node.",[46,2508,2509,2510],{},"Drag a date time formatter node onto the canvas. Name it \"Format Requested Time\" and double-click it to configure.\n",[43,2511,2512,2515],{},[46,2513,2514],{},"Set outputFrom to fromNow.",[46,2516,2517,2518,289],{},"Set both input and output to ",[131,2519,2520],{},"msg.payload.requested",[46,2522,2523],{},"Connect the function node to the date time formatter node.",[46,2525,2526],{},"Drag a link-out node and connect it to the function node.",[46,2528,2529,2530,2533,2534],{},"Drag a switch node onto the canvas. Name it \"Check if Acknowledged is null\". Set the property to ",[131,2531,2532],{},"msg.payload.acknowledged",", and add the following conditions:\n",[43,2535,2536,2539],{},[46,2537,2538],{},"is null",[46,2540,1635],{},[46,2542,2543],{},"Connect the function node to the switch node.",[46,2545,2546,2547],{},"Drag a second date time formatter node onto the canvas. Name it \"Format Acknowledged Time\".\n",[43,2548,2549,2551],{},[46,2550,2514],{},[46,2552,2517,2553,289],{},[131,2554,2532],{},[46,2556,2557],{},"Connect the second date time formatter node to the second output of the switch node (\"Check if Acknowledged is null\").",[46,2559,2560,2561,2564],{},"Drag another switch node onto the canvas. Name it \"Check if Resolved is null\". set the property to ",[131,2562,2563],{},"msg.payload.resolved",". Add the following conditions:",[43,2566,2567,2569],{},[46,2568,2538],{},[46,2570,1635],{},[171,2572,2573,2576,2581,2584,2587,2590],{"start":549},[46,2574,2575],{},"Connect this switch node (\"Check if Resolved is null\") to the output of the second date time formatter (\"Format Acknowledged Time\") node and to the first output of the first switch node (\"Check if Acknowledged is null\").",[46,2577,2578,2579,289],{},"Drag a third date time formatter node onto the canvas. Name it \"Format Resolved Time\" and set outputFrom to fromNow. Set both input and output to ",[131,2580,2563],{},[46,2582,2583],{},"Connect this third date time formatter node to the second output of the second switch node (\"Check if Resolved is null\") .",[46,2585,2586],{},"Drag a link-out node and connect it to the third date time formatter node.",[46,2588,2589],{},"Drag another link-out node and connect it to the first output of the second switch node. Name it \"Link to First Switch Output\".",[46,2591,1649],{},[10,2593,2594,2598],{},[293,2595],{"alt":2596,"dataZoomable":218,"src":2597},"Dashboard view displaying a highlighted request entry with visual emphasis based on request age.","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Fhighlighted-requst.png",[298,2599,2600],{},"Dashboard showing a request visually highlighted based on how long ago it was made, with applied styling and alert classification.",[10,2602,2603,2607],{},[293,2604],{"alt":2605,"dataZoomable":218,"src":2606},"Node-RED flow that includes nodes for assigning visual alert classes and formatting timestamps using relative time.","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Fvisual-alert-and-timestamp-formatting.png",[298,2608,2609],{},"Flow for setting visual alert classes and formatting timestamps like \"5 minutes ago\" to enhance clarity and urgency of displayed requests.",[35,2611,69],{"id":2612},"highlight-requests-with-css-&-add-buttons-to-update-request-status",[10,2614,2615,2616,112,2619,2622,2623,2626],{},"We have the data prepared, the class property added to each request message, and the timestamp formatted for better readability. In this section, we will add ",[30,2617,2618],{},"'Resolve'",[30,2620,2621],{},"'Acknowledge'"," buttons for each request to update its status and apply CSS classes based on the ",[131,2624,2625],{},"status"," property for visual highlighting.",[171,2628,2629,2636,2648,2701,2715,2727,2765,2785,2793],{},[46,2630,346,2631,2633,2634,1936],{},[30,2632,2067],{}," node onto the canvas and connect it to the last ",[30,2635,1941],{},[46,2637,2638,2639,2564,2641],{},"Drag a switch node, name it \"Is acknowledged null?\", and set the property to ",[131,2640,2532],{},[43,2642,2643,2645],{},[46,2644,2538],{},[46,2646,2647],{},"otherwise",[46,2649,346,2650,2653,2654,2657,2658,2660,2661],{},[30,2651,2652],{},"template node",", name it ",[30,2655,2656],{},"\"Build ack link\"",", set the property to ",[131,2659,2532],{},", and add the following Mustache:",[213,2662,2664],{"className":378,"code":2663,"language":380,"meta":218,"style":218},"\u003Ca href=\"\u002Fdashboard\u002Flines?line={{line}}&action=ack&request={{payload.rowid}}\" style=\"color: #000000\" class=\"{{payload.class}}\">ACKNOWLEDGE\u003C\u002Fa>\n",[131,2665,2666],{"__ignoreMap":218},[222,2667,2668,2670,2672,2675,2677,2680,2682,2684,2687,2689,2691,2694,2697,2699],{"class":224,"line":225},[222,2669,388],{"class":387},[222,2671,14],{"class":391},[222,2673,2674],{"class":472}," href",[222,2676,476],{"class":387},[222,2678,2679],{"class":479},"\"\u002Fdashboard\u002Flines?line={{line}}&action=ack&request={{payload.rowid}}\"",[222,2681,1889],{"class":472},[222,2683,476],{"class":387},[222,2685,2686],{"class":479},"\"color: #000000\"",[222,2688,473],{"class":472},[222,2690,476],{"class":387},[222,2692,2693],{"class":479},"\"{{payload.class}}\"",[222,2695,2696],{"class":387},">ACKNOWLEDGE\u003C\u002F",[222,2698,14],{"class":391},[222,2700,395],{"class":387},[46,2702,279,2703,2705,2706,2708,2709,2712,2713,1936],{},[30,2704,2067],{}," node to the ",[30,2707,1576],{}," node. Connect the ",[30,2710,2711],{},"first output"," of the switch node (\"Is acknowledged null?\") to the input of the ",[30,2714,392],{},[46,2716,2717,2718,2720,2721],{},"Drag another switch node, name it \"Is resolved null?\", set the property to ",[131,2719,2563],{},", and add the following conditions:",[43,2722,2723,2725],{},[46,2724,2538],{},[46,2726,2647],{},[46,2728,346,2729,2731,2732,2660,2734],{},[30,2730,2652],{},", give it name \"Build res link\",  set the property to ",[131,2733,2563],{},[213,2735,2737],{"className":378,"code":2736,"language":380,"meta":218,"style":218},"\u003Ca href=\"\u002Fdashboard\u002Flines?line={{line}}&action=res&request={{payload.rowid}}\" style=\"color: #000000\">RESOLVE\u003C\u002Fa>\n",[131,2738,2739],{"__ignoreMap":218},[222,2740,2741,2743,2745,2747,2749,2752,2754,2756,2758,2761,2763],{"class":224,"line":225},[222,2742,388],{"class":387},[222,2744,14],{"class":391},[222,2746,2674],{"class":472},[222,2748,476],{"class":387},[222,2750,2751],{"class":479},"\"\u002Fdashboard\u002Flines?line={{line}}&action=res&request={{payload.rowid}}\"",[222,2753,1889],{"class":472},[222,2755,476],{"class":387},[222,2757,2686],{"class":479},[222,2759,2760],{"class":387},">RESOLVE\u003C\u002F",[222,2762,14],{"class":391},[222,2764,395],{"class":387},[46,2766,2767,2768,2771,2772,2775,2776,2778,2779,2782,2783,1936],{},"Connect the input of this second switch node (\"Is resolved null?\") to the ",[30,2769,2770],{},"second output"," of the previous switch node (",[131,2773,2774],{},"acknowledged"," switch), then connect the ",[30,2777,2711],{}," of the ",[131,2780,2781],{},"resolved"," switch node to the input of the second ",[30,2784,392],{},[46,2786,346,2787,2789,2790,2792],{},[30,2788,1941],{}," node and connect both outputs of the ",[131,2791,2781],{}," switch node to this link-out.",[46,2794,1649],{},[10,2796,2797,2801],{},[293,2798],{"alt":2799,"dataZoomable":218,"src":2800},"Adding 'Acknowledged' and 'Resolved' buttons for each request in the table","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Fadd-request-update-button.png",[298,2802,2803],{},"Adding action buttons to update the status of each request directly from the dashboard table.",[92,2805,2807],{"id":2806},"adding-a-mechanism-to-update-the-status-of-a-request-in-the-database","Adding a Mechanism to Update the Status of a Request in the Database",[171,2809,2810,2817,2840,2849,2868,2871,2903,2931],{},[46,2811,2812,2813,2816],{},"Drag ",[30,2814,2815],{},"ui event widget"," onto the canvas and configure it with the correct ui base.",[46,2818,2812,2819,2821,2822],{},[30,2820,1536],{}," onto the canvas and add the following element:",[43,2823,2824,2832],{},[46,2825,203,2826,207,2829],{},[131,2827,2828],{},"msg.params",[131,2830,2831],{},"{}",[46,2833,203,2834,207,2837],{},[131,2835,2836],{},"msg.params.$request",[131,2838,2839],{},"msg.payload.query.request",[46,2841,2812,2842,2845,2846,289],{},[30,2843,2844],{},"Date\u002FTime Formatter node"," onto the canvas and set input format to \"timestamp: milliseconds since epoch\" and output to ",[131,2847,2848],{},"msg.now",[46,2850,2812,2851,2853,2854,2857,2858],{},[30,2852,1387],{}," onto the canvas, set property to ",[131,2855,2856],{},"msg.query.action",", and add the following conditions to check against:",[43,2859,2860,2863,2866],{},[46,2861,2862],{},"== ack",[46,2864,2865],{},"== res",[46,2867,2647],{},[46,2869,2870],{},"Drag two SQLite nodes onto the canvas, select the correct database configuration for both, and set the query type to 'Prepared Statement'.",[46,2872,2873,2874,2877,2878],{},"For the ",[30,2875,2876],{},"first",", set the following sql query:",[213,2879,2881],{"className":215,"code":2880,"language":217,"meta":218,"style":218},"UPDATE requests \nSET acknowledged = $now\nWHERE rowid = $request \nAND acknowledged IS NULL\n",[131,2882,2883,2888,2893,2898],{"__ignoreMap":218},[222,2884,2885],{"class":224,"line":225},[222,2886,2887],{},"UPDATE requests \n",[222,2889,2890],{"class":224,"line":231},[222,2891,2892],{},"SET acknowledged = $now\n",[222,2894,2895],{"class":224,"line":237},[222,2896,2897],{},"WHERE rowid = $request \n",[222,2899,2900],{"class":224,"line":243},[222,2901,2902],{},"AND acknowledged IS NULL\n",[46,2904,2873,2905,2877,2908],{},[30,2906,2907],{},"second",[213,2909,2911],{"className":215,"code":2910,"language":217,"meta":218,"style":218},"UPDATE requests \nSET resolved = $now\nWHERE rowid = $request \nAND resolved IS NULL \n",[131,2912,2913,2917,2922,2926],{"__ignoreMap":218},[222,2914,2915],{"class":224,"line":225},[222,2916,2887],{},[222,2918,2919],{"class":224,"line":231},[222,2920,2921],{},"SET resolved = $now\n",[222,2923,2924],{"class":224,"line":237},[222,2925,2897],{},[222,2927,2928],{"class":224,"line":243},[222,2929,2930],{},"AND resolved IS NULL\n",[46,2932,279,2933,207,2935,782,2937,207,2939,782,2942,207,2945,2947],{},[30,2934,2815],{},[30,2936,1536],{},[30,2938,1536],{},[30,2940,2941],{},"date\u002Ftime formatter node",[30,2943,2944],{},"date\u002Ftime formatter",[30,2946,1387],{},", and:",[43,2949,2950,2959],{},[46,2951,2952,2955,2956],{},[30,2953,2954],{},"switch node first output"," to first ",[30,2957,2958],{},"first sqlite node",[46,2960,2961,2964,2965],{},[30,2962,2963],{},"switch node second output"," to second ",[30,2966,2967],{},"second sqlite node",[10,2969,2970,2974,2977],{},[293,2971],{"alt":2972,"dataZoomable":218,"src":2973},"Flow for the mechanism to update request status","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Fupdate-request-status.png",[2975,2976],"br",{},[298,2978,2979],{},"Flow that handles the update of request status based on user actions (Acknowledged or Resolved).",[92,2981,63],{"id":2982},"render-request-data-in-a-table",[10,2984,2985,2986,2989],{},"Now, let's display the prepared data in a table. To do this, we'll use a ",[30,2987,2988],{},"ui_table"," widget. However, before displaying, we need to convert the data back into an array, as we are currently spilling array data retrieved from the database into a single message.",[171,2991,2992,2999,3010,3017,3023,3028,3039,3053,3060,3086],{},[46,2993,346,2994,1942,2996,1936],{},[30,2995,2067],{},[30,2997,2998],{},"last link-out",[46,3000,346,3001,1942,3004,3006,3007,289],{},[30,3002,3003],{},"join",[30,3005,2067],{}," node. Double-click on the join node and set the mode to ",[30,3008,3009],{},"automatic",[46,3011,1402,3012,3014,3015,1936],{},[30,3013,1941],{}," node and connect it to the ",[30,3016,3003],{},[46,3018,346,3019,3014,3021,1936],{},[30,3020,2067],{},[30,3022,2998],{},[46,3024,346,3025,3027],{},[30,3026,2988],{}," widget onto the canvas and double-click to configure.",[46,3029,3030,3031,3034,3035,3038],{},"Create a ",[30,3032,3033],{},"new group"," on the ",[298,3036,3037],{},"lines"," page.",[46,3040,203,3041,207,3044,112,3047,207,3050,289],{},[30,3042,3043],{},"Action",[131,3045,3046],{},"replace",[30,3048,3049],{},"Interaction",[131,3051,3052],{},"none",[46,3054,3055,3056,3059],{},"Untick the ",[30,3057,3058],{},"Auto Calculate Columns"," option.",[46,3061,3062,3063],{},"Add the following column elements:",[43,3064,3065,3068,3071,3074,3077,3080,3083],{},[46,3066,3067],{},"Key: rowid, Label: Request, Align: Left, Type: Text",[46,3069,3070],{},"Key: line, Label: Line, Align: Left, Type: Text",[46,3072,3073],{},"Key: support, Label: Support, Align: Left, Type: Text",[46,3075,3076],{},"Key: requested, Label: Requested, Align: Left, Type: HTML",[46,3078,3079],{},"Key: acknowledged, Label: Acknowledged, Align: Left, Type: HTML",[46,3081,3082],{},"Key: resolved, Label: Resolved, Align: Left, Type: HTML",[46,3084,3085],{},"Key: notes, Label: Notes, Align: Left, Type: Text",[46,3087,1649],{},[10,3089,3090,3094],{},[293,3091],{"alt":3092,"dataZoomable":218,"src":3093},"Render Data on table\"","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Frender-data-in-table.png",[298,3095,3096],{},"Render Data on table.",[35,3098,72],{"id":3099},"create-new-request-submission-flow",[10,3101,3102],{},"To create a flow that allows users to submit a request, follow these steps to set up the necessary UI elements, store the request details, validate input, and store the data in a database.",[171,3104,3105,3111,3126,3134,3145,3151,3166,3169,3178,3223,3229],{},[46,3106,3107,3108,3110],{},"Drag the ",[30,3109,349],{}," widget onto the canvas and configure it with the correct UI settings.",[46,3112,346,3113,3115,3116],{},[30,3114,1536],{}," to retrieve the department list and name it \"Show Dropdown Options\". Add the following:",[43,3117,3118],{},[46,3119,203,3120,207,3123,289],{},[131,3121,3122],{},"msg.ui_update.options",[131,3124,3125],{},"global.departments",[46,3127,3128,3129,3131,3132,1936],{},"Create a new group for the dropdown widget on the lines page. Connect the ",[30,3130,1536],{}," to the input of the dropdown widget, then link it to the ",[30,3133,349],{},[46,3135,1402,3136,3138,3139,207,3141,3144],{},[30,3137,1536],{}," and set ",[131,3140,1547],{},[131,3142,3143],{},"msg.store[msg._client.socketId].support",". Name it \"Store support (department) to context store\".",[46,3146,346,3147,3150],{},[30,3148,3149],{},"text input widget"," for the notes field. Create a group for it in the lines page.",[46,3152,3153,3154,3156,3157],{},"Add another ",[30,3155,1536],{}," to store the notes in the global context and name it \"Store notes to context store\":",[43,3158,3159],{},[46,3160,203,3161,207,3163,289],{},[131,3162,1547],{},[131,3164,3165],{},"msg.store[msg._client.socketId].notes",[46,3167,3168],{},"Connect the change node (\"Store support (department) to context store\") to the input of the dropdown widget and connect the change node (\"Store notes to context store\") to the text input widget.",[46,3170,3107,3171,3174,3175,3177],{},[30,3172,3173],{},"button widget",", label it \"Request Support,\" and connect it to the ",[30,3176,1536],{}," that updates the UI with the department list.",[46,3179,1976,3180,3182,3183],{},[30,3181,1536],{}," to store the request details in the global context and name it \"Retrieve entered support request data\":",[43,3184,3185,3192,3200,3207,3215],{},[46,3186,203,3187,207,3190,289],{},[131,3188,3189],{},"msg.request",[131,3191,2831],{},[46,3193,203,3194,3197,3198,289],{},[131,3195,3196],{},"msg.request.support"," to the value ",[131,3199,3143],{},[46,3201,203,3202,3197,3205,289],{},[131,3203,3204],{},"msg.request.notes",[131,3206,3165],{},[46,3208,203,3209,3197,3212,289],{},[131,3210,3211],{},"msg.request.line",[131,3213,3214],{},"msg.store[msg._client.socketId].line",[46,3216,203,3217,3197,3220,289],{},[131,3218,3219],{},"msg.request.reference",[131,3221,3222],{},"msg.store[msg._client.socketId].reference",[46,3224,3225,3226,3228],{},"Drag the switch node onto the canvas and set property to ",[131,3227,3189],{}," and add condtion to check \"is not null\".",[46,3230,346,3231,3233],{},[30,3232,1564],{}," and add the following code. Name it \"Does department and notes are not empty?\":",[213,3235,3237],{"className":1433,"code":3236,"language":1435,"meta":218,"style":218},"let request = msg.request;\nif (typeof request !== \"object\" || request === null) {\n   msg.payload = \"Request must be an object.\";\n   return [null, null, msg];\n} else if (!request.hasOwnProperty(\"support\")) {\n   msg.payload = \"Please select the appropriate department for the request.\";\n   return [null, msg, null];\n} else if (!request.hasOwnProperty(\"notes\") || typeof request.notes !== \"string\" || request.notes.trim() === \"\") {\n   msg.payload = \"Please add notes to provide more context on the request.\";\n   return [null, null, msg];\n} else {\n   return [msg, null, null];\n}\n",[131,3238,3239,3251,3282,3294,3311,3338,3349,3364,3418,3429,3443,3451,3466],{"__ignoreMap":218},[222,3240,3241,3243,3246,3248],{"class":224,"line":225},[222,3242,1442],{"class":674},[222,3244,3245],{"class":387}," request ",[222,3247,476],{"class":674},[222,3249,3250],{"class":387}," msg.request;\n",[222,3252,3253,3255,3258,3261,3263,3266,3269,3272,3274,3277,3280],{"class":224,"line":231},[222,3254,2274],{"class":674},[222,3256,3257],{"class":387}," (",[222,3259,3260],{"class":674},"typeof",[222,3262,3245],{"class":387},[222,3264,3265],{"class":674},"!==",[222,3267,3268],{"class":479}," \"object\"",[222,3270,3271],{"class":674}," ||",[222,3273,3245],{"class":387},[222,3275,3276],{"class":674},"===",[222,3278,3279],{"class":756}," null",[222,3281,750],{"class":387},[222,3283,3284,3287,3289,3292],{"class":224,"line":237},[222,3285,3286],{"class":387},"   msg.payload ",[222,3288,476],{"class":674},[222,3290,3291],{"class":479}," \"Request must be an object.\"",[222,3293,947],{"class":387},[222,3295,3296,3298,3301,3304,3306,3308],{"class":224,"line":243},[222,3297,2354],{"class":674},[222,3299,3300],{"class":387}," [",[222,3302,3303],{"class":756},"null",[222,3305,782],{"class":387},[222,3307,3303],{"class":756},[222,3309,3310],{"class":387},", msg];\n",[222,3312,3313,3316,3318,3320,3322,3325,3328,3331,3333,3336],{"class":224,"line":249},[222,3314,3315],{"class":387},"} ",[222,3317,2365],{"class":674},[222,3319,2368],{"class":674},[222,3321,3257],{"class":387},[222,3323,3324],{"class":674},"!",[222,3326,3327],{"class":387},"request.",[222,3329,3330],{"class":472},"hasOwnProperty",[222,3332,743],{"class":387},[222,3334,3335],{"class":479},"\"support\"",[222,3337,2298],{"class":387},[222,3339,3340,3342,3344,3347],{"class":224,"line":255},[222,3341,3286],{"class":387},[222,3343,476],{"class":674},[222,3345,3346],{"class":479}," \"Please select the appropriate department for the request.\"",[222,3348,947],{"class":387},[222,3350,3351,3353,3355,3357,3360,3362],{"class":224,"line":261},[222,3352,2354],{"class":674},[222,3354,3300],{"class":387},[222,3356,3303],{"class":756},[222,3358,3359],{"class":387},", msg, ",[222,3361,3303],{"class":756},[222,3363,1508],{"class":387},[222,3365,3366,3368,3370,3372,3374,3376,3378,3380,3382,3385,3387,3389,3392,3395,3397,3400,3402,3405,3408,3411,3413,3416],{"class":224,"line":267},[222,3367,3315],{"class":387},[222,3369,2365],{"class":674},[222,3371,2368],{"class":674},[222,3373,3257],{"class":387},[222,3375,3324],{"class":674},[222,3377,3327],{"class":387},[222,3379,3330],{"class":472},[222,3381,743],{"class":387},[222,3383,3384],{"class":479},"\"notes\"",[222,3386,788],{"class":387},[222,3388,768],{"class":674},[222,3390,3391],{"class":674}," typeof",[222,3393,3394],{"class":387}," request.notes ",[222,3396,3265],{"class":674},[222,3398,3399],{"class":479}," \"string\"",[222,3401,3271],{"class":674},[222,3403,3404],{"class":387}," request.notes.",[222,3406,3407],{"class":472},"trim",[222,3409,3410],{"class":387},"() ",[222,3412,3276],{"class":674},[222,3414,3415],{"class":479}," \"\"",[222,3417,750],{"class":387},[222,3419,3420,3422,3424,3427],{"class":224,"line":273},[222,3421,3286],{"class":387},[222,3423,476],{"class":674},[222,3425,3426],{"class":479}," \"Please add notes to provide more context on the request.\"",[222,3428,947],{"class":387},[222,3430,3431,3433,3435,3437,3439,3441],{"class":224,"line":488},[222,3432,2354],{"class":674},[222,3434,3300],{"class":387},[222,3436,3303],{"class":756},[222,3438,782],{"class":387},[222,3440,3303],{"class":756},[222,3442,3310],{"class":387},[222,3444,3445,3447,3449],{"class":224,"line":521},[222,3446,3315],{"class":387},[222,3448,2365],{"class":674},[222,3450,681],{"class":387},[222,3452,3453,3455,3458,3460,3462,3464],{"class":224,"line":549},[222,3454,2354],{"class":674},[222,3456,3457],{"class":387}," [msg, ",[222,3459,3303],{"class":756},[222,3461,782],{"class":387},[222,3463,3303],{"class":756},[222,3465,1508],{"class":387},[222,3467,3468],{"class":224,"line":555},[222,3469,998],{"class":387},[171,3471,3472,3482,3489,3494,3500,3505,3510,3513],{"start":549},[46,3473,3474,3475,3478,3479,289],{},"Connect the change node to switch node and switch node to function node. Connect the function node's first output to a ",[30,3476,3477],{},"Date\u002FTime Formatter"," node. Set the input to 'Timestamp (milliseconds since epoch)' and the output to ",[131,3480,3481],{},"msg.request.time",[46,3483,346,3484,3138,3486,3488],{},[30,3485,1536],{},[131,3487,1547],{}," to \"Are you sure you want to submit a request?\". Name it \"Set Confirmation message\".",[46,3490,3491,3492,1936],{},"Connect the change node (\"Set Confirmation message\") to the ",[30,3493,3477],{},[46,3495,346,3496,3499],{},[30,3497,3498],{},"ui-notification widget",", configure it with the correct UI, and set the position to center, checked the checkbox for both allow mnaual dismisal and all amnual confirmation, change close to \"Cancel\".",[46,3501,3491,3502,3504],{},[30,3503,1324],{}," widget.",[46,3506,1402,3507,3509],{},[30,3508,3498],{},", configure it with the correct UI, and set the position to center.",[46,3511,3512],{},"connect it to the second output of the function node.",[46,3514,1976,3515,3517,3518,3520],{},[30,3516,1387],{}," and set the property to ",[131,3519,1547],{}," with the condition:",[43,3522,3523],{},[46,3524,3525,3526,3529],{},"== ",[131,3527,3528],{},"confirm_clicked","\nName it \"Is confirm clicked?\"",[171,3531,3532],{"start":636},[46,3533,346,3534,3536],{},[30,3535,1536],{}," and add the following elements and give it name \"Set Params\":",[43,3538,3539,3545,3552,3559,3566],{},[46,3540,203,3541,207,3543,289],{},[131,3542,2828],{},[131,3544,2831],{},[46,3546,203,3547,3197,3549,289],{},[131,3548,1830],{},[131,3550,3551],{},"msg.request.linet",[46,3553,203,3554,3197,3557,289],{},[131,3555,3556],{},"msg.params.$support",[131,3558,3196],{},[46,3560,203,3561,3197,3564,289],{},[131,3562,3563],{},"msg.params.$time",[131,3565,3481],{},[46,3567,203,3568,3197,3571,289],{},[131,3569,3570],{},"msg.params.$notes",[131,3572,3204],{},[171,3574,3575,3578,3584],{"start":646},[46,3576,3577],{},"Connect the change node (\"Set Params\") to the switch node (\"Is confirm clicked?\").",[46,3579,346,3580,3583],{},[30,3581,3582],{},"sqlite node",", select the correct database configuration, and choose SQL Query via \"Prepared Statement\" and connect that sqlite node to the input change node (\"Set Params\")",[46,3585,346,3586,3588],{},[30,3587,1536],{}," and set the following, give it name \"Clear entered request data\":",[43,3590,3591,3596],{},[46,3592,3593,3594,289],{},"Delete ",[131,3595,3143],{},[46,3597,3593,3598,289],{},[131,3599,3165],{},[171,3601,3602,3609,3614,3619],{"start":671},[46,3603,279,3604,3606,3607,289],{},[30,3605,1536],{},"* (\"Clear entered request data\") to the ",[30,3608,3582],{},[46,3610,346,3611,3613],{},[30,3612,1973],{}," and connect it to the last change node.",[46,3615,346,3616,3618],{},[30,3617,1969],{}," and connect it to the last link-out node.",[46,3620,3621,3622,3624],{},"Drag two ",[30,3623,2053],{}," and configure them as follows:",[43,3626,3627,3636],{},[46,3628,3629,3630,3632,3633,289],{},"First change node: Set ",[131,3631,1547],{}," to an empty array ",[131,3634,3635],{},"[]",[46,3637,3638,3639,2030,3641,289],{},"Second change node: Set ",[131,3640,1547],{},[131,3642,2033],{},[171,3644,3645,3648,3657],{"start":713},[46,3646,3647],{},"Connect the change nodes to the link-in node to complete the flow.",[46,3649,3650,3651,3654,3655,289],{},"Connect the first change node to the ",[30,3652,3653],{},"ui dropdown widget"," and the second to the ",[30,3656,3149],{},[46,3658,1649],{},[10,3660,3661,3665],{},[293,3662],{"alt":3663,"dataZoomable":218,"src":3664},"Node-RED flow showing the logic for validating and storing user-submitted support requests.","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Fsubmit-request-flow.png",[298,3666,3667],{},"Node-RED flow to handle request submission, including form validation, timestamp formatting, and SQL database insertion.",[10,3669,3670],{},"We have now successfully built one part of the Andon task dashboard. You can open the line view in the dashboard and check whether you can submit a request, mark it as acknowledged, and resolve it.",[10,3672,3673,3677],{},[293,3674],{"alt":3675,"dataZoomable":218,"src":3676},"UI form with dropdown, text input, and a submit button labeled \"Request Support.\"","\u002Fblog\u002F2025\u002F06\u002Fimages\u002Fsubmit-form.png",[298,3678,3679],{},"Dashboard UI where users select a department, enter notes, and submit a support request for the production line.",[35,3681,3683],{"id":3682},"up-next","Up Next",[10,3685,3686],{},"Up until now, the focus has been on building the core functionality of the Andon Task Manager dashboard, including the Lines page. Design and layout have not been the priority. In the next part, you will learn how to enhance the visual design, improve usability, and create a dedicated page and menu for departments.",[10,3688,3689],{},"Later, we will guide you through building the Admin page for the Andon Task Manager dashboard—enabling request management, department configuration, and overall system control.",[919,3691,3692],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":218,"searchDepth":231,"depth":231,"links":3694},[3695,3705,3709,3710],{"id":37,"depth":231,"text":38,"children":3696},[3697,3698,3699,3700,3701,3702,3703,3704],{"id":94,"depth":237,"text":95},{"id":166,"depth":237,"text":48},{"id":310,"depth":237,"text":51},{"id":338,"depth":237,"text":54},{"id":1255,"depth":237,"text":57},{"id":1661,"depth":237,"text":1662},{"id":1958,"depth":237,"text":1959},{"id":2097,"depth":237,"text":66},{"id":2612,"depth":231,"text":69,"children":3706},[3707,3708],{"id":2806,"depth":237,"text":2807},{"id":2982,"depth":237,"text":63},{"id":3099,"depth":231,"text":72},{"id":3682,"depth":231,"text":3683},"In Part 1, we introduced the concept of an Andon Task Manager—designed to streamline issue reporting and resolution on the factory floor—and outlined the system’s key features, user roles, and dashboard layout.","md",{"navTitle":5,"excerpt":3714},{"type":7,"value":3715},[3716],[10,3717,12,3718,18],{},[14,3719,17],{"href":16},"\u002Fblog\u002F2025\u002F06\u002Fbuilding-andon-task-manager-dashboard-with-ff",{"title":5,"description":3711},"blog\u002F2025\u002F06\u002Fbuilding-andon-task-manager-dashboard-with-ff","hM95ZnflFrGS0aU0-LpveWWia1nIiqdIVXagpCdaJoU",1780132427880]