Search code examples
excelvbatimelistboxunique

Excel VBA Form Show Unique Entries with Time Calculation in Listbox


I would like to ask your help about my query below. I have here 2 different forms. Form 1 for saving entries. For saving entries, I already have code. It's already working. Form 2 is a form to display data from excel with time calculation as a desired output. Please see images as it could help.

Data in Excel Sheet1:

Date      || Project ID || Implementation Area  || Start Time   || End Time     || Status
8/28/2023 || 1145544    || Arizona              || 8:00:03 AM   || 9:15:17 AM   || For Approval 1
8/28/2023 || 1157788    || Arizona              || 9:15:20 AM   || 12:00:19 PM  || For Approval 1
8/28/2023 ||LUNCH BREAK ||                      || 12:00:18 PM  || 1:00:00 PM   || LUNCH BREAK
8/29/2023 || 1145544    || Arizona              || 1:00:01 PM   || 3:00:00 PM   || For Approval 2
8/29/2023 || 1145544    || Arizona              || 3:30:07 PM   || 3:40:40 PM   || COMPLETED
8/30/2023 || 1157788    || Arizona              || 3:41:00 PM   || 3:50:00 PM   || For Approval 2
9/1/2023  || 1157788    || Arizona              || 4:00:00 PM   || 4:30:45 PM   || COMPLETED
9/2/2023  || 1233343    || New York             || 9:05:17 AM   || 11:30:20 AM  || For Approval 1
9/2/2023  ||LUNCH BREAK ||                      || 12:00:00 AM  || 1:00:00 PM   || LUNCH BREAK
9/2/2023  || 1233343    || New York             || 1:45:01 PM   || 2:45:30 PM   || For Approval 2
9/2/2023  || 1233343    || New York             || 3:00:00 AM   || 3:22:00 AM   || COMPLETED
9/2/2023  || 1422457    || Louisana             || 3:50:00 PM   || 4:12:00 PM   || For Approval 1
9/3/2023  || 1422457    || Louisana             || 10:18:03 AM  || 11:15:17 AM  || For Approval 2
9/4/2023  || 1422457    || Louisana             || 4:15:20 PM   || 4:35:19 PM   || COMPLETED

Form 1

Form1

Form 2

Form 2

This is my code for Form 2

Private Sub UserForm_Initialize()
Dim colimplementationArea1 As Variant, colimplementationArea2 As Variant, colimplementationArea3 As Variant
Dim colStatus1 As Variant, colStatus2 As Variant, colStatus3 As Variant

Set Rng = Range("C:C") 'project id
Set rng1 = Range("D:D") 'implementation area
Set rng2 = Range("E:E") 'start time
Set rng3 = Range("F:F") 'end time
Set rng3 = Range("G:G") 'status

colimplementationArea1 = "Arizona"
colimplementationArea2 = "New York"
colimplementationArea3 = "Louisana"
colStatus1 = "For Approval 1"
colStatus2 = "For Approval 2"
colStatus3 = "COMPLETED"


'i lack codes for Listbox1 and Listbox2 that will display data from Excel Sheet1:

'--------Listbox1
'Unique Project ID     |    Area of Implementation     |    Total Hours Worked from Approval 1 to COMPLETED per Unique ID

'Calculation:
'***total hours worked will add the time of Start Time and End Time of For Approval 1 + Start Time and
' + End Time of For Approval 2 + Start 'Time and End Time of COMPLETED***



'--------Listbox2
'Unique Area of Implementation | Total Hours Worked using different Unique IDs containing same Area | Avearage Hours

'Calculation:
'***using Sheet1, we will be adding the total hours of the 2 Unique IDs 1145544 and 1157788 for Arizona from
'Start Time to End Time then divide it by 2 (since there are 2 unique ids)
'There's nothing to calculate for the rest of the Areas because they contain 1 unique ID only***


'Apologies... I really don't know where to start for the calculations of my listboxes
End Sub

Solution

  • This seems a rather complex problem to code and output the results in List Boxes, but here's one way to keep it relatively organized and flexible enough for changes to be made.

    I use a Class Module and Dictionary objects to organize the data and create output arrays

    Class Module
    Rename as indicated in Comments

    'rename cProjectData
    Option Explicit
    Private pID As Variant
    Private pDt As Date
    Private pIArea As String
    Private pStartTime As Date
    Private pEndTime As Date
    Private pStatus As String
    Private pCol As Collection
    
    Public Property Get ID() As Variant
        ID = pID
    End Property
    Public Property Let ID(value As Variant)
        pID = value
    End Property
    
    Public Property Get Dt() As Date
        Dt = pDt
    End Property
    Public Property Let Dt(value As Date)
        pDt = value
    End Property
    
    Public Property Get IArea() As String
        IArea = pIArea
    End Property
    Public Property Let IArea(value As String)
        pIArea = value
    End Property
    
    Public Property Get StartTime() As Date
        StartTime = pStartTime
    End Property
    Public Property Let StartTime(value As Date)
        pStartTime = value
    End Property
    
    Public Property Get EndTime() As Date
        EndTime = pEndTime
    End Property
    Public Property Let EndTime(value As Date)
        pEndTime = value
    End Property
    
    Public Property Get Status() As String
        Status = pStatus
    End Property
    Public Property Let Status(value As String)
        pStatus = value
    End Property
    
    Public Property Get TotHrs() As Date
        TotHrs = pTotHrs
    End Property
    Public Property Let TotHrs(value As Date)
        pTotHrs = value
    End Property
    
    Public Property Get Col() As Collection
        Set Col = pCol
    End Property
    
    Public Function addColItem(value)
        pCol.Add value
    End Function
    
    Private Sub Class_Initialize()
        Set pCol = New Collection
    End Sub
    

    User Form Module
    Note that a lot of your declarations are not necessary in this implementation
    Be sure to set the Reference as indicated in the comments

    'Add Reference to Microsoft Scripting Runtime
    '   for early binding to Dictionary object
    
    Option Explicit
        'should always be at the start of any VBA module
        'set Tools/Options/Editor/Code Settings/Require variable declaration
        'be sure to declare **ALL** variables
        
    Private Sub UserForm_Initialize()
    
    Dim TotalHours As Date
    Dim I As Long, j As Long
    Dim k, V
    Dim sKey As String
    
    'more descriptive names might be useful in debugging
    '  and an array will both limit the appropriate ranges
    '  and process much faster
    
      Dim vData As Variant
    
    'since this code is in a sheet module, no need to
    '  specifically reference this worksheet
    vData = Range(Cells(2, 2), Cells(Rows.Count, 7).End(xlUp))
    
    'Trim the strings
    'may not be needed in your real data
        For I = 1 To UBound(vData, 1)
            For j = 1 To UBound(vData, 2)
                vData(I, j) = Trim(vData(I, j))
            Next j
        Next I
    
    'Group data by Project Name and by Implementation area
    Dim dProj As Dictionary
      Dim KeyDProj As Variant
        
    'Create Class object to store the data
      Dim cProj As cProjectData
    
    'iterate through the data and classify it
    Set dProj = New Dictionary
    
    For I = 1 To UBound(vData)
     If Not vData(I, 6) = "LUNCH BREAK" Then
        Set cProj = New cProjectData
        With cProj
            .Dt = vData(I, 1)
            .ID = vData(I, 2)
            .IArea = vData(I, 3)
            .StartTime = vData(I, 4)
            .EndTime = vData(I, 5)
            .Status = vData(I, 6)
        End With
        
        KeyDProj = cProj.ID
        
        If Not dProj.Exists(KeyDProj) Then
            cProj.addColItem cProj
            dProj.Add Key:=KeyDProj, Item:=cProj
        Else
            dProj(KeyDProj).addColItem cProj
        End If
        
     End If
    Next I
    
    'Access the dictionary and summarize the results
    'for ListBox 1 into an array
        Dim vResL1 As Variant
    ReDim vResL1(0 To dProj.Count, 1 To 3)
    
    'Headers
        vResL1(0, 1) = "Project Name"
        vResL1(0, 2) = "Implementation Area"
        vResL1(0, 3) = "Total Hours"
    I = 1
    For Each k In dProj.Keys
        TotalHours = 0
        For Each V In dProj(k).Col
            TotalHours = TotalHours + V.EndTime - V.StartTime
            vResL1(I, 1) = V.ID
            vResL1(I, 2) = V.IArea
        Next V
        vResL1(I, 3) = TotalHours
        I = I + 1
    Next k
    
    'summarize vResL1 by averaging TotalHours
    Dim dImplArea As Dictionary
    Set dImplArea = New Dictionary
        dImplArea.CompareMode = TextCompare
        
    For I = 1 To UBound(vResL1, 1)
        sKey = vResL1(I, 2)
        
        If Not dImplArea.Exists(sKey) Then
            dImplArea.Add Key:=sKey, Item:=Array(1, vResL1(I, 3))
        Else
            V = dImplArea(sKey)
            V(0) = V(0) + 1
            V(1) = V(1) + vResL1(I, 3)
            dImplArea(sKey) = V
        End If
        vResL1(I, 3) = Format(vResL1(I, 3), "hh:mm:ss")
    Next I
    
    Dim vResL2 As Variant
    ReDim vResL2(0 To dImplArea.Count, 1 To 3)
    
    'Headers
        vResL2(0, 1) = "Implementation Area"
        vResL2(0, 2) = "Total Hours Worked:"
        vResL2(0, 3) = "Average Hours:"
        
    I = 1
        For Each V In dImplArea.Keys
            vResL2(I, 1) = V
            vResL2(I, 2) = Format(dImplArea(V)(1), "hh:mm:ss")
            vResL2(I, 3) = Format(CDate(dImplArea(V)(1) / dImplArea(V)(0)), "hh:mm:ss")
            I = I + 1
        Next V
        
    'write results to the listboxes
    With Me.ListBox1
        .ColumnCount = 3
        .ColumnWidths = "75;110;100"
        
        .List = vResL1
    End With
    
    With Me.ListBox2
        .ColumnCount = 3
        .ColumnWidths = "110;100;75"
        .List = vResL2
    End With
    
    End Sub
    

    Output from your Data
    enter image description here