    Imports System
    Imports System.Collections
    Imports System.Data
    Imports System.Text
    Imports System.Web.UI.WebControls
    Imports ASPNET.StarterKit.TimeTracker.BusinessLogicLayer

    Namespace ASPNET.StarterKit.TimeTracker.Web

        '*********************************************************
        '
        ' Main page of the Application, for users to enter time entries.
        '
        '*********************************************************

        Public Class TimeEntry
            Inherits System.Web.UI.Page
            'Use Time Entry component to capture user's input during edit mode
            Protected _userInput As New BusinessLogicLayer.TimeEntry(0, 0, 0, 0, DateTime.MinValue, Nothing, -1D)
            Protected _dayListTable As DataTable = Nothing

            Protected WithEvents AddEntry As System.Web.UI.WebControls.Button
            Protected CompareValidator1 As System.Web.UI.WebControls.CompareValidator
            Protected WithEvents Cancel As System.Web.UI.WebControls.Button
            Protected CategoryList As System.Web.UI.WebControls.DropDownList
            Protected Days As System.Web.UI.WebControls.DropDownList
            Protected Description As System.Web.UI.WebControls.TextBox
            Protected Hours As System.Web.UI.WebControls.TextBox
            Protected WithEvents ProjectList As System.Web.UI.WebControls.DropDownList
            Protected RangeValidator1 As System.Web.UI.WebControls.RangeValidator
            Protected Requiredfieldvalidator1 As System.Web.UI.WebControls.RequiredFieldValidator
            Protected RequiredFieldValidator2 As System.Web.UI.WebControls.RequiredFieldValidator
            Protected WithEvents TimeEntryGrid As System.Web.UI.WebControls.DataGrid
            Protected TimeGraph As System.Web.UI.WebControls.Image
            Protected WithEvents UserList As System.Web.UI.WebControls.DropDownList
            Protected WithEvents WeekEnding As System.Web.UI.WebControls.TextBox

            Private _user As TTUser
            Private _weekEndingDate As DateTime = DateTime.Today
            Private _weekStartingDate As DateTime = DateTime.Today

            '*******************************************************
            '
            ' The Page_Load server event handler on this page is used
            ' to populate information fields on this page
            '
            '*******************************************************

            Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
                _user = New TTUser(TTSecurity.GetUserID(), User.Identity.Name, TTSecurity.GetName(), TTSecurity.GetUserRole())

                If Not Page.IsPostBack Then
                    ' If page is loaded for the first time, select the current week.
                    BusinessLogicLayer.TimeEntry.FillCorrectStartEndDates(DateTime.Today, _weekStartingDate, _weekEndingDate)
                    WeekEnding.Text = _weekEndingDate.ToShortDateString()

                    BindUserList()
                    BindEntryFields()
                    BindTimeSheet(_user.UserID, _weekStartingDate, _weekEndingDate)
                Else
                    ' Selects the weekending date, corresponding to week user selected.
                    BusinessLogicLayer.TimeEntry.FillCorrectStartEndDates(Convert.ToDateTime(WeekEnding.Text), _weekStartingDate, _weekEndingDate)
                    _dayListTable = BusinessLogicLayer.TimeEntry.GetWeek(Convert.ToDateTime(WeekEnding.Text))
                End If
            End Sub 'Page_Load

            '*******************************************************
            '
            ' BindCategoryList method databinds data to CategoryList dropdown.
            '
            '*******************************************************

            Private Sub BindCategoryList()
                If Not (ProjectList.SelectedItem Is Nothing) Then
                    ' CategoryList is different for each project, Project.GetCategories gets a list of
                    ' categories based on the project.
                    CategoryList.DataSource = Project.GetCategories(Convert.ToInt32(ProjectList.SelectedItem.Value))
                    CategoryList.DataValueField = "CategoryID"
                    CategoryList.DataTextField = "Abbreviation"
                    CategoryList.DataBind()
                End If
            End Sub 'BindCategoryList

            '*******************************************************
            '
            ' BindDates method populates the DropDownList Days to show each day of the week
            '
            '*******************************************************

            Private Sub BindDates()
                _dayListTable = BusinessLogicLayer.TimeEntry.GetWeek(Convert.ToDateTime(WeekEnding.Text))
                Days.DataSource = _dayListTable
                Days.DataValueField = "Date"
                Days.DataTextField = "Day"
                Days.DataBind()
                Days.Items.FindByText(Convert.ToDateTime(DateTime.Today).ToString("ddd")).Selected = True
            End Sub 'BindDates

            '*******************************************************
            '
            ' BindEntryFields calls methods that populate the drop down lists with data.
            '
            '*******************************************************

            Private Sub BindEntryFields()
                BindDates()
                BindProjectList()
                BindCategoryList()
            End Sub 'BindEntryFields

            '*******************************************************
            '
            ' BindProjectList method populates the DropDownList ProjectList to show
            ' list of projects that belongs to the user.
            '
            '*******************************************************

            Private Sub BindProjectList()
                ' Preserve the selected value so information is still there after postback.
                If Not (ProjectList.SelectedItem Is Nothing) Then
                    _userInput.ProjectID = Convert.ToInt32(ProjectList.SelectedItem.Value)
                End If
                ProjectList.DataSource = ListUserProjects()
                ProjectList.DataTextField = "Name"
                ProjectList.DataValueField = "ProjectID"
                ProjectList.DataBind()

                ' Select the correct item from ProjectList.
                If Not (ProjectList.Items.FindByValue(_userInput.ProjectID.ToString()) Is Nothing) Then
                    ProjectList.Items.FindByValue(_userInput.ProjectID.ToString()).Selected = True
                End If
            End Sub 'BindProjectList

            '*******************************************************
            '
            ' An overload of BindTimeSheet method
            ' this is called when the logged on user in querying time entry about themself.
            '
            '*******************************************************

            Private Overloads Sub BindTimeSheet(ByVal userID As Integer, ByVal start As DateTime, ByVal [end] As DateTime)
                BindTimeSheet(userID, userID, start, [end])
            End Sub 'BindTimeSheet

            '*******************************************************
            '
            ' BindTimeSheet method databinds the TimeEntryGrid based on the user
            ' currently logged in and user which is being queried.
            '
            '*******************************************************

            Private Overloads Sub BindTimeSheet(ByVal queryUserID As Integer, ByVal userID As Integer, ByVal start As DateTime, ByVal [end] As DateTime)
                Dim entryList As TimeEntriesCollection = BusinessLogicLayer.TimeEntry.GetEntries(queryUserID, userID, start, [end])

                ' Sort datagrid if it is not empty.
                If Not (entryList Is Nothing) Then
                    SortGridData(entryList, SortField, SortAscending)
                End If
                ' Draw time graph with new dataset.
                DrawTimeGraph(entryList, _dayListTable)

                TimeEntryGrid.DataSource = entryList
                TimeEntryGrid.DataBind()
            End Sub 'BindTimeSheet

            '*******************************************************
            '
            ' BindUserList method databinds a list of users to the UserList;
            ' This is based on the user currently logged in.
            '
            '*******************************************************

            Private Sub BindUserList()
                UserList.DataSource = TTUser.GetUsers(_user.UserID, _user.Role)
                UserList.DataBind()

                'Select User Drop Down to the correct value.
                UserList.Items.FindByValue(_user.UserID.ToString()).Selected = True
            End Sub 'BindUserList

            '*******************************************************
            '
            ' ClearEntryFields method clears all the entry fields on this page.
            '
            '*******************************************************

            Private Sub ClearEntryFields()
                BindEntryFields()

                ' Make sure that no items appear in edit mode in the TimeEntryGrid.
                TimeEntryGrid.EditItemIndex = -1

                BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate)
                Description.Text = String.Empty
                Hours.Text = String.Empty
            End Sub 'ClearEntryFields

            '*******************************************************
            '
            ' DrawTimeGraph method creates the bar graph on the fly.
            ' Binds accordingly to number hours in each day of the week.
            '
            '*******************************************************

            Private Sub DrawTimeGraph(ByVal chartData As TimeEntriesCollection, ByVal dtWeek As DataTable)
                ' Assume dtWeek is already in the correct order according to FirstDayOfWeek Setting.
                Dim htWeekSummary As New Hashtable(7) '7 days of data - key: EntryDate | value: Duration.
                ' x & y values holder
                Dim xValues As New StringBuilder()
                Dim yValues As New StringBuilder()

                'Initialize htWeekSummary to start with first day of week setting.
                Dim row As DataRow
                For Each row In dtWeek.Rows
                    htWeekSummary.Add(Convert.ToDateTime(row("Date")).ToShortDateString(), 0D)
                Next row

                ' Summarize data from grid Group By Days of Week.
                Dim time As BusinessLogicLayer.TimeEntry
                For Each time In chartData
                    Dim key As String = time.EntryDate.ToShortDateString()
                    Dim dayDuration As [Decimal] = Convert.ToDecimal(htWeekSummary(key))
                    dayDuration += time.Duration
                    htWeekSummary(key) = dayDuration
                Next time

                'Build x & y values for graph.
                Dim index As Integer = 1
                Dim thisRow As DataRow
                For Each thisRow In dtWeek.Rows
                    Dim key As String = Convert.ToDateTime(thisRow("Date")).ToShortDateString()
                    Dim currentDay As String = Convert.ToDateTime(key).ToString("ddd")
                    Dim currentHour As String = Convert.ToString(htWeekSummary(key))

                    xValues.Append(currentDay)
                    yValues.Append(currentHour)

                    'Don't append separator if this is the last item.
                    If index <> dtWeek.Rows.Count Then
                        xValues.Append("|")
                        yValues.Append("|")
                    End If
                    index += 1
                Next thisRow

                'Attach image generator to image.
                TimeGraph.ImageUrl = "TimeEntryBarChart.aspx?" + "xValues=" + xValues.ToString() + "&yValues=" + yValues.ToString()
            End Sub 'DrawTimeGraph

            '*******************************************************
            '
            ' ListGridCategories method returns a List of categories based on the projectID.
            '
            '*******************************************************
            Protected Function ListGridCategories(ByVal projectID As Integer) As CategoriesCollection
                Return Project.GetCategories(projectID)
            End Function 'ListGridCategories

            '*******************************************************
            '
            ' ListUserProjects method returns a ArrayList of available projects.
            '
            '*******************************************************

            Protected Function ListUserProjects() As ProjectsCollection
                Return BusinessLogicLayer.Project.GetProjects(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value))
            End Function 'ListUserProjects

            '*******************************************************
            '
            ' SortGridData methods sorts the TimeEntryGrid based on which
            ' sort field is being selected.  Also does reverse sorting based on the boolean.
            '
            '*******************************************************

            Private Sub SortGridData(ByVal list As TimeEntriesCollection, ByVal sortField As String, ByVal asc As Boolean)
                Dim sortCol As TimeEntriesCollection.TimeEntryFields = TimeEntriesCollection.TimeEntryFields.InitValue

                Select Case sortField
                    Case "EntryDate"
                        sortCol = TimeEntriesCollection.TimeEntryFields.Day
                    Case "ProjectName"
                        sortCol = TimeEntriesCollection.TimeEntryFields.Project
                    Case "CategoryName"
                        sortCol = TimeEntriesCollection.TimeEntryFields.Category
                    Case "Duration"
                        sortCol = TimeEntriesCollection.TimeEntryFields.Hours
                    Case "Description"
                        sortCol = TimeEntriesCollection.TimeEntryFields.Description
                    Case Else
                End Select

                list.Sort(sortCol, asc)
            End Sub 'SortGridData

            '*******************************************************
            '
            ' AddEntry_Click server event handler on this page is used to
            ' saves a time entry and adds it to the system.
            '
            '*******************************************************

            Private Sub AddEntry_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles AddEntry.Click
                ' check validation.
                Requiredfieldvalidator1.Validate()
                RequiredFieldValidator2.Validate()
                RangeValidator1.Validate()
                CompareValidator1.Validate()

                ' proceeds if data is valid then create an obejct and save it.
                If Requiredfieldvalidator1.IsValid And RequiredFieldValidator2.IsValid And CompareValidator1.IsValid And RangeValidator1.IsValid Then
                    Dim te As New BusinessLogicLayer.TimeEntry(0, Convert.ToInt32(UserList.SelectedItem.Value), Convert.ToInt32(ProjectList.SelectedItem.Value), Convert.ToInt32(CategoryList.SelectedItem.Value), Convert.ToDateTime(Days.SelectedItem.Value), TTSecurity.CleanStringRegex(Description.Text), Convert.ToDecimal(Hours.Text))
                    te.Save()

                    ClearEntryFields()
                End If
            End Sub 'AddEntry_Click

            '*******************************************************
            '
            ' Cancel_Click server event handler on this page is used to
            ' clear all entry fields on page.
            '
            '*******************************************************
            Private Sub Cancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Cancel.Click
                ClearEntryFields()
            End Sub 'Cancel_Click

            '*******************************************************
            '
            ' ProjectList_SelectedIndexChanged server event handler on this page is used to
            ' update the CategoryList dropdown when a different project is selected
            '
            '*******************************************************

            Private Sub ProjectList_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ProjectList.SelectedIndexChanged
                BindCategoryList()
            End Sub 'ProjectList_SelectedIndexChanged

            '*******************************************************
            '
            ' TimeEntryGrid_Itembound server event handler on this page is used
            ' during TimeEntryGrid databind
            '
            '*******************************************************

            Private Sub TimeEntryGrid_Itembound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) Handles TimeEntryGrid.ItemDataBound
                ' During in-line-editing mode this selects corresponding items for dropdowns and text boxes.
                ' The _userInput object is created in TimeEntryGrid_OnEdit().
                If e.Item.ItemType = ListItemType.EditItem Then
                    Dim currentCbo As DropDownList = CType(e.Item.FindControl("EntryDays"), DropDownList)
                    currentCbo.SelectedIndex = currentCbo.Items.IndexOf(currentCbo.Items.FindByText(_userInput.Day))

                    currentCbo = CType(e.Item.FindControl("EntryProjects"), DropDownList)
                    currentCbo.SelectedIndex = currentCbo.Items.IndexOf(currentCbo.Items.FindByText(_userInput.ProjectName))

                    currentCbo = CType(e.Item.FindControl("EntryCategories"), DropDownList)
                    currentCbo.SelectedIndex = currentCbo.Items.IndexOf(currentCbo.Items.FindByText(_userInput.CategoryName))
                End If
            End Sub 'TimeEntryGrid_Itembound

            '*******************************************************
            '
            ' TimeEntryGrid_OnCancel server event handler on this page is used to
            ' quit in-line-editing mode. and Undo all changes
            '
            '*******************************************************

            Protected Sub TimeEntryGrid_OnCancel(ByVal sender As [Object], ByVal e As DataGridCommandEventArgs) Handles TimeEntryGrid.CancelCommand
                TimeEntryGrid.EditItemIndex = -1
                BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate)
            End Sub 'TimeEntryGrid_OnCancel

            '*******************************************************
            '
            ' TimeEntryGrid_OnDelete server event handler on this page is used to
            ' delete a item from the database.
            '
            '*******************************************************

            Protected Sub TimeEntryGrid_OnDelete(ByVal sender As [Object], ByVal e As DataGridCommandEventArgs) Handles TimeEntryGrid.DeleteCommand
                Dim entryLogID As Integer = Convert.ToInt32(TimeEntryGrid.DataKeys(CInt(e.Item.ItemIndex)))
                BusinessLogicLayer.TimeEntry.Remove(entryLogID)
                BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate)
            End Sub 'TimeEntryGrid_OnDelete

            '*******************************************************
            '
            ' TimeEntryGrid_OnEdit server event handler on this page is used to
            ' start in-line-editing mode for TimeEntryGrid.
            '
            '*******************************************************

            Protected Sub TimeEntryGrid_OnEdit(ByVal sender As [Object], ByVal e As DataGridCommandEventArgs) Handles TimeEntryGrid.EditCommand
                TimeEntryGrid.EditItemIndex = e.Item.ItemIndex

                ' Saves current info on temporary, use later in TimeEntryGrid_Itembound()
                _userInput.Day = CType(e.Item.FindControl("EntryDay"), Label).Text
                _userInput.ProjectName = CType(e.Item.FindControl("EntryProject"), Label).Text.Trim()
                _userInput.ProjectID = Convert.ToInt32(CType(e.Item.FindControl("EntryProjectID"), Label).Text)
                _userInput.CategoryName = CType(e.Item.FindControl("EntryCategory"), Label).Text.Trim()
                _userInput.Duration = Convert.ToDecimal(CType(e.Item.FindControl("EntryDuration"), Label).Text)
                _userInput.Description = CType(e.Item.FindControl("GridDescription"), Label).Text.Trim()
                BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate)
            End Sub 'TimeEntryGrid_OnEdit

            '*******************************************************
            '
            ' TimeEntryGrid_OnUpdate server event handler on this page is used to
            ' update the changes made from in-line-editing.
            '
            '*******************************************************

            Protected Sub TimeEntryGrid_OnUpdate(ByVal sender As [Object], ByVal e As DataGridCommandEventArgs) Handles TimeEntryGrid.UpdateCommand
                Dim item As DataGridItem = TimeEntryGrid.Items(TimeEntryGrid.EditItemIndex)

                Dim required As RequiredFieldValidator = CType(item.FindControl("RequiredFieldValidatorGridHours"), RequiredFieldValidator)
                Dim comparer As CompareValidator = CType(item.FindControl("CompareValidatorGridHours"), CompareValidator)

                required.Validate()
                comparer.Validate()

                ' Check for validation, if all valid, update the object to data entered and save it.
                If required.IsValid And comparer.IsValid Then
                    Dim entryLogID As Integer
                    Dim userID As Integer
                    Dim projectID As Integer
                    Dim categoryID As Integer
                    Dim taskDate As DateTime
                    Dim description As String
                    Dim duration As Decimal
                    Dim te As BusinessLogicLayer.TimeEntry = Nothing

                    ' Fill in properties for TimeEntry object.
                    entryLogID = Convert.ToInt32(TimeEntryGrid.DataKeys(CInt(e.Item.ItemIndex)))
                    userID = Convert.ToInt32(UserList.SelectedItem.Value)
                    projectID = Convert.ToInt32(CType(e.Item.FindControl("EntryProjects"), DropDownList).SelectedItem.Value)
                    categoryID = Convert.ToInt32(CType(e.Item.FindControl("EntryCategories"), DropDownList).SelectedItem.Value)
                    taskDate = Convert.ToDateTime(CType(e.Item.FindControl("EntryDays"), DropDownList).SelectedItem.Value)
                    description = TTSecurity.CleanStringRegex(CType(e.Item.FindControl("EntryDescription"), TextBox).Text)
                    duration = Convert.ToDecimal(CType(e.Item.FindControl("EntryHours"), TextBox).Text)

                    ' Save the TimeEntry object.
                    te = New BusinessLogicLayer.TimeEntry(entryLogID, userID, projectID, categoryID, taskDate, description, duration)
                    te.Save()

                    ' Quit in-line-editing mode.
                    TimeEntryGrid.EditItemIndex = -1
                    BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate)
                End If
            End Sub 'TimeEntryGrid_OnUpdate

            '*******************************************************
            '
            ' TimeEntryGrid_Sort server event handler on this page is used to
            ' sorts the TimeEntryGrid according to the sort field
            ' being selected.
            '
            '*******************************************************

            Private Sub TimeEntryGrid_Sort(ByVal [source] As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles TimeEntryGrid.SortCommand
                SortField = e.SortExpression
                BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate)
            End Sub 'TimeEntryGrid_Sort

            '*******************************************************
            '
            ' UserProjects_OnChange server event handler on this page is called when user makes
            ' a seletion change from project dropdown within in-line-editing, is used to
            ' update the category dropdown items according to the project selected
            '
            '*******************************************************

            Protected Sub UserProjects_OnChange(ByVal sender As Object, ByVal e As System.EventArgs)
                Dim item As DataGridItem = TimeEntryGrid.Items(TimeEntryGrid.EditItemIndex)

                ' Save temporary inputs so that data would be still there when user makes a Project selection
                ' and cause postback.
                If Not (item Is Nothing) Then
                    Dim txt As TextBox = CType(item.FindControl("EntryHours"), TextBox)
                    _userInput.Duration = Convert.ToDecimal(txt.Text)

                    Dim txtDesc As TextBox = CType(item.FindControl("EntryDescription"), TextBox)
                    _userInput.Description = txtDesc.Text

                    Dim EntryDays As DropDownList = CType(item.FindControl("EntryDays"), DropDownList)
                    _userInput.EntryDate = Convert.ToDateTime(EntryDays.SelectedItem.Value)
                    _userInput.Day = EntryDays.SelectedItem.Text
                End If
                Dim EntryProjects As DropDownList = CType(sender, DropDownList)
                _userInput.ProjectID = Convert.ToInt32(EntryProjects.SelectedItem.Value)
                _userInput.ProjectName = EntryProjects.SelectedItem.Text
                BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate)
            End Sub 'UserProjects_OnChange

            '*******************************************************
            '
            ' UserList_OnChange server event handler on this page is used
            ' when a selection is changed in UserList DropDown
            ' UserList items are limited to Role of the current user logged on the system.
            '
            '*******************************************************

            Private Sub UserList_OnChange(ByVal sender As Object, ByVal e As System.EventArgs) Handles UserList.SelectedIndexChanged
                TimeEntryGrid.EditItemIndex = -1
                BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate)
                BindEntryFields()
            End Sub 'UserList_OnChange

            '*******************************************************
            '
            ' WeekEnding_TextChanged server event handler on this page is used to
            ' select the correct week starting date and week ending date
            ' it gets called when user makes a selection change on calendar.
            '
            '*******************************************************

            Private Sub WeekEnding_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles WeekEnding.TextChanged
                TimeEntryGrid.EditItemIndex = -1
                BusinessLogicLayer.TimeEntry.FillCorrectStartEndDates(Convert.ToDateTime(WeekEnding.Text), _weekStartingDate, _weekEndingDate)
                WeekEnding.Text = _weekEndingDate.ToShortDateString()
                BindEntryFields()
                BindTimeSheet(_user.UserID, Convert.ToInt32(UserList.SelectedItem.Value), _weekStartingDate, _weekEndingDate)
            End Sub 'WeekEnding_TextChanged

            Property SortField() As String
                Get
                    Dim o As Object = ViewState("SortField")
                    If o Is Nothing Then
                        Return [String].Empty
                    End If
                    Return CStr(o)
                End Get

                Set(ByVal Value As String)
                    If Value = SortField Then
                        ' same as current sort file, toggle sort direction
                        SortAscending = Not SortAscending
                    End If
                    ViewState("SortField") = Value
                End Set
            End Property

            '*********************************************************************
            '
            ' SortAscending property is tracked in ViewState
            '
            '*********************************************************************

            Property SortAscending() As Boolean
                Get
                    Dim o As Object = ViewState("SortAscending")
                    If o Is Nothing Then
                        Return True
                    End If
                    Return CBool(o)
                End Get

                Set(ByVal Value As Boolean)
                    ViewState("SortAscending") = Value
                End Set
            End Property

            Private Sub InitializeComponent()

            End Sub
        End Class 'TimeEntry
    End Namespace 'ASPNET.StarterKit.TimeTracker.Web