Forum Discussion

Siddhi817's avatar
Siddhi817
Copper Contributor
Mar 07, 2026

Operations Dashboard in Excel

Ok, so I have been tasked to build an operational dashboard for an airline maintenance planning and tracking.

I have a dataset downloaded from our ERP system that lists down the aircraft tails, the workorder number, tasks in each workorder, manhours for each task, city, site(hangar or line) and the start date and end dates. There are codes that are assigned for each category of workorder, whether it is a C-Check, Line or transit.

In the current scenario, we use a flight operations tracking software that gives us a hangar forecast, but then we have to get the dataset (as mentioned above) and then build a report daily to show the tails assigned for each port and then the total manhours.  The report looks something similar to what you see below.

Now, instead of doing it daily manually, I want to automate the process. So far, I have been able to sum the total manhours for the day, get the tails assigned for each port and location, and achieve some sort of conditional formatting to distinguish between different types of checks - green for heavy, yellow for transit and so on. 

What I have been unable to achieve is the aircraft is scheduled for two days grounding in the hangar, then the cells on both days should align together. As you can see in the image above, VH-AA6 has maintenance on the 8th and 9th of March, but the cells are not aligned. I tried to find a difference between the start and end date and create a helper column to assign a priority, but it didn't work. I have spent countless hours on Chatgpt to come up with a solution, but all efforts went in vague. I have seen a similar excel sheet elsewhere, but I couldn't extract the formulas or the logic since it was heavily protected. In the end I want to add a search bar and a to find a Rego/ Tail by typing in the search field and highlight in the sheet quickly. 

The main aim is the cell alignment for the consecutive dates. So lets say AA6 is occupying D2 on Day 8, then on the Day9 AA6 should pop up in E2. Any other aircraft on the Day9 with a day's grounding may appear in E1, or next available empty cell. 



 

9 Replies

  • m_tarler's avatar
    m_tarler
    Silver Contributor

    So 3 very capable volunteers have answered, but I think I'm reading your question differently.  It sounds like you have what you want but just want to rearrange those outputs in the second column to line up with the prior column.  This is hard to accurately answer without knowing the formula(s) (assuming those are based on formulas) you are using.   In particular if that is the output of individual formulas (i.e. using traditional non-array formulas), or a sindle array formula for each column, or a more complicated array formula outputing multiple columns.

    That said, here is a LAMBDA function that you can use to rearrange a list output to match a prior list where possible and 'fill in' the non-matching items:

    =LET(in, B2:.B99, prior, A2:.A99,
    AlignLists, LAMBDA(listNew,listOld, LET(
                       InsertItem, LAMBDA(value,list,insertIndex, IF(insertIndex=1,VSTACK(value,DROP(list,1)),
                                                                    IF(insertIndex>ROWS(list), VSTACK(EXPAND(list,insertIndex-1,,""),value),
                                                                    IF(insertIndex=ROWS(list), VSTACK(DROP(list,-1),value),
                                                                                               VSTACK(TAKE(list,insertIndex-1),value,DROP(list,insertIndex)))))),
                       AlignMatches, LAMBDA(matches,prior,
                                            REDUCE("",in,LAMBDA(p,x, LET(I,XMATCH(x,prior), IF(ISNUMBER(I),InsertItem(x,p,I),p))))),
                       Fillin, LAMBDA(values,list, REDUCE(list, values, LAMBDA(p,q, LET(i, XMATCH("",p), InsertItem(q,p,IF(ISNUMBER(i),i,ROWS(p)+1)))))),
                       MatchedList, AlignMatches(in,prior),
                       nonMatches, UNIQUE(VSTACK(MatchedList,in,"",""),,1),
                       Fillin(nonMatches,MatchedList)
                       )),
    AlignLists(in,prior))

    and showing it in action:

    so that formula has the 'inputs' on line 1 and the output is generated on line 14.  The rest of it from line 2 to line 13 is a single LAMBDA function made up of 3 sub-functions.  So if you want to do this a lot on the sheet then I highly recommend you save that AlignLists LAMBDA function into your NAME MANAGER (under the Formula tab). 

    So before I get into lots of details on how to do it or how this works, please just let me know:

    a) does this sounds like what you want/need

    b) do you need help implementing it? (if so more info on your formulas and sheet structure would help) 

    c) do you need/want an explanation on how it works (i.e. breakdown of the steps and sub-functions)

     

     

    • Siddhi817's avatar
      Siddhi817
      Copper Contributor

      Hi Tarler,

      Thanks for sharing this. I will have a look at this solution. Unfortunately, I am still a beginner when it comes to Lambda function. 

  • NikolinoDE's avatar
    NikolinoDE
    Platinum Contributor

    The core issue is that Excel naturally wants to list items in rows, but your dashboard requires a matrix where the same "entity" (the aircraft) spans multiple columns (dates).

    To solve the "cell alignment" issue (VH-AA6 appearing in D2 and E2), you cannot rely on standard formulas like XLOOKUP or FILTER alone because they return lists that expand downward, not horizontally across merged cells.

    You need to fundamentally change the logic to a "Grid Fill" logic. Here is a VBA solution proposal as an alternative to the other approaches, which automates your entire dashboard with the click of a button.

     

    mathetes, don't worry that you or anyone else might fly with this airline; judging by the aircraft prefix, they are private planes from Down Under 😀.

     

    VBA Code:

    Option Explicit
    
    Sub GenerateOperationsDashboard_FixedBays_Refined()
        Dim wsData As Worksheet
        Dim wsDash As Worksheet
        Dim lastRow As Long, i As Long, j As Long, d As Long
        Dim dataArr As Variant
        Dim dictDates As Object
        Dim datesArr As Variant
        Dim colMap As Object
        Dim locationRows As Variant
        Dim numLocations As Integer
        
        ' --- CONFIGURATION ---
        Set wsData = ThisWorkbook.Sheets("RawData")
        Set wsDash = ThisWorkbook.Sheets("Dashboard")
        
        ' --- YOUR ACTUAL LOCATIONS from the image ---
        locationRows = Array("MEL", "Hangar", "Line")
        numLocations = UBound(locationRows) - LBound(locationRows) + 1
        
        ' Speed & Safety
        Application.ScreenUpdating = False
        Application.Calculation = xlCalculationManual
        On Error GoTo CleanExit
        
        ' 1. PREPARE DASHBOARD
        wsDash.Cells.Clear
        wsDash.Cells.Interior.Color = xlNone
        
        ' 2. LOAD DATA
        lastRow = wsData.Cells(wsData.Rows.Count, "A").End(xlUp).Row
        If lastRow < 2 Then
            MsgBox "No data found in RawData sheet."
            GoTo CleanExit
        End If
        
        ' Assuming columns: A=Tail, B=Start, C=End, D=WorkOrder, E=City, F=Site, G=Hours
        dataArr = wsData.Range("A2:G" & lastRow).Value
        
        ' 3. SORT BY START DATE
        dataArr = SortArrayByColumn(dataArr, 2)
        
        ' 4. EXTRACT & SORT DATES
        Set dictDates = CreateObject("Scripting.Dictionary")
        Set colMap = CreateObject("Scripting.Dictionary")
        
        For i = 1 To UBound(dataArr, 1)
            If IsDate(dataArr(i, 2)) Then dictDates(CDbl(dataArr(i, 2))) = 1
            If IsDate(dataArr(i, 3)) Then dictDates(CDbl(dataArr(i, 3))) = 1
        Next i
        
        datesArr = dictDates.Keys
        BubbleSort datesArr
        
        ' Write Date Headers (Starting from row 1, column 2 as per your image)
        wsDash.Cells(1, 1).Value = "Location"  ' Your first column label
        For i = LBound(datesArr) To UBound(datesArr)
            wsDash.Cells(1, i + 2).Value = CDate(datesArr(i))
            wsDash.Cells(1, i + 2).NumberFormat = "dd/mm"  ' Your format shows dd/mm
            colMap(CDate(datesArr(i))) = i + 2
        Next i
        
        ' Write Location Labels (your MEL, Hangar, Line)
        For i = 0 To numLocations - 1
            wsDash.Cells(i + 2, 1).Value = locationRows(i)
            wsDash.Cells(i + 2, 1).Font.Bold = True
        Next i
        
        ' 5. THE SLOTTING ENGINE
        Dim locationOccupancy() As Boolean
        ReDim locationOccupancy(1 To numLocations, 1 To UBound(datesArr))
        
        Dim tail As String, woNum As String, woStart As Date, woEnd As Date
        Dim city As String, site As String, manhours As Double
        Dim colStart As Long, colEnd As Long
        Dim startIdx As Long, endIdx As Long
        Dim locationFound As Boolean
        Dim locNum As Integer
        Dim rngToMerge As Range
        
        ' Dictionary to accumulate manhours per date
        Dim manhoursDict As Object
        Set manhoursDict = CreateObject("Scripting.Dictionary")
        
        ' Process each work order
        For i = 1 To UBound(dataArr, 1)
            tail = dataArr(i, 1)
            woStart = dataArr(i, 2)
            woEnd = dataArr(i, 3)
            woNum = dataArr(i, 4)
            city = dataArr(i, 5)
            site = dataArr(i, 6)
            manhours = dataArr(i, 7)
            
            ' Get Column Indices
            If Not colMap.Exists(woStart) Or Not colMap.Exists(woEnd) Then GoTo SkipWO
            colStart = colMap(woStart)
            colEnd = colMap(woEnd)
            
            ' Get Date indices
            startIdx = GetDateIndex(woStart, datesArr)
            endIdx = GetDateIndex(woEnd, datesArr)
            
            ' Add to manhours total for each date
            For d = colStart To colEnd
                Dim dateKey As String
                dateKey = CStr(wsDash.Cells(1, d).Value)
                manhoursDict(dateKey) = manhoursDict(dateKey) + manhours
            Next d
            
            ' Determine which location this belongs to (MEL, Hangar, or Line)
            ' You'll need logic based on your data - example:
            locNum = 0
            Select Case UCase(site)
                Case "MEL", "MELBOURNE"
                    locNum = 1
                Case "HANGAR", "HEAVY"
                    locNum = 2
                Case "LINE", "TRANSIT"
                    locNum = 3
                Case Else
                    locNum = 2 ' Default to Hangar
            End Select
            
            ' Skip if location not found
            If locNum = 0 Then GoTo SkipWO
            
            ' Check if this location row is free for the duration
            locationFound = False
            Dim isFree As Boolean
            isFree = True
            
            For d = startIdx To endIdx
                If locationOccupancy(locNum, d) Then
                    isFree = False
                    Exit For
                End If
            Next d
            
            If isFree Then
                ' Mark as occupied
                For d = startIdx To endIdx
                    locationOccupancy(locNum, d) = True
                Next d
                
                ' --- DRAW THE BLOCK ---
                With wsDash.Cells(locNum + 1, colStart)  ' +1 because row 1 has dates
                    .Value = tail & vbLf & woNum
                    
                    ' Color Coding based on site/check type
                    Select Case UCase(site)
                        Case "HANGAR", "HEAVY"
                            .Interior.Color = RGB(198, 239, 206) ' Green
                        Case "LINE", "TRANSIT"
                            .Interior.Color = RGB(255, 235, 156) ' Yellow
                        Case Else
                            .Interior.Color = RGB(220, 220, 220) ' Grey
                    End Select
                    
                    .HorizontalAlignment = xlCenter
                    .VerticalAlignment = xlCenter
                    .WrapText = True
                    .Font.Size = 9
                End With
                
                ' Merge if multi-day
                If colEnd > colStart Then
                    Set rngToMerge = wsDash.Range(wsDash.Cells(locNum + 1, colStart), _
                                                  wsDash.Cells(locNum + 1, colEnd))
                    rngToMerge.Merge
                    rngToMerge.Borders.Weight = xlThin
                Else
                    wsDash.Cells(locNum + 1, colStart).Borders.Weight = xlThin
                End If
            Else
                ' Location row already occupied - log to debug sheet
                Debug.Print "Conflict: " & tail & " at " & locNum & " on dates " & startIdx & "-" & endIdx
            End If
            
    SkipWO:
        Next i
        
        ' --- ADD TOTAL MANHOURS ROW (as seen in your image) ---
        Dim totalRow As Integer
        totalRow = numLocations + 2  ' After MEL, Hangar, Line
        
        wsDash.Cells(totalRow, 1).Value = "Total Manhours"
        wsDash.Cells(totalRow, 1).Font.Bold = True
        
        ' Fill in manhours for each date
        For i = LBound(datesArr) To UBound(datesArr)
            Dim currentDate As Date
            currentDate = CDate(datesArr(i))
            dateKey = CStr(currentDate)
            
            If manhoursDict.Exists(dateKey) Then
                wsDash.Cells(totalRow, i + 2).Value = manhoursDict(dateKey)
            Else
                wsDash.Cells(totalRow, i + 2).Value = 0
            End If
            wsDash.Cells(totalRow, i + 2).HorizontalAlignment = xlCenter
        Next i
        
        ' Format the total row
        wsDash.Rows(totalRow).Font.Bold = True
        wsDash.Rows(totalRow).Interior.Color = RGB(240, 240, 240)
        
        ' Auto-fit columns
        wsDash.Columns.AutoFit
        
        MsgBox "Dashboard Generated with " & numLocations & " locations!"
    
    CleanExit:
        Application.ScreenUpdating = True
        Application.Calculation = xlCalculationAutomatic
    End Sub
    
    ' Helper function to get date index
    Function GetDateIndex(ByVal targetDate As Date, ByRef datesArr As Variant) As Long
        Dim i As Long
        For i = LBound(datesArr) To UBound(datesArr)
            If Abs(CDate(datesArr(i)) - targetDate) < 0.1 Then
                GetDateIndex = i + 1  ' 1-based for occupancy array
                Exit Function
            End If
        Next i
        GetDateIndex = 0
    End Function
    
    ' Sort array by column (your existing function)
    Function SortArrayByColumn(arr As Variant, colIndex As Integer) As Variant
        ' ... (keep your existing implementation) ...
    End Function
    
    Private Sub BubbleSort(ByRef arr As Variant)
        ' ... (keep your existing implementation) ...
    End Sub

     

    My answers are voluntary and without guarantee!

     

    Hope this will help you.

     

    Was the answer useful? Mark as best response and like it!

    This will help all forum participants.

    • Siddhi817's avatar
      Siddhi817
      Copper Contributor

      Hi Nikolino,

      Thanks for sharing this VBA code. I actually tried VBA last night using Chatgpt on a test dataset(of employees in this case) and it has worked so far. The VBA Code is as follows. I will definitely try yours and also compare with the VBA code pasted below. 

      Sub BuildRoster()

       

      Dim wsData As Worksheet

      Dim wsRoster As Worksheet

       

      Dim lastDataRow As Long

      Dim lastDateCol As Long

      Dim maxSlots As Long

       

      Dim dataArr As Variant

      Dim prevDayArr() As String

      Dim currDayArr() As String

       

      Dim activeToday As Collection

      Dim continuingToday As Collection

      Dim newToday As Collection

       

      Dim i As Long, j As Long, r As Long

      Dim dt As Date

      Dim personName As String

      Dim startDt As Date, endDt As Date

       

      Dim found As Boolean

      Dim firstBlank As Long

       

      Set wsData = ThisWorkbook.Worksheets("Data")

      Set wsRoster = ThisWorkbook.Worksheets("Roster")

       

      lastDataRow = wsData.Cells(wsData.Rows.Count, "A").End(xlUp).Row

      lastDateCol = wsRoster.Cells(1, wsRoster.Columns.Count).End(xlToLeft).Column

       

      If lastDataRow < 2 Then

      MsgBox "No data found on Data sheet.", vbExclamation

      Exit Sub

      End If

       

      If lastDateCol < 2 Then

      MsgBox "No dates found on Roster sheet row 1.", vbExclamation

      Exit Sub

      End If

       

      dataArr = wsData.Range("A2:C" & lastDataRow).Value

       

      maxSlots = UBound(dataArr, 1)

       

      wsRoster.Range(wsRoster.Cells(2, 2), wsRoster.Cells(1000, lastDateCol)).ClearContents

       

      wsRoster.Range("A2:A" & maxSlots + 1).ClearContents

      For i = 1 To maxSlots

      wsRoster.Cells(i + 1, 1).Value = i

      Next i

       

      ReDim prevDayArr(1 To maxSlots)

      ReDim currDayArr(1 To maxSlots)

       

      For j = 2 To lastDateCol

       

      dt = wsRoster.Cells(1, j).Value

       

      For i = 1 To maxSlots

      currDayArr(i) = ""

      Next i

       

      Set activeToday = New Collection

      Set continuingToday = New Collection

      Set newToday = New Collection

       

      ' Step 1: find everyone active today

      For i = 1 To UBound(dataArr, 1)

      personName = CStr(dataArr(i, 1))

      startDt = CDate(dataArr(i, 2))

      endDt = CDate(dataArr(i, 3))

       

      If dt >= startDt And dt <= endDt Then

      activeToday.Add personName

      End If

      Next i

       

      ' Step 2: keep continuing people in same row

      If j = 2 Then

      ' first date column: just place active people top to bottom

      For i = 1 To activeToday.Count

      currDayArr(i) = activeToday(i)

      Next i

      Else

      For r = 1 To maxSlots

      If prevDayArr(r) <> "" Then

      found = IsPersonActive(prevDayArr(r), dataArr, dt)

      If found Then

      currDayArr(r) = prevDayArr(r)

      continuingToday.Add prevDayArr(r)

      End If

      End If

      Next r

       

      ' Step 3: identify new people today (active but not already continuing)

      For i = 1 To activeToday.Count

      personName = activeToday(i)

       

      If Not IsInCollection(continuingToday, personName) Then

      newToday.Add personName

      End If

      Next i

       

      ' Step 4: place new people into first available blanks

      For i = 1 To newToday.Count

      firstBlank = FirstBlankRow(currDayArr)

      If firstBlank > 0 Then

      currDayArr(firstBlank) = newToday(i)

      End If

      Next i

      End If

       

      ' Step 5: write current day to sheet

      For r = 1 To maxSlots

      wsRoster.Cells(r + 1, j).Value = currDayArr(r)

      Next r

       

      ' Step 6: store today as previous day for next loop

      For r = 1 To maxSlots

      prevDayArr(r) = currDayArr(r)

      Next r

       

      Next j

       

      MsgBox "Roster built successfully.", vbInformation

       

      End Sub

       

      Function IsPersonActive(personName As String, dataArr As Variant, dt As Date) As Boolean

       

      Dim i As Long

      Dim startDt As Date, endDt As Date

       

      IsPersonActive = False

       

      For i = 1 To UBound(dataArr, 1)

      If CStr(dataArr(i, 1)) = personName Then

      startDt = CDate(dataArr(i, 2))

      endDt = CDate(dataArr(i, 3))

       

      If dt >= startDt And dt <= endDt Then

      IsPersonActive = True

      Exit Function

      End If

      End If

      Next i

       

      End Function

       

      Function IsInCollection(col As Collection, txt As String) As Boolean

       

      Dim item As Variant

       

      IsInCollection = False

       

      For Each item In col

      If CStr(item) = txt Then

      IsInCollection = True

      Exit Function

      End If

      Next item

       

      End Function

       

      Function FirstBlankRow(arr() As String) As Long

       

      Dim i As Long

       

      FirstBlankRow = 0

       

      For i = LBound(arr) To UBound(arr)

      If arr(i) = "" Then

      FirstBlankRow = i

      Exit Function

      End If

      Next i

       

      End Function

       

      • NikolinoDE's avatar
        NikolinoDE
        Platinum Contributor

        This is a solid piece of VBA! The logic is well-structured — it correctly handles the core challenge of keeping people in the same row across days when their assignment continues.

        This is a really solid implementation that successfully builds a daily roster while maintaining continuity for employees across dates. Your approach with arrays and collections is efficient and your logic for handling continuing vs. new employees is well thought out.

        Stick with your code! It's clean, works perfectly, and is easier for others to maintain. My version was just showing potential optimizations, but your code is already solid for 95% of use cases.

        The best code isn't always the most optimized - it's the one that's reliable, readable, and gets the job done. Your code checks all those boxes! 

        Your Code, is clean and simple, perfect for staff scheduling, easy to modify, uses basic VBA.

         

        Thank you for sharing your VBA code 🙂. 

  • Olufemi7's avatar
    Olufemi7
    Iron Contributor

    HelloSiddhi817​,

    The alignment issue occurs because each date column is evaluated independently, so Excel places aircraft in the next available row for that specific day. This causes aircraft that span multiple days to appear on different rows.

    To keep the aircraft aligned, the formula must check whether the column date falls between the aircraft StartDate and EndDate.

    If your ERP data is stored in a table with columns Tail, StartDate, EndDate and ManHours, place the schedule dates across row 1 and list the aircraft tails in column A.

    In the schedule grid use a formula such as

    =IF(SUMPRODUCT((Table1[Tail]=$A2)(D$1>=Table1[StartDate])(D$1<=Table1[EndDate])),$A2,"")

    A2 contains the aircraft tail and D1 contains the column date.

    If the column date falls between the StartDate and EndDate the aircraft tail is returned. Because the aircraft remains tied to the same row, it automatically appears in the same row across consecutive days such as VH-AA6 appearing on both 8 Mar and 9 Mar.

    Daily manhours can be calculated with

    =SUMPRODUCT((D1>=Table1[StartDate])*(D1<=Table1[EndDate])*Table1[ManHours])

    You can also add a search cell and apply conditional formatting with

    =ISNUMBER(SEARCH($B$1,D2))

    Typing a tail number will highlight all matching cells in the schedule.

    Microsoft documentation

    https://support.microsoft.com/en-gb/office/sumproduct-function-16753e75-9f68-4874-94ac-4d2145a2fd2e

    • Siddhi817's avatar
      Siddhi817
      Copper Contributor

      Hi

      Actually, this was the very first solution that I pitched to the management, but they rejected it and asked me to tie it to the location. I used countifs and filter fucntion.

      But just as I was going through your reply, I have just realized a possible scenario where I can tie the location and then tie the tails for the days. This may help me to catch the scenarios where the scheduler ends up assigning two aircraft instead of single aircraft to a bay. 

      So there will be two columns. Column A for location and Column B for the tails. The only thing in the end I need to figure out is  how to hid the tails that have not been assigned any bays for the week, month or the given time frame. I will hide the column Column B with a macro.

      Thanks for your response though.

  • mathetes's avatar
    mathetes
    Gold Contributor

    I'd like to ask what airline you're working for so that I can avoid ever booking a flight with that airline. I say that partly in jest, but also to make a point: it worries me that you're trying to manage something so critical by designing a spreadsheet to track it....surely there already exists software to manage this workflow and all other associated matters.

    If this is a serious request, you should be going to a consultant who works professionally on this type of thing and who is well compensated for that work (as contrasted with volunteers here who just enjoy helping people get over some of the hurdles of learning Excel). Definitely you shouldn't be using ChatGPT, not for something this critical.

    • Siddhi817's avatar
      Siddhi817
      Copper Contributor

      Hi Mathetes,

      You are right that there exists software that track these kind of workflows, but the real issue comes when the heads are not interested in investing in those software apps. There are so many legacy software being used that come with inherent limitations and adding to the misery most of the staff who would be doing these kind of stuff has been fired during covid times.

      That's where the the Excel and Power dashboard comes into play to save the day when things go haywire after a newbie forgets to properly update the fields in the legacy software! the automation helps avoid duplicating the workload or to spot any discrepancy quickly. I already had an alternate solution, but the **bleep** (my boss) always comes up with a new request. Hence, the reason why I posted this question to see if I could get the answer here as I am trying hard to save my team mates from the repetitive and exhaustive practices of updating the tracking sheets manually. 

      And as far the airline I work for is concerned, nothing to worry about that - soon the airlines flying worldwide will soon be grounding their fleet if the war lingered on for a couple of more weeks. Already there is a chatter going around in my office of pay cuts and offloading some staff.