Index Page - Deductions - Code

Index.razor

@page "/"
@using BlazorWallAreaCalculator.Data
@inject IProjectService ProjectService
@inject IRoomService RoomService
@inject IWallService WallService
@inject IDeductionService DeductionService
@inject SfDialogService DialogService

<PageTitle>Wall Area Calculator</PageTitle>

<div class="control_wrapper">
    <SfDropDownList TItem="Project"
                    TValue="string"
                    DataSource="@projects"
                    Placeholder="Select a Project"
                    PopupHeight="200px"
                    PopupWidth="250px">
        <DropDownListFieldSettings Text="ProjectName" Value="ProjectID"></DropDownListFieldSettings>
        <DropDownListEvents TItem="Project" TValue="string" ValueChange="@OnChangeProject"></DropDownListEvents>
    </SfDropDownList>
</div>
<hr />

<h6><b>Rooms</b></h6>

<SfGrid @ref="RoomGrid"
        DataSource="@rooms"
        AllowSorting="true"
        AllowResizing="true"
        Height="70"
        Toolbar="@RoomToolbaritems">
    <GridColumns>
        <GridColumn Field="@nameof(Room.RoomName)"
                    HeaderText="Room Name"
                    TextAlign="@TextAlign.Left"
                    Width="50">
        </GridColumn>
    </GridColumns>
    <GridEvents OnToolbarClick="RoomToolbarClickHandler" TValue="Room" RowSelected="RoomRowSelectHandler"></GridEvents>
</SfGrid>

<hr />
<h6><b>Walls</b></h6>

<SfGrid @ref="WallGrid"
        DataSource="@walls"
        AllowSorting="true"
        AllowResizing="true"
        Height="150"
        Toolbar="@WallToolbaritems">
    <GridColumns>
        <GridColumn Field="@nameof(Wall.WallName)"
                    HeaderText="Wall Name"
                    TextAlign="@TextAlign.Left"
                    Width="50">
        </GridColumn>
        <GridColumn Field="@nameof(Wall.WallSqM)"
                    HeaderText="Area"
                    Format="n3"
                    TextAlign="@TextAlign.Right"
                    Width="50">
        </GridColumn>
    </GridColumns>
    <GridAggregates>
        <GridAggregate>
            <GridAggregateColumns>
                <GridAggregateColumn Field=@nameof(Wall.WallSqM) Type="AggregateType.Sum" Format="n3">
                    <FooterTemplate>
                        @{
                            var aggregate = (context as AggregateTemplateContext);
                            <div>
                                <p>Total Net Area (SqM):   @aggregate.Sum</p>
                            </div>
                        }
                    </FooterTemplate>
                </GridAggregateColumn>
            </GridAggregateColumns>
        </GridAggregate>
    </GridAggregates>

    <GridEvents OnToolbarClick="WallToolbarClickHandler" TValue="Wall" RowSelected="WallRowSelectHandler"></GridEvents>
</SfGrid>

<hr />
<h6><b>Deductions</b></h6>

<SfGrid ID="DeductionGrid"
        DataSource="@deductions"
        AllowSorting="true"
        AllowResizing="true"
        Height="125"
        Toolbar="@DeductionToolbaritems">
    <GridColumns>
        <GridColumn Field="@nameof(Deduction.DeductionName)"
                    HeaderText="Description"
                    TextAlign="@TextAlign.Left"
                    Width="50">
        </GridColumn>
        <GridColumn Field="@nameof(Deduction.SqM)"
                    HeaderText="Area SqM"
                    TextAlign="@TextAlign.Right"
                    Format="n3"
                    Width="20">
            <Template>
                @{
                    var value = (context as Deduction);

                    decDeductionWidth = Convert.ToDecimal(value.DeductionWidth);
                    decDeductionHeight = Convert.ToDecimal(value.DeductionHeight);
                    decimal SqM = decimal.Round(((decDeductionWidth * decDeductionHeight) / 1000000), 3, MidpointRounding.AwayFromZero);
                    string SqMString = SqM.ToString("F3");
                    <div>@SqMString</div>
                }
            </Template>
        </GridColumn>
    </GridColumns>

    <GridEvents OnToolbarClick="DeductionToolbarClickHandler"
                TValue="Deduction" RowSelected="DeductionRowSelectHandler">
    </GridEvents>
</SfGrid>

<SfDialog @ref="DialogRoom" IsModal="true" Width="420px" ShowCloseIcon="false" Visible="false" AllowDragging="true">
    <DialogTemplates>
        <Header> @dialogTitle</Header>
        <Content>
            <EditForm Model="@roomAddEdit" OnValidSubmit="@RoomSave">
                <div>
                    <SfTextBox Enabled="true" Placeholder="Room Name"
                               FloatLabelType="@FloatLabelType.Always"
                               @bind-Value="roomAddEdit.RoomName">
                    </SfTextBox>
                </div>
                <br /><br />
                <div class="e-footer-content">
                    <div class="button-container">
                        <button type="submit" class="e-btn e-normal e-primary">Save</button>
                        <button type="button" class="e-btn e-normal" @onclick="@CancelRoom">Cancel</button>
                    </div>
                </div>
            </EditForm>
        </Content>
    </DialogTemplates>
</SfDialog>

<SfDialog @ref="DialogWall" IsModal="true" Width="420px" ShowCloseIcon="false" Visible="false" AllowDragging="true">
    <DialogTemplates>
        <Header> @dialogTitle</Header>
        <Content>
            <EditForm Model="@wallAddEdit" OnValidSubmit="@WallSave">
                <div>
                    <SfTextBox Enabled="true" Placeholder="Wall Name"
                               FloatLabelType="@FloatLabelType.Always"
                               @bind-Value="wallAddEdit.WallName">
                    </SfTextBox>

                    <div>
                        <br />
                    </div>

                    <hr />
                    @if (IsAdd)
                    {
                        <SfDropDownList TItem="WallType"
                                        TValue="string"
                                        FloatLabelType="@FloatLabelType.Always"
                                        DataSource="@LocalData"
                                        Placeholder="Select a Wall Type"
                                        PopupHeight="200px"
                                        PopupWidth="250px">
                            <DropDownListFieldSettings Text="WallTypeName" Value="WallTypeID"></DropDownListFieldSettings>
                            <DropDownListEvents TItem="WallType" TValue="string" ValueChange="@OnChangeWallType"></DropDownListEvents>
                        </SfDropDownList>
                    }
                    else
                    {
                        <SfTextBox Enabled="false" Placeholder="Wall Type"
                                   FloatLabelType="@FloatLabelType.Always"
                                   @bind-Value="wallAddEdit.WallTypeName">
                        </SfTextBox>
                    }

                    @switch (wallAddEdit.WallTypeName)
                    {
                        case "Simple":
                            <div>
                                <br />
                                <img src=".\\media\\simple.jpg" />
                                <br />
                            </div>

                            <div class="grid-container">
                                <div class="grid-child left-column">
                                    <SfNumericTextBox Enabled="true"
                                                      Placeholder="Wall Length (mm)"
                                                      Format="n0"
                                                      FloatLabelType="@FloatLabelType.Always"
                                                      @bind-Value="wallAddEdit.WallLengthMax"
                                                      ShowSpinButton=false
                                                      CssClass="e-style">
                                    </SfNumericTextBox>
                                </div>
                                <div class="grid-child right-column">
                                    <SfNumericTextBox Enabled="true"
                                                      Placeholder="Wall Height (mm)"
                                                      Format="n0"
                                                      FloatLabelType="@FloatLabelType.Always"
                                                      @bind-Value="wallAddEdit.WallHeightMax"
                                                      ShowSpinButton=false
                                                      CssClass="e-style">
                                    </SfNumericTextBox>
                                </div>
                            </div>


                            break;

                        case "Complex - Left":
                            <div>
                                <br />
                                <img src=".\\media\\complex-left.jpg" />
                                <br />
                            </div>
                            <div class="grid-container">
                                <div class="grid-child left-column">
                                    <SfNumericTextBox Enabled="true"
                                                      Placeholder="Max Wall Length (mm)"
                                                      Format="n0"
                                                      FloatLabelType="@FloatLabelType.Always"
                                                      Width="50"
                                                      @bind-Value="wallAddEdit.WallLengthMax"
                                                      ShowSpinButton=false
                                                      CssClass="e-style">
                                    </SfNumericTextBox>

                                    <SfNumericTextBox Enabled="true"
                                                      Placeholder="Min Wall Length (mm)"
                                                      Format="n0"
                                                      FloatLabelType="@FloatLabelType.Always"
                                                      Width="50"
                                                      @bind-Value="wallAddEdit.WallLengthMin"
                                                      ShowSpinButton=false
                                                      CssClass="e-style">
                                    </SfNumericTextBox>
                                </div>

                                <div class="grid-child right-column">
                                    <SfNumericTextBox Enabled="true"
                                                      Placeholder="Max Wall Height (mm)"
                                                      Format="n0"
                                                      FloatLabelType="@FloatLabelType.Always"
                                                      Width="50"
                                                      @bind-Value="wallAddEdit.WallHeightMax"
                                                      ShowSpinButton=false
                                                      CssClass="e-style">
                                    </SfNumericTextBox>

                                    <SfNumericTextBox Enabled="true"
                                                      Placeholder="Min Wall Height (mm)"
                                                      Format="n0"
                                                      FloatLabelType="@FloatLabelType.Always"
                                                      Width="50"
                                                      @bind-Value="wallAddEdit.WallHeightMin"
                                                      ShowSpinButton=false
                                                      CssClass="e-style">
                                    </SfNumericTextBox>
                                </div>

                            </div>


                            break;

                        case "Complex - Right":
                            <div>
                                <br />
                                <img src=".\\media\\complex-right.jpg" />
                                <br />
                            </div>

                            <div class="grid-container">
                                <div class="grid-child left-column">
                                    <SfNumericTextBox Enabled="true"
                                                      Placeholder="Max Wall Length (mm)"
                                                      Format="n0"
                                                      FloatLabelType="@FloatLabelType.Always"
                                                      Width="50"
                                                      @bind-Value="wallAddEdit.WallLengthMax"
                                                      ShowSpinButton=false
                                                      CssClass="e-style">
                                    </SfNumericTextBox>

                                    <SfNumericTextBox Enabled="true"
                                                      Placeholder="Min Wall Length (mm)"
                                                      Format="n0"
                                                      FloatLabelType="@FloatLabelType.Always"
                                                      Width="50"
                                                      @bind-Value="wallAddEdit.WallLengthMin"
                                                      ShowSpinButton=false
                                                      CssClass="e-style">
                                    </SfNumericTextBox>
                                </div>

                                <div class="grid-child right-column">
                                    <SfNumericTextBox Enabled="true"
                                                      Placeholder="Max Wall Height (mm)"
                                                      Format="n0"
                                                      FloatLabelType="@FloatLabelType.Always"
                                                      Width="50"
                                                      @bind-Value="wallAddEdit.WallHeightMax"
                                                      ShowSpinButton=false
                                                      CssClass="e-style">
                                    </SfNumericTextBox>

                                    <SfNumericTextBox Enabled="true"
                                                      Placeholder="Min Wall Height (mm)"
                                                      Format="n0"
                                                      FloatLabelType="@FloatLabelType.Always"
                                                      Width="50"
                                                      @bind-Value="wallAddEdit.WallHeightMin"
                                                      ShowSpinButton=false
                                                      CssClass="e-style">
                                    </SfNumericTextBox>
                                </div>

                            </div>


                            break;
                    }



                </div>
                <br /><br />
                <div class="e-footer-content">
                    <div class="button-container">
                        <button type="submit" class="e-btn e-normal e-primary">Save</button>
                        <button type="button" class="e-btn e-normal" @onclick="@CancelWall">Cancel</button>
                    </div>
                </div>
            </EditForm>
        </Content>
    </DialogTemplates>
</SfDialog>

<SfDialog @ref="DialogDeduction" IsModal="true" Width="420px" ShowCloseIcon="false" Visible="false" AllowDragging="true">
    <DialogTemplates>
        <Header> @dialogTitle</Header>
        <Content>
            <EditForm Model="@deductionAddEdit" OnValidSubmit="@DeductionSave">
                <div>
                    <SfTextBox Enabled="true" Placeholder="Description"
                               FloatLabelType="@FloatLabelType.Always"
                               @bind-Value="deductionAddEdit.DeductionName">
                    </SfTextBox>

                    <div class="grid-container">
                        <div class="grid-child left-column">
                            <SfNumericTextBox Enabled="true"
                                              Placeholder="Width (mm)"
                                              Format="n0"
                                              FloatLabelType="@FloatLabelType.Always"
                                              @bind-Value="deductionAddEdit.DeductionWidth"
                                              ShowSpinButton=false
                                              CssClass="e-style">
                            </SfNumericTextBox>
                        </div>
                        <div class="grid-child right-column">
                            <SfNumericTextBox Enabled="true"
                                              Placeholder="Height (mm)"
                                              Format="n0"
                                              FloatLabelType="@FloatLabelType.Always"
                                              @bind-Value="deductionAddEdit.DeductionHeight"
                                              ShowSpinButton=false
                                              CssClass="e-style">
                            </SfNumericTextBox>
                        </div>
                    </div>
                </div>
                <br /><br />
                <div class="e-footer-content">
                    <div class="button-container">
                        <button type="submit" class="e-btn e-normal e-primary">Save</button>
                        <button type="button" class="e-btn e-normal" @onclick="@CancelDeduction">Cancel</button>
                    </div>
                </div>
            </EditForm>
        </Content>
    </DialogTemplates>
</SfDialog>



<style>
    .control_wrapper {
        width: 250px;
    }

    .e-numeric.e-style .e-control.e-numerictextbox {
        text-align: right;
        padding: 0px 5px 0px 0px;
    }

    .grid-container {
        display: grid;
        max-width: 400px; /* Maximum width of the whole container - in this case both columns */
        grid-template-columns: 1fr 1fr; /* Relative width of each column (1fr 1fr is equivalent to, say, 33fr 33fr */
        grid-gap: 100px; /* size of the gap between columns */
    }

</style>

@code {
    public IEnumerable<Project>? projects;
    public IEnumerable<Room>? rooms;
    public IEnumerable<Wall>? walls;
    public IEnumerable<Deduction>? deductions;

    [Parameter]
    public int SelectedProjectID { get; set; } = 0;
    public int SelectedRoomID { get; set; } = 0;

    public int SelectedWallID { get; set; } = 0;
    public string SelectedWallName { get; set; } = string.Empty;
    public decimal SelectedWallSqM { get; set; } = decimal.Zero;

    public int SelectedDeductionID { get; set; } = 0;
    public string SelectedDeductionName { get; set; } = string.Empty;
    public int SelectedDeductionWidth { get; set; } = 0;
    public int SelectedDeductionHeight { get; set; } = 0;

    SfGrid<Room>? RoomGrid;
    SfGrid<Wall>? WallGrid;

    SfDialog? DialogRoom;
    Room roomAddEdit = new Room();

    SfDialog? DialogWall;
    Wall wallAddEdit = new Wall();

    SfDialog? DialogDeduction;
    Deduction deductionAddEdit = new Deduction();

    public string SelectedWallTypeName { get; set; } = string.Empty;

    public bool IsAdd { get; set; }

    public string dialogTitle = "";
    public string SelectedRoomName { get; set; } = string.Empty;

    private List<ItemModel> RoomToolbaritems = new List<ItemModel>();
    private List<ItemModel> WallToolbaritems = new List<ItemModel>();
    private List<ItemModel> DeductionToolbaritems = new List<ItemModel>();

    public int SelectedWallLengthMax { get; set; } = 0;
    public int SelectedWallLengthMin { get; set; } = 0;
    public int SelectedWallHeightMax { get; set; } = 0;
    public int SelectedWallHeightMin { get; set; } = 0;

    public decimal decWallSqM = decimal.Zero;
    public decimal decWallLengthMax = decimal.Zero;
    public decimal decWallLengthMin = decimal.Zero;
    public decimal decWallHeightMax = decimal.Zero;
    public decimal decWallHeightMin = decimal.Zero;
    public decimal decDeductionWidth = decimal.Zero;
    public decimal decDeductionHeight = decimal.Zero;

    protected override async Task OnInitializedAsync()
    {
        //Populate the list of projects objects from the Project table.
        projects = await ProjectService.ProjectReadAll();

        RoomToolbaritems.Add(new ItemModel() { Text = "Add", TooltipText = "Add a new room", PrefixIcon = "e-add" });
        RoomToolbaritems.Add(new ItemModel() { Text = "Edit", TooltipText = "Edit selected room", PrefixIcon = "e-edit" });
        RoomToolbaritems.Add(new ItemModel() { Text = "Delete", TooltipText = "Delete selected room", PrefixIcon = "e-delete" });

        WallToolbaritems.Add(new ItemModel() { Text = "Add", TooltipText = "Add a new wall", PrefixIcon = "e-add" });
        WallToolbaritems.Add(new ItemModel() { Text = "Edit", TooltipText = "Edit selected wall", PrefixIcon = "e-edit" });
        WallToolbaritems.Add(new ItemModel() { Text = "Delete", TooltipText = "Delete selected wall", PrefixIcon = "e-delete" });

        DeductionToolbaritems.Add(new ItemModel() { Text = "Add", TooltipText = "Add a deduction", PrefixIcon = "e-add" });
        DeductionToolbaritems.Add(new ItemModel() { Text = "Edit", TooltipText = "Edit selected deduction", PrefixIcon = "e-edit" });
        DeductionToolbaritems.Add(new ItemModel() { Text = "Delete", TooltipText = "Delete selected deduction", PrefixIcon = "e-delete" });
    }

    public class WallType
    {
        public int WallTypeID { get; set; } = 0;
        public string WallTypeName { get; set; } = string.Empty;
    }

    List<WallType> LocalData = new List<WallType>
    {
        new WallType() {WallTypeID =1, WallTypeName= "Simple" },
        new WallType() {WallTypeID =2, WallTypeName= "Complex - Left" },
        new WallType() {WallTypeID =3, WallTypeName= "Complex - Right" },
    };

    #region Rooms

    public async Task RoomToolbarClickHandler(ClickEventArgs args)
    {
        if (args.Item.Text == "Add")
        {
            //Code for adding goes here
            //Check that a Project has been selected:
            if (SelectedProjectID == 0)
            {
                await DialogService.AlertAsync("Please select a project.", "No Project Selected");
                return;
            }

            dialogTitle = "Add a Room";
            roomAddEdit = new();
            await DialogRoom.ShowAsync(false);

        }
        if (args.Item.Text == "Edit")
        {
            //Code for editing
            dialogTitle = "Edit a Room";

            //Check that a Room has been selected
            if (SelectedRoomID == 0)
            {
                await DialogService.AlertAsync("Please select a room.", "No Room Selected");
                return;
            }
            else
            {
                //populate roomAddEdit (temporary data set used for the editing process)
                roomAddEdit = new();
                roomAddEdit.RoomID = SelectedRoomID;
                roomAddEdit.RoomName = SelectedRoomName;
                await DialogRoom.ShowAsync(false);
            }

        }
        if (args.Item.Text == "Delete")
        {
            //Code for deleting
            if (SelectedRoomID == 0)
            {
                await DialogService.AlertAsync("Please select a room.", "No Room Selected");
                return;
            }
            else
            {
                bool isConfirm = await DialogService.ConfirmAsync(
                    "Are you sure you want to delete " + SelectedRoomName + "?",
                    "Delete " + SelectedRoomName);
                if (isConfirm == true)
                {
                    await RoomService.RoomDelete(SelectedRoomID);
                    //Refresh datagrid
                    rooms = await RoomService.RoomsReadByProject(SelectedProjectID);
                    walls = Enumerable.Empty<Wall>();
                    deductions = Enumerable.Empty<Deduction>();
                    SelectedRoomID = 0;
                    StateHasChanged();
                }
            }
        }
    }

    public async Task RoomRowSelectHandler(RowSelectEventArgs<Room> args)
    {
        //{args.Data} returns the current selected records.
        SelectedRoomID = args.Data.RoomID;
        SelectedRoomName = args.Data.RoomName;
        walls = await WallService.WallsReadByRoom(SelectedRoomID);
        deductions = Enumerable.Empty<Deduction>();
    }

    public async Task OnChangeProject(ChangeEventArgs<string, Project> args)
    {
        SelectedProjectID = args.ItemData.ProjectID;
        rooms = await RoomService.RoomsReadByProject(SelectedProjectID);
        SelectedRoomID = 0;
        walls = Enumerable.Empty<Wall>();
        deductions = Enumerable.Empty<Deduction>();
    }

    protected async Task RoomSave()
    {
        if (roomAddEdit.RoomID == 0)
        {
            // Insert if RoomID is zero.
            //Check for duplicates
            int duplicates = await RoomService.CountRoomsByNameAndProject(roomAddEdit.RoomName, SelectedProjectID);

            if (duplicates == 0)
            {
                roomAddEdit.ProjectID = SelectedProjectID;

                try
                {
                    await RoomService.RoomCreate(roomAddEdit);
                    //Refresh datagrid
                    rooms = await RoomService.RoomsReadByProject(SelectedProjectID);
                    walls = Enumerable.Empty<Wall>();
                    deductions = Enumerable.Empty<Deduction>();
                    SelectedRoomID = 0;
                    StateHasChanged();
                    await DialogRoom.HideAsync();
                }
                catch
                {
                    // Display warning message
                    await DialogService.AlertAsync("An unexpected error has occured.", "Unknown Error");
                    return;
                }
            }
            else
            {
                //Room already exists - warn the user
                await DialogService.AlertAsync("This Room already exists; it cannot be added again.", "Warning!");
                return;
            }
        }
        else
        {
            // Record is being edited
            // Check for duplicates
            int duplicates = await RoomService.CountRoomsByNameAndProjectAndId(roomAddEdit.RoomName, roomAddEdit.RoomID, SelectedProjectID);

            if (duplicates == 0)
            {
                try
                {
                    await RoomService.RoomUpdate(roomAddEdit);
                    //Refresh datagrid
                    rooms = await RoomService.RoomsReadByProject(SelectedProjectID);
                    StateHasChanged();
                    await DialogRoom.HideAsync();
                }
                catch
                {
                    // Display warning message
                    await DialogService.AlertAsync("An unexpected error has occured.", "Unknown Error");
                    return;
                }
            }
            else
            {
                //Room already exists - warn the user
                await DialogService.AlertAsync("This room already exists", "Room already Exists");
                return;
            }
        }
    }

    void CancelRoom()
    {
        DialogRoom.HideAsync();
    }

    #endregion

    #region Walls

    public void OnChangeWallType(ChangeEventArgs<string, WallType> args)
    {
        wallAddEdit.WallTypeID = args.ItemData.WallTypeID;
        wallAddEdit.WallTypeName = args.ItemData.WallTypeName;
    }
    public async Task WallToolbarClickHandler(ClickEventArgs args)
    {
        //Check that a room has been selected
        if (SelectedRoomID == 0)
        {
            await DialogService.AlertAsync("Please select a room.", "No Room Selected");
            return;
        }
        if (args.Item.Text == "Add")
        {
            //Code for adding goes here
            IsAdd = true;
            SelectedWallID = 0;
            SelectedWallTypeName = string.Empty;

            dialogTitle = "Add a Wall";
            wallAddEdit = new();
            await DialogWall.ShowAsync(false);
        }
        if (args.Item.Text == "Edit")
        {
            //Code for editing
            IsAdd = false;
            dialogTitle = "Edit a Wall";

            //Check that a wall has been selected
            if (SelectedWallID == 0)
            {
                await DialogService.AlertAsync("Please select a wall.", "No Wall Selected");
                return;
            }
            else
            {
                //populate wallAddEdit (temporary data set used for the editing process)
                wallAddEdit.WallID = SelectedWallID;
                wallAddEdit.RoomID = SelectedRoomID;
                wallAddEdit.WallName = SelectedWallName;
                wallAddEdit.WallSqM = SelectedWallSqM;
                wallAddEdit.WallTypeName = SelectedWallTypeName;                                                    //Omitted for Dynamic Dialog video
                wallAddEdit.WallLengthMax = SelectedWallLengthMax;
                wallAddEdit.WallLengthMin = SelectedWallLengthMin;
                wallAddEdit.WallHeightMax = SelectedWallHeightMax;
                wallAddEdit.WallHeightMin = SelectedWallHeightMin;

                await DialogWall.ShowAsync(false);
                StateHasChanged();
            }

        }
        if (args.Item.Text == "Delete")
        {
            //Code for deleting
            if (SelectedWallID == 0)
            {
                await DialogService.AlertAsync("Please select a wall.", "No Wall Selected");
                return;
            }
            else
            {
                bool isConfirm = await DialogService.ConfirmAsync(
                    "Are you sure you want to delete " + SelectedWallName + "?",
                    "Delete " + SelectedWallName);
                if (isConfirm == true)
                {
                    await WallService.WallDelete(SelectedWallID);
                    //Refresh datagrid
                    walls = await WallService.WallsReadByRoom(SelectedRoomID);
                    deductions = Enumerable.Empty<Deduction>();
                    StateHasChanged();
                    SelectedWallID = 0;
                }
            }

        }
    }

    public async Task WallRowSelectHandler(RowSelectEventArgs<Wall> args)  //Note <Wall>
    {
        //{args.Data} returns the current selected records.
        SelectedWallID = args.Data.WallID;
        SelectedWallName = args.Data.WallName;
        SelectedWallTypeName = args.Data.WallTypeName;                                                              //Omitted for Dynamic Dialog video
        SelectedWallLengthMax = args.Data.WallLengthMax;
        SelectedWallLengthMin = args.Data.WallLengthMin;
        SelectedWallHeightMax = args.Data.WallHeightMax;
        SelectedWallHeightMin = args.Data.WallHeightMin;

        deductions = await DeductionService.DeductionsReadByWall(SelectedWallID);
        StateHasChanged();
    }

    protected async Task WallSave()
    {
        //Run CalculateArea
        await CalculateArea(wallAddEdit, 0);

        if (wallAddEdit.WallID == 0)    //It's an insert
        {
            //Check for duplicates
            int duplicates = await WallService.CountWallsByNameAndRoom(wallAddEdit.WallName, SelectedRoomID);

            if (duplicates == 0)
            {
                wallAddEdit.RoomID = SelectedRoomID;

                try
                {
                    await WallService.WallCreate(wallAddEdit);

                    //Refresh datagrid
                    walls = await WallService.WallsReadByRoom(SelectedRoomID);
                    deductions = Enumerable.Empty<Deduction>();
                    StateHasChanged();
                    await DialogWall.HideAsync();
                }
                catch
                {
                    // Display warning message
                    await DialogService.AlertAsync("An unexpected error has occured.", "Unknown Error");
                    return;
                }
            }
            else
            {
                //Room already exists - warn the user
                await DialogService.AlertAsync("This wall already exists for this room; it cannot be added again.", "Warning!");
                return;
            }
        }
        else                            //It's an edit
        {
            //It's an edit
            // Check for duplicates
            int duplicates = await WallService.CountWallsByNameAndRoomAndId(wallAddEdit.WallName, wallAddEdit.WallID, SelectedRoomID);

            if (duplicates == 0)
            {
                try
                {
                    //Upate Wall
                    await WallService.WallUpdate(wallAddEdit);

                    //Refresh datagrid
                    walls = await WallService.WallsReadByRoom(SelectedRoomID);

                    StateHasChanged();
                    await DialogWall.HideAsync();
                }
                catch
                {
                    // Display warning message
                    await DialogService.AlertAsync("An unexpected error has occured.", "Unknown Error");
                    return;
                }
            }
            else
            {
                //Wall already exists - warn the user
                await DialogService.AlertAsync("This wall already exists for this room", "Wall Already Exists");
                return;
            }
        }

        wallAddEdit = new();
    }

    void CancelWall()
    {
        DialogWall.HideAsync();
    }

    #endregion

    #region Deduction
    public async Task DeductionToolbarClickHandler(ClickEventArgs args)
    {
        //Check that a Wall has been selected
        if (args.Item.Text == "Add")
        {
            if (SelectedWallID == 0)
            {
                await DialogService.AlertAsync("Please select a wall.", "No Wall Selected");
                return;
            }
            else
            {
                //Code for adding goes here
                dialogTitle = "Add a Deduction";
                deductionAddEdit = new();
                deductionAddEdit.WallID = SelectedWallID;
                await DialogDeduction.ShowAsync(false);
            }
        }
        if (args.Item.Text == "Edit")
        {
            //Code for editing
            dialogTitle = "Edit a Deduction";

            //Check that a wall has been selected
            if (SelectedDeductionID == 0)
            {
                await DialogService.AlertAsync("Please select a deduction.", "No Deduction Selected");
                return;
            }
            else
            {
                //populate deductionAddEdit (temporary data set used for the editing process)
                deductionAddEdit = new();
                deductionAddEdit.DeductionID = SelectedDeductionID;
                deductionAddEdit.WallID = SelectedWallID;
                deductionAddEdit.DeductionName = SelectedDeductionName;
                deductionAddEdit.DeductionWidth = SelectedDeductionWidth;
                deductionAddEdit.DeductionHeight = SelectedDeductionHeight;
                await DialogDeduction.ShowAsync(false);
                StateHasChanged();
            }
        }
        if (args.Item.Text == "Delete")
        {
            //Code for deleting
            if (SelectedDeductionID == 0)
            {
                await DialogService.AlertAsync("Please select a deduction.", "No Deduction Selected");
                return;
            }
            else
            {
                bool isConfirm = await DialogService.ConfirmAsync(
                    "Are you sure you want to delete " + SelectedDeductionName + "?",
                    "Delete " + SelectedDeductionName);
                if (isConfirm == true)
                {
                    await DeductionService.DeductionDelete(SelectedDeductionID);

                    //Always do area calculation
                    await CalculateArea(wallAddEdit, SelectedWallID);

                    //Refresh datagrid
                    deductions = await DeductionService.DeductionsReadByWall(SelectedWallID);
                    walls = await WallService.WallsReadByRoom(SelectedRoomID);

                    StateHasChanged();
                    SelectedWallID = 0;
                }
            }
        }
    }

    public void DeductionRowSelectHandler(RowSelectEventArgs<Deduction> args)
    {
        //{args.Data} returns the current selected record.
        SelectedDeductionID = args.Data.DeductionID;
        SelectedDeductionName = args.Data.DeductionName;
        SelectedDeductionWidth = args.Data.DeductionWidth;
        SelectedDeductionHeight = args.Data.DeductionHeight;
        StateHasChanged();
    }

    protected async Task DeductionSave()
    {
        if (deductionAddEdit.DeductionID == 0)    //It's an insert
        {
            await DeductionService.DeductionCreate(deductionAddEdit);
        }
        else
        {
            await DeductionService.DeductionUpdate(deductionAddEdit);
        }

        //Always do area calculation
        await CalculateArea(wallAddEdit, SelectedWallID);

        deductions = await DeductionService.DeductionsReadByWall(SelectedWallID);
        walls = await WallService.WallsReadByRoom(SelectedRoomID);
        await DialogDeduction.HideAsync();

        StateHasChanged();
    }

    void CancelDeduction()
    {
        DialogDeduction.HideAsync();
    }

    #endregion

    #region Area Calculation
    public async Task CalculateArea(Wall wallAddEdit, int SelectedWallID)
    {
        //If coming from WallSave, wallAddEdit will be populated.
        //If coming from Deductions wallAddEdit will be empty (but SelectedWallID will be non-zero),
        //so we need to populate wallAddEdit using SelectedWallID

        if (SelectedWallID != 0)
        {
            wallAddEdit = await WallService.WallReadOne(SelectedWallID);
        }

        switch (wallAddEdit.WallTypeName)
        {
            case "Simple":

                decWallLengthMax = Convert.ToDecimal(wallAddEdit.WallLengthMax);
                decWallHeightMax = Convert.ToDecimal(wallAddEdit.WallHeightMax);

                decimal area = decimal.Round((decWallLengthMax * decWallHeightMax) / 1000000, 3, MidpointRounding.AwayFromZero);

                wallAddEdit.WallSqM = area;

                break;

            case "Complex - Left":
            case "Complex - Right":

                decWallLengthMax = Convert.ToDecimal(wallAddEdit.WallLengthMax);
                decWallHeightMax = Convert.ToDecimal(wallAddEdit.WallHeightMax);
                decWallLengthMin = Convert.ToDecimal(wallAddEdit.WallLengthMin);
                decWallHeightMin = Convert.ToDecimal(wallAddEdit.WallHeightMin);

                decimal area1 = (decWallHeightMax * decWallLengthMin);
                decimal area2 = ((decWallLengthMax - decWallLengthMin) * decWallHeightMin);
                decimal area3 = decimal.Round((((decWallLengthMax - decWallLengthMin) * (decWallHeightMax - decWallHeightMin)) / 2), 3, MidpointRounding.AwayFromZero);

                wallAddEdit.WallSqM = decimal.Round(((area1 + area2 + area3) / 1000000), 3, MidpointRounding.AwayFromZero);

                break;
        }

        //Calculate Deductions

        //Always set Deductions total to 0
        decimal wallDeductionTotal = decimal.Zero;

        // If a new wall is being inserted wallAddEdit.WallID will be 0 and we can skip deductions
        if (wallAddEdit.WallID != 0)
        {
            //Get deductions for this Wall
            deductions = await DeductionService.DeductionsReadByWall(wallAddEdit.WallID);

            foreach (var deductionItem in deductions)
            {
                decDeductionWidth = Convert.ToDecimal(deductionItem.DeductionWidth);
                decDeductionHeight = Convert.ToDecimal(deductionItem.DeductionHeight);
                decimal areaDeduction = decimal.Round((decDeductionWidth * decDeductionHeight), 3, MidpointRounding.AwayFromZero) / 1000000;

                wallDeductionTotal = wallDeductionTotal + areaDeduction;
            }

        }

        wallAddEdit.WallSqM = wallAddEdit.WallSqM - wallDeductionTotal;

        if (SelectedWallID != 0)  //SelectedWall is set to 0 for WallSave
        {
            // Otherwise, update wall record with revised calculation
            await WallService.WallUpdateArea(wallAddEdit.WallID, wallAddEdit.WallSqM);
        }

    }
    #endregion
}

DeductionsService.cs

using Dapper;
using System.Data.SQLite;
using System.Data;

namespace BlazorWallAreaCalculator.Data
{
    public class DeductionService : IDeductionService
    {
        // Database connection
        private readonly IConfiguration _configuration;
        public DeductionService(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public string connectionId = "Default";
        public string sqlCommand = "";
        private Deduction? deduction;

        // DeductionCreate
        public async Task<bool> DeductionCreate(Deduction deduction)
        {
            var parameters = new DynamicParameters();

            parameters.Add("@WallID", deduction.WallID, DbType.Int32);
            parameters.Add("@DeductionName", deduction.DeductionName, DbType.String);
            parameters.Add("@DeductionWidth", deduction.DeductionWidth, DbType.Int32);
            parameters.Add("@DeductionHeight", deduction.DeductionHeight, DbType.Int32);

            sqlCommand = "Insert into Deduction (WallID, DeductionName, DeductionWidth, DeductionHeight) ";
            sqlCommand += "values(@WallID, @DeductionName, @DeductionWidth, @DeductionHeight )";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                await conn.ExecuteAsync(sqlCommand, parameters);                
            }
            return true;
        }


        // DeductionRead
        public async Task<IEnumerable<Deduction>> DeductionReadAll()
        {
            IEnumerable<Deduction> deductions;

            sqlCommand = "Select * from Deduction ORDER BY DeductionName";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                deductions = await conn.QueryAsync<Deduction>(sqlCommand);
            }
            return deductions;
        }

        public async Task<IEnumerable<Deduction>> DeductionsReadByWall(int WallID)
        {
            IEnumerable<Deduction> deductions;
            var parameters = new DynamicParameters();
            parameters.Add("WallID", WallID, DbType.Int32);

            sqlCommand = "Select * from Deduction ";
            sqlCommand += "WHERE WallID  = @WallID";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                deductions = await conn.QueryAsync<Deduction>(sqlCommand, parameters);
            }
            return deductions;
        }

        public async Task<int> CountDeductionsByName(string DeductionName)
        {
            var parameters = new DynamicParameters();
            parameters.Add("@DeductionName", DeductionName, DbType.String);

            sqlCommand = "Select Count(*) from Deduction ";
            sqlCommand += "where Upper(DeductionName) = Upper(@DeductionName)";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                var countDeduction = await conn.QueryFirstOrDefaultAsync<int>(sqlCommand, parameters);
                return countDeduction;
            }
        }

        public async Task<int> CountDeductionsByNameAndId(string DeductionName, int DeductionID)
        {
            var parameters = new DynamicParameters();
            parameters.Add("@DeductionName", DeductionName, DbType.String);
            parameters.Add("@DeductionID", DeductionID, DbType.Int32);

            sqlCommand = "Select Count(*) from Deduction ";
            sqlCommand += "where Upper(DeductionName) = Upper(@DeductionName) ";
            sqlCommand += "and DeductionID <> @DeductionID";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                var countDeduction = await conn.QueryFirstOrDefaultAsync<int>(sqlCommand, parameters);
                return countDeduction;
            }
        }


        //DeductionUpdate
        public async Task<bool> DeductionUpdate(Deduction deduction)
        {
            var parameters = new DynamicParameters();

            parameters.Add("DeductionID", deduction.DeductionID, DbType.Int32);
            parameters.Add("DeductionName", deduction.DeductionName, DbType.String);
            parameters.Add("@DeductionWidth", deduction.DeductionWidth, DbType.Int32);
            parameters.Add("@DeductionHeight", deduction.DeductionHeight, DbType.Int32);

            sqlCommand = "Update Deduction ";
            sqlCommand += "SET DeductionName = @DeductionName, ";
            sqlCommand += "DeductionWidth = @DeductionWidth, ";
            sqlCommand += "DeductionHeight = @DeductionHeight ";
            sqlCommand += "WHERE DeductionID  = @DeductionID";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                await conn.ExecuteAsync(sqlCommand, parameters);                
            }
            return true;
        }


        //DeductionDelete
        public async Task<bool> DeductionDelete(Int32 DeductionID)
        {
            var parameters = new DynamicParameters();
            parameters.Add("@DeductionID", DeductionID, DbType.Int32);

            //PRAGMA is specific to SQLite and this command is required for DELETE CASCADE to work
            sqlCommand = "PRAGMA foreign_keys = ON;";
            sqlCommand += "Delete from Deduction ";
            sqlCommand += "WHERE DeductionID  = @DeductionID";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                await conn.ExecuteAsync(sqlCommand, parameters);                
            }
            return true;
        }


    }
}

IDeductionService.cs

namespace BlazorWallAreaCalculator.Data
{
    public interface IDeductionService
    {
        Task<bool> DeductionCreate(Deduction deduction);
        Task<IEnumerable<Deduction>> DeductionReadAll();
        Task<IEnumerable<Deduction>> DeductionsReadByWall(int WallID);
        Task<int> CountDeductionsByName(string DeductionName);
        Task<int> CountDeductionsByNameAndId(string DeductionName, int DeductionID);
        Task<bool> DeductionUpdate(Deduction deduction);
        Task<bool> DeductionDelete(Int32 DeductionID);
    }
}

WallService.cs

using Dapper;
using System.Data.SQLite;
using System.Data;

namespace BlazorWallAreaCalculator.Data
{
    public class WallService : IWallService
    {
        // Database connection
        private readonly IConfiguration _configuration;
        public WallService(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public string connectionId = "Default";
        public string sqlCommand = "";
        private Wall? wall;

        // WallCreate
        public async Task<bool> WallCreate(Wall wall)
        {
            var parameters = new DynamicParameters();

            parameters.Add("@RoomID", wall.RoomID, DbType.Int32);
            parameters.Add("@WallName", wall.WallName, DbType.String);
            parameters.Add("@WallTypeID", wall.WallTypeID, DbType.Int32);
            parameters.Add("@WallTypeName", wall.WallTypeName, DbType.String);
            parameters.Add("@WallLengthMax", wall.WallLengthMax, DbType.Int32);
            parameters.Add("@WallLengthMin", wall.WallLengthMin, DbType.Int32);
            parameters.Add("@WallHeightMax", wall.WallHeightMax, DbType.Int32);
            parameters.Add("@WallHeightMin", wall.WallHeightMin, DbType.Int32);
            parameters.Add("@WallSqM", wall.WallSqM, DbType.Decimal);

            sqlCommand = "Insert into Wall (RoomID, WallName, WallTypeID, WallTypeName, " +
                "WallLengthMax, WallLengthMin, WallHeightMax, WallHeightMin, WallSqM) ";
            sqlCommand += "values(@RoomID, @WallName, @WallTypeID, @WallTypeName, " +
                "@WallLengthMax, @WallLengthMin, @WallHeightMax, @WallHeightMin, @WallSqM) ";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                await conn.ExecuteAsync(sqlCommand, parameters);
            }
            return true;
        }


        // WallRead
        public async Task<IEnumerable<Wall>> WallsReadByRoom(int RoomID)
        {
            IEnumerable<Wall> walls;
            var parameters = new DynamicParameters();
            parameters.Add("RoomID", RoomID, DbType.Int32);

            sqlCommand = "Select * from Wall ";
            sqlCommand += "WHERE RoomID  = @RoomID";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                walls = await conn.QueryAsync<Wall>(sqlCommand, parameters);
            }
            return walls;
        }


        #region CountWalls
        public async Task<int> CountWallsByNameAndRoom(string WallName, int RoomID)
        {
            var parameters = new DynamicParameters();
            parameters.Add("@WallName", WallName, DbType.String);
            parameters.Add("@RoomID", RoomID, DbType.Int32);

            sqlCommand = "Select Count(*) from Wall ";
            sqlCommand += "where Upper(WallName) = Upper(@WallName) ";
            sqlCommand += "and RoomID = @RoomID";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                var countWall = await conn.QueryFirstOrDefaultAsync<int>(sqlCommand, parameters);
                return countWall;
            }
        }

        public async Task<int> CountWallsByNameAndRoomAndId(string WallName, int WallID, int RoomID)
        {
            var parameters = new DynamicParameters();
            parameters.Add("@WallName", WallName, DbType.String);
            parameters.Add("@WallID", WallID, DbType.Int32);
            parameters.Add("@RoomID", RoomID, DbType.Int32);
            sqlCommand = "Select Count(*) from Wall ";
            sqlCommand += "where Upper(WallName) = Upper(@WallName) ";
            sqlCommand += "and RoomID = @RoomID ";
            sqlCommand += "and WallID <> @WallID";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                var countWall = await conn.QueryFirstOrDefaultAsync<int>(sqlCommand, parameters);
                return countWall;
            }
        }
        #endregion



        //WallUpdate
        public async Task<bool> WallUpdate(Wall wall)
        {
            var parameters = new DynamicParameters();

            parameters.Add("@WallID", wall.WallID, DbType.Int32);
            parameters.Add("@WallName", wall.WallName, DbType.String);
            parameters.Add("@WallTypeID", wall.WallTypeID, DbType.Int32);
            parameters.Add("@WallTypeName", wall.WallTypeName, DbType.String);
            parameters.Add("@WallLengthMax", wall.WallLengthMax, DbType.Int32);
            parameters.Add("@WallLengthMin", wall.WallLengthMin, DbType.Int32);
            parameters.Add("@WallHeightMax", wall.WallHeightMax, DbType.Int32);
            parameters.Add("@WallHeightMin", wall.WallHeightMin, DbType.Int32);
            parameters.Add("@WallSqM", wall.WallSqM, DbType.Decimal);

            sqlCommand = "Update Wall ";
            sqlCommand += "SET WallName = @WallName, " +
                "WallTypeID = @WallTypeID, " +
                "WallTypeName = @WallTypeName, " +
                "WallLengthMax = @WallLengthMax, " +
                "WallLengthMin = @WallLengthMin, " +
                "WallHeightMax = @WallHeightMax, " +
                "WallHeightMin = @WallHeightMin, " +
                "WallSqM = @WallSqM ";
            sqlCommand += "WHERE WallID  = @WallID";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                await conn.ExecuteAsync(sqlCommand, parameters);
            }
            return true;
        }



        //WallDelete
        public async Task<bool> WallDelete(Int32 WallID)
        {
            var parameters = new DynamicParameters();
            parameters.Add("@WallID", WallID, DbType.Int32);

            //PRAGMA is specific to SQLite and this command is required for DELETE CASCADE to work
            sqlCommand = "PRAGMA foreign_keys = ON;";
            sqlCommand += "Delete from Wall ";
            sqlCommand += "WHERE WallID  = @WallID";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                await conn.ExecuteAsync(sqlCommand, parameters);                
            }
            return true;
        }

        //WallUpdateArea
        public async Task<bool> WallUpdateArea(int WallID, decimal WallSqM)
        {
            var parameters = new DynamicParameters();

            parameters.Add("@WallID", WallID, DbType.Int32);
            parameters.Add("@WallSqM", WallSqM, DbType.Decimal);

            sqlCommand = "Update Wall ";
            sqlCommand += "SET WallSqM = @WallSqM ";
            sqlCommand += "WHERE WallID  = @WallID";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                await conn.ExecuteAsync(sqlCommand, parameters);
            }
            return true;
        }

        public async Task<Wall> WallReadOne(Int32 @WallID)
        {
            var parameters = new DynamicParameters();
            parameters.Add("@WallID", WallID, DbType.Int32);

            sqlCommand = "Select * from Wall ";
            sqlCommand += "WHERE WallID  = @WallID";

            using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
            {
                wall = await conn.QueryFirstOrDefaultAsync<Wall>(sqlCommand, parameters);
            }
            return wall;
        }

    }
}

IWallService.cs

namespace BlazorWallAreaCalculator.Data
{
    public interface IWallService
    {
        Task<bool> WallCreate(Wall wall);
        Task<IEnumerable<Wall>> WallsReadByRoom(int RoomID);
        Task<Wall> WallReadOne(Int32 @WallID);
        Task<int> CountWallsByNameAndRoom(string WallName, int RoomID);
        Task<int> CountWallsByNameAndRoomAndId(string WallName, int WallID, int RoomID);
        Task<bool> WallUpdate(Wall wall);
        Task<bool> WallUpdateArea(int WallID, decimal WallSqM);
        Task<bool> WallDelete(Int32 WallID);
    }
}