Blazor - Despesas Pessoais com Gráfico de Barras e Pizza - III


Neste artigo vamos iniciar a criação de uma aplicação Blazor Web Assemply para gerenciar despesas pessoais onde vamos criar gráficos de barras e de pizza.

Continuando a segunda parte do artigo vamos exibir as despesas, implementar o formulário de Despesas e a exclusão de uma receita/despesa.

Exibindo as despesas

Vamos agora exibir as despesas criando na pasta Pages o componente Despesas.razor usando o mesmo procedimento e lógica que foi usado para implementar o componente Receitas.razor.

Nota: Em outro artigo poderei mostrar como otimizar a aplicação removendo esta lógica duplicada.

No projeto Client na pasta Pages crie o componente Despesas.razor :

@page "/despesas"
@inject HttpClient Http;

<div class="row">
    <div class="col-lg-8">
        <div class="card" >
            <div class="card-header" style="background-color: sandybrown; font-weight: bold">
                Despesas
            </div>
            <div class="card-body">
                <table class="table table-striped">
                    <thead>
                        <tr>
                            <th>Data</th>
                            <th>Categoria</th>
                            <th>Descricao</th>
                            <th>Valor</th>
                        </tr>
                    </thead>

                    <tbody>
                        @if (despesas == null)
                        {
                            <tr><td><em>Carregando...</em></td></tr>
                        }
                        else
                        {
                            @foreach (var despesa in despesas)
                            {
                    <tr>
                        <td>@despesa.Data.ToShortDateString()</td>
                        <td>@despesa.Categoria</td>
                        <td>@despesa.Descricao</td>
                        <td align="right">@despesa.Valor.ToString("C2", CultureInfo.CreateSpecificCulture("pt-BR"))</td>
                        <td><button type="button" class="btn btn-danger btn-sm">Excluir</button></td>

                    </tr>
                            }
                        }
                    </tbody>
                </table>
            </div>
        </div>
    </div>
    <div class="col-lg-4">
        <DespesaForm OnSubmitCallback="@Refresh"></DespesaForm>
    </div>

</div>
<div>&nbsp;</div>
@code {
    private Despesa[] despesas;
    protected async override Task OnInitializedAsync()
    {
        await CarregaDados();
    }

    protected async Task CarregaDados()
    {
        despesas = await Http.GetFromJsonAsync<Despesa[]>("api/Despesas");
        StateHasChanged();
    }

    public async Task Refresh()
    {
        await CarregaDados();
    }   

}

Criando o formulário de despesas

A seguir na pasta Components crie o componente DespesaForm.razor :

@inject HttpClient Http;
<div class="card">
    <div class="card-header" style="background-color: sandybrown; font-weight: bold">
        Incluir despesa
    </div>
    <div class="card-body">
        <EditForm Model="@despesa" OnValidSubmit="@HandleValidSubmit">
            <DataAnnotationsValidator />
            <ValidationSummary />
            <div class="form-group">
                <label for="datainput">Data</label>
                <InputDate class="form-control" id="datainput" @bind-Value="despesa.Data" />
            </div>
            <div class="form-group">
                <label for="descricaoinput">Descrição</label>
                <InputText class="form-control" id="descricaoinput" @bind-Value="despesa.Descricao" />
            </div>
            <div class="form-group">
                <label for="categoriainput">Categoria</label>
                <InputSelect class="form-control" id="categoriainput" @bind-Value="despesa.Categoria">
                    @{
                        foreach (var value in Enum.GetValues(typeof(CategoriaDespesa)))
                        {
                            <option value="@value">@value</option>
                        }
                    }
                </InputSelect>
            </div>
            <div class="form-group">
                <label for="valorinput">Valor</label>
                <InputNumber class="form-control" id="valorinput" @bind-Value="despesa.Valor" />
            </div>
            <div>
                <button type="submit" class="btn btn-primary">Enviar</button>
            </div>
        </EditForm>
    </div>
</div>
@code{
    private DespesaDTO despesa = new DespesaDTO { Data = DateTime.Today };
    [Parameter]
    public EventCallback OnSubmitCallback { get; set; }
    public async Task HandleValidSubmit()
    {
        await Http.PostAsJsonAsync<DespesaDTO>("api/Despesas", despesa);
        await OnSubmitCallback.InvokeAsync();
    }
}

Com isso já temos a exibição dos dados das receitas e despesas e dos respectivos formulários concluídos.

Executando o projeto teremos:

Agora vamos implementar a exclusão de receita e despesa.

Implementando a exclusão de receita e despesa

Vamos agora implementar a exclusão de uma receita ou despesa quando o usuário clica no botão Excluir nos componentes Receitas.razor e Despesas.razor.

Vamos usar uma janela de diálogo Modal para solicitar a confirmação do usuário antes de realizar a exclusão. Para isso vamos criar na pasta Components o componente ModelDialog.razor :

<div class="modal fade show" id="myModal" style="display:block; background-color: rgba(10,10,10,.8);" aria-modal="true" role="dialog">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title">@Title</h4>
                <button type="button" class="close" @onclick="@ModalCancel">&times;</button>
            </div>
            <div class="modal-body">
                <p>@Text</p>
            </div>
            <div class="modal-footer">
                @switch (DialogType)
                {
                    case ModalDialogType.Ok:
                        <button type="button" class="btn btn-primary" @onclick=@ModalOk>OK</button>
                        break;
                    case ModalDialogType.OkCancel:
                        <button type="button" class="btn" @onclick="@ModalCancel">Cancela</button>
                        <button type="button" class="btn btn-primary" @onclick=@ModalOk>OK</button>
                        break;
                    case ModalDialogType.DeleteCancel:
                        <button type="button" class="btn" @onclick="@ModalCancel">Cancela</button>
                        <button type="button" class="btn btn-danger" @onclick=@ModalOk>Deleta</button>
                        break;
                }
            </div>
        </div>
    </div>
</div>
@code {
    [Parameter]
    public EventCallback<bool> OnClose { get; set; }

    [Parameter]
    public ModalDialogType DialogType { get; set; }

    [Parameter]
    public string Title { get; set; }

    [Parameter]
    public string Text { get; set; }

    private Task ModalCancel()
    {
        return OnClose.InvokeAsync(false);
    }
    private Task ModalOk()
    {
        return OnClose.InvokeAsync(true);
    }

}

Definimos um parâmetro do tipo EventCallback novamente e também usamos o argumento de tipo genérico e definimos a propriedade OnClose como EventCallback de boolean.

Implementamos um método privado ModalCancel onde usamos o retorno de chamada OnClose e chamamos seu método InvokeAsync e fornecemos false como seu argumento.

A seguir, implementamos um método ModalOk usando o retorno de chamada OnClose e chamamos seu método InvokeAsync. No entanto, desta vez, fornecemos true como argumento.

Para o botão Fechar adicionamos o atributo onclick e usamos o método ModalCancel e fazemos o mesmo para o botão OK, exceto que vinculamos o método ModalOk.

Como queremos construir um componente que possa ser usado em diferentes lugares do aplicativo não podemos implementar código específico de negócios neste componente.

Para tornar mais flexível o uso das janelas de diálogos implementamos também o tipo de diálogo que desejamos exibir criando a  enumeração ModelDialogType.cs na pasta Components:

 public enum ModalDialogType
 {
        Ok,
        OkCancel,
        DeleteCancel
 }

Definimos os parâmetros para o Titulo o Texto e o botão de Cancelar e passamos o tipo de diálogo que desejamos exibir.

Agora teremos que ajustar o código dos componentes Receitas.razor e Despesas.razor definindo os seguintes ajustes:

1- No botão Excluir vamos a chamada do método OpenDeleteDialog()

<td><button type="button" class="btn btn-danger btn-sm" @onclick="() => OpenDeleteDialog(despesa)">Excluir</button></td>
<td><button type="button" class="btn btn-danger btn-sm" @onclick="() => OpenDeleteDialog(receita)">Excluir</button></td>

2- Incluir o código que vai usar o componente ModelDialog para exibir o tipo de janela de diálogo:

@if (DeleteDialogOpen)
{
     <ModelDialog Title="Confirma ?" Text="Deseja excluir este item ?"
       OnClose="@OnDeleteDialogClose"
       DialogType="ModalDialogType.DeleteCancel"/>
}

3- Implementar o código dos métodos OpenDeleteDialog e OnDeleteDialogClose  para receita e despesa:

 private async Task OnDeleteDialogClose(bool accepted)
 {
        if (accepted)
        {
            await Http.DeleteAsync($"api/Receitas/{_receitaDeleta.Id}");
            await CarregaDados();
            _receitaDeleta = null;
        }
        DeleteDialogOpen = false;
    }
  private void OpenDeleteDialog(Receita receita)
  {
        _receitaDeleta = receita;
        DeleteDialogOpen = true;
  }
  private async Task OnDeleteDialogClose(bool accepted)
    {
        if (accepted)
        {
            await Http.DeleteAsync($"api/Despesas/{_despesaDeleta.Id}");
            await CarregaDados();
            _despesaDeleta = null;
        }
        DeleteDialogOpen = false;
    }
    private void OpenDeleteDialog(Despesa despesa)
    {
        _despesaDeleta = despesa;
        DeleteDialogOpen = true;
    }
}

Com isso já podemos testar a exclusão de receitas usando as janela de diálogos modais para confirmar a exclusão:

Na próxima parte do artigo vamos criar o  dashboard para exibir os gráficos de barras e de pizza.

"Brame o mar e a sua plenitude; o mundo, e os que nele habitam.
Os rios batam as palmas; regozijem-se também as montanhas,
Perante a face do Senhor, porque vem a julgar a terra; com justiça julgará o mundo, e o povo com eqüidade."
Salmos 98:7-9

Referências:


José Carlos Macoratti