How to Build a Manufacturing Dashboard

Manufacturing dashboards offer a real-time snapshot of the state of your production processes. The goal is to provide decision-makers with a fast, easy way to access KPIs and other important insights.
Crucially, this replaces the need to manually crunch numbers each time you need the figures in question.
Any time we set out to build dashboards, the biggest challenge is getting all of the different data that we need into one location, formatted compatibly.
Today, we鈥檙e checking out how 大象传媒 makes it easier than ever to output professional dashboards based on existing data sources.
But, before we get to that, let鈥檚 get to grips with the basics.
What is a manufacturing dashboard
A dashboard is a reporting UI that connects to an external data source. That way, users can view the most up-to-date figures for whatever information we鈥檝e configured our dashboard to display, each time they access it.
The goal is to simultaneously reduce admin workloads and improve access to key information.
In the specific case of a manufacturing dashboard, this will typically concern data relating to our productivity, efficiency, incidents, breakages, costs, or other KPIs.
As such, we might need to draw on a fairly diverse data set - comprising our manufacturing output, incident reports, machinery, and more.
With that in mind鈥
What are we building?
Today, we鈥檙e going to be building a relatively simple, single-screen manufacturing dashboard.
This is going to focus on the current month鈥檚 productivity, quality control, breakages, and incidents - across two manufacturing sites - breaking various KPIs down by machine, location, and product.
Here鈥檚 what we鈥檒l have by the end:
To achieve this, we鈥檙e going to connect to our manufacturing database, which is stored in an external PostgreSQL instance.
This is made up of five interrelated tables, called production, machines, qa, breakages, and incidents.
We鈥檙e going to create custom queries within 大象传媒 to extract the information we need from these - and then use this to populate our display elements. 大象传媒 is the ideal solution for SQL pros who need to turn data into professional UIs.
You might also like our guide to open-source low-code platforms .
Let鈥檚 dive right in.
How to build a manufacturing dashboard in 5 steps
If you haven鈥檛 already, sign up for a free 大象传媒 account.
Join 200,000 teams building workflow apps with 大象传媒
1. Create a 大象传媒 app and connect your data
The first thing we need to do is create a new 大象传媒 application. We can use a template or import an existing app file, but we鈥檙e starting from scratch. We then need to give our app a name and URL extension:
Then, we鈥檙e asked which data source we鈥檇 like to connect to first:
大象传媒 offers dedicated connectors for a huge range of SQL and NoSQL databases, alongside REST, Google Sheets, and our internal database.
As we say though, we鈥檙e using Postgres today.
When we select this, we鈥檒l be prompted to enter our configuration details:
We can then choose which of the constituent tables we want to fetch so that we can manipulate them in 大象传媒:
Straight away, we can use 大象传媒鈥檚 back-end to perform CRUD actions on our tables or alter their schemas, without writing a single query:
But, we鈥檙e primarily going to rely on custom queries to transform and aggregate our data in order to build a manufacturing dashboard.
So, let鈥檚 quickly get to grips with what鈥檚 stored in our tables and how they all relate to each other:
- production represents the product that we create, with a unique id and product, date, and turnaround_time_minutes attributes. This also stores a machine_id which corresponds to the id attribute in the machines table.
- machines store a unique id, location, and machine_name. The possible locations are Texas and Anaheim.
- qa has a unique id and a production_id attribute that relates to the id of the production table. It also stores a description and an attribute called pass_fail which can either be set to Pass or Fail.
- breakages has a unique id, date, and description, as well as a production_id which links it to the production table.
- incidents has a unique id, date, category, and description. It relates to the machines table via an attribute called machine_id.
We鈥檙e going to write several custom queries that aggregate and transform data points from different combinations of these tables to build our various UI elements.
2. Building our summary cards
The first thing we鈥榬e going to do is head to the design section and create a new blank screen. We can call this anything we like, as our dashboard is only going to have one screen anyway. We鈥檝e simply set our URL path to 鈥/鈥.
On this, we鈥檒l first add a headline component.
We鈥檒l open up the bindings drawer to set the text attribute. We can do this with either handlebars or Javascript:
We want our headline to read This Month: followed by the current month in the format 鈥淢惭/驰驰驰驰鈥.
To achieve this, we鈥檙e going to use the following handlebars expression:
1This Month: {{ date now "MM" }}/{{ date now "YYYY"}}
Beneath this, we鈥檙e going to add a container and set its direction to horizontal:
Inside of this, we鈥檒l nest a cards block. This is a preconfigured set of components that we can point at a data source. It will then iterate over this, displaying whichever values we specify for each entry.
When we鈥檙e finished, we鈥檒l have three separate cards blocks, each displaying a single card.
The first one will display the current month鈥檚 overall pass rate from our qa table.
To calculate this, we鈥檒l create a custom query under our Postgres data source:
We鈥檒l call this one QaPassRateByMonth.
We need the pass_fail and production_id attributes from the qa table and the date and product_name from the corresponding production row.
We want to SELECT the numerical month and year from the date attribute and the COUNT of qa rows where pass_fail is set to Pass. We鈥檒l then divide this by the overall COUNT and multiply by 100.
We鈥檒l use a LEFT JOIN statement between p.id and qa.production_id and GROUP BY year and month.
So, our query is:
1SELECT
2
3 EXTRACT(YEAR FROM p.date)::INTEGER AS year,
4
5 EXTRACT(MONTH FROM p.date)::INTEGER AS month,
6
7 COUNT(qa.id) FILTER (WHERE qa.pass_fail = 'Pass') * 100.0 / COUNT(qa.id)::FLOAT AS pass_percentage
8
9FROM production p
10
11LEFT JOIN qa ON p.id = qa.production_id
12
13GROUP BY year, month
14
15ORDER BY year, month;
The data object this returns looks like:
1{
2
3 "year": 2023,
4
5 "month": 9,
6
7 "pass_percentage": 90
8
9}
Now, head back to the design section, and we鈥檒l point the data field for our cards block to our new query:
Now, it鈥檚 displaying three cards - because our sample data goes back three months. In a second, we鈥檒l set a couple of filters so that this only displays the current month.
First, though, we want to set the actual data that our cards display.
We鈥檒l start by binding the title to {{ round Pass Rate Cards block.QaPassRateByMonth.pass_percentage }}%.
We鈥檒l also give it a descriptive subtitle and remove the description entirely:
Finally, we鈥檒l add two filtering statements - based on the month and year attributes in our query response:
This time, we鈥檙e going to use JavaScript for our bindings instead of handlebars. So, we鈥檒l filter the year against:
1var currentDate = new Date();
2
3return currentDate.getFullYear():
For the month, we鈥檒l use:
1var currentDate = new Date();
2
3return currentDate.getMonth() + 1;
We have to add one here because JavaScript uses zero-based counting for dates. So, the index for January is 0.
Here鈥檚 what our filtered cards block looks like:
We鈥檙e going to use the same filtering expressions for our other two cards. Rather than configure these from scratch, we鈥檒l make two duplicates of this existing one:
All we need to do is swap out the data.
The second card will show the total number of stock breakages we鈥檝e had this week.
We鈥檒l create a new query called BreakagesByMonth. This time we want to SELECT to COUNT of rows and the numerical month and year from the breakages table, grouped and ordered by month and year.
1SELECT
2
3 EXTRACT(YEAR FROM date)::INTEGER AS year,
4
5 EXTRACT(MONTH FROM date)::INTEGER AS month,
6
7 COUNT(*)::INTEGER AS breakages_count
8
9FROM breakages
10
11GROUP BY year, month
12
13ORDER BY year, month;
The response schema is:
1{
2
3 "year": 2023,
4
5 "month": 9,
6
7 "breakages_count": 5
8
9}
We鈥檒l also create a query called IncidentsByMonth to retrieve the same information from the incidents table.
1SELECT
2
3 EXTRACT(YEAR FROM date)::INTEGER AS year,
4
5 EXTRACT(MONTH FROM date)::INTEGER AS month,
6
7 COUNT(*)::INTEGER AS incidents_count
8
9FROM incidents
10
11GROUP BY year, month
12
13ORDER BY year, month;
This returns:
1{
2
3 "year": 2023,
4
5 "month": 9,
6
7 "incidents_count": 41
8
9}
Now, we can simply swap the data for our new cards to these queries and update the title bindings and subtitles.
Here鈥檚 our completed row of cards:
3. Productivity breakdowns
Next, we鈥檒l start building some charts. Add another horizontal container, this time giving it a top margin of 16px:
By the time we鈥檙e finished, we鈥檒l have two bar charts inside this, displaying the number of products we鈥檝e created this month - respectively broken down by machine and location.
Start by adding a chart block. This is a preconfigured set of components that accepts a data source and visualizes whichever attributes we tell it to.
We鈥檒l create a new query called ProductionCountByMachineByMonth.
We鈥檒l SELECT the machine_name from machines along with the following from production:
- The numerical month and year.
- The COUNT of the machine_id attribute.
We鈥檒l then LEFT JOIN on m.id = p.machine_id and GROUP BY machine_name, month, and year.
1SELECT
2
3 m.machine_name,
4
5 EXTRACT(YEAR FROM p.date)::INTEGER AS year,
6
7 EXTRACT(MONTH FROM p.date)::INTEGER AS month,
8
9 COUNT(p.machine_id)::INTEGER AS production_count
10
11FROM machines m
12
13LEFT JOIN production p ON m.id = p.machine_id
14
15GROUP BY m.machine_name, year, month
16
17ORDER BY m.machine_name, year, month;
This returns:
1{
2
3 "machine_name": "Machine 1",
4
5 "year": 2023,
6
7 "month": 9,
8
9 "production_count": 7
10
11}
We鈥檒l set the data for our chart block to this query - and choose bar for its type. We鈥檒l also give it a descriptive title:
We also need to configure which attributes will be used for each axis on our graph. We鈥檒l set the label column to machine_name and the data column to production_count:
Just like with the cards, a chart block iterates over the data source we point it at, and displays values for all of the entires. So, we鈥檒l apply the same filters as we did earlier to our month and year attributes in the query response:
Lastly, we鈥檒l add some custom CSS to set the width to 50%:
Here鈥檚 our finished chart:
Again, we鈥檒l duplicate this to save ourselves a bit of time:
For the second chart, we鈥檒l use a similar query that calculates the count of products per month, but grouped by the location attribute from the machines table, rather than machine_name.
We鈥檒l call this ProductionCountByLocationByMonth:
1SELECT
2
3 m.location,
4
5 EXTRACT(YEAR FROM p.date)::INTEGER AS year,
6
7 EXTRACT(MONTH FROM p.date)::INTEGER AS month,
8
9 COUNT(p.machine_id)::INTEGER AS production_count
10
11FROM machines m
12
13LEFT JOIN production p ON m.id = p.machine_id
14
15GROUP BY m.location, year, month
16
17ORDER BY m.location, year, month;
This returns:
1{
2
3 "location": "Anaheim",
4
5 "year": 2023,
6
7 "month": 9,
8
9 "production_count": 16
10
11}
Back on the design section, we can swap the data for our second chart to this query鈥檚 response:
We鈥檒l also check the horizontal option:
Here鈥檚 what we have so far:
4. Breakages and incidents breakdowns
Next, we鈥檒l create our second row of charts. We want a pie chart to show the number of breakages by product and a bar chart showing the incidents by machine.
We鈥檒l start by duplicating our entire existing chart container:
Just like before, we simply need to create new queries to extract the data we want to display, and then swap this out on each of our charts.
We鈥檒l call the first one BreakagesByMonthByProduct. This will SELECT the same information as our original BreakagesByMonth query - but this time also retrieving the relevant product attribute from the production table.
We鈥檒l JOIN breakages to production on b.production_id = p.id.
1SELECT
2
3 EXTRACT(YEAR FROM b.date)::INTEGER AS year,
4
5 EXTRACT(MONTH FROM b.date)::INTEGER AS month,
6
7 p.product,
8
9 COUNT(*)::INTEGER AS breakages_count
10
11FROM breakages b
12
13JOIN production p ON b.production_id = p.id
14
15GROUP BY year, month, p.product
16
17ORDER BY year, month, p.product;
This will return:
1{
2
3 "year": 2023,
4
5 "month": 9,
6
7 "product": "Ground Screw",
8
9 "breakages_count": 1
10
11}
Back on the design screen, we鈥檒l update our third chart to point it at this new query. We鈥檒l also set its type to pie, label column to product, and data column to breakages_count.
Our last query will be called IncidentsByMachine. We need the machine_name from machines, along with the numerical month, year, and COUNT of rows from the incidents table.
We鈥檒l JOIN these on i.machine_id = m.id.
1SELECT
2
3 m.machine_name,
4
5 EXTRACT(YEAR FROM i.date)::INTEGER AS year,
6
7 EXTRACT(MONTH FROM i.date)::INTEGER AS month,
8
9 COUNT(*)::INTEGER AS incidents_count
10
11FROM incidents i
12
13JOIN machines m ON i.machine_id = m.id
14
15GROUP BY m.machine_name, year, month
16
17ORDER BY m.machine_name, year, month;
This will return:
1{
2
3 "machine_name": "Machine 1",
4
5 "year": 2023,
6
7 "month": 9,
8
9 "incidents_count": 12
10
11}
We鈥檒l swap the data for our final chart to match this query response - this time deselecting the horizontal option.
Here鈥檚 our manufacturing dashboard so far:
5. Design tweaks and publishing
Before we push this live, we鈥檙e going to make a couple of minor UX changes.
First of all, we鈥檒l choose the lightest theme under screen:
Finally, we鈥檒l adjust the color palettes of our charts, to improve visual separation:
Once we鈥檙e satisfied, we can publish our app to push it live:
Here鈥檚 a reminder of what our finished manufacturing dashboard looks like:
If you found this guide helpful, you might also like our tutorial on how to build an inventory calculator .