Pular para conteúdo

Frontend: Componentes Globais

Visão Geral

Componentes reutilizáveis em Ciba.Web/Components/. São utilizados em 2+ contextos diferentes no portal.

Padrão de organização: - Componente: {Nome}.razor (markup) - Code-behind: {Nome}.razor.cs (lógica) - Estilos isolados: {Nome}.razor.css (quando necessário)


StateView\<TItem>

Gerencia estados de carregamento, erro, vazio e conteúdo.

Arquivo: Components/StateView.razor

Parâmetros

Parâmetro Tipo Descrição
Loading bool Exibe indicador de carregamento
Result Result<TItem>? Resultado da operação (sucesso ou erro)
ChildContent RenderFragment<TItem> Conteúdo quando sucesso
EmptyContent RenderFragment? Conteúdo quando lista vazia
OnRetry EventCallback Callback para tentar novamente

Estados

  1. LoadingMudProgressLinear indeterminado
  2. ErrorMudAlert com mensagem + botão retry
  3. EmptyEmptyContent ou mensagem padrão
  4. SuccessChildContent com dados

Uso

<StateView TItem="List<AgentListResponse>"
           Result="_result"
           Loading="_loading"
           OnRetry="LoadData">
    <EmptyContent>
        <EmptyState Icon="@Icons.Material.Outlined.SmartToy"
                    Message="Nenhum agente cadastrado"
                    ActionText="Criar agente"
                    OnAction="Create" />
    </EmptyContent>
    <ChildContent Context="agents">
        <MudTable Items="agents">
            @* colunas da tabela *@
        </MudTable>
    </ChildContent>
</StateView>

Code-behind

public partial class StateView<TItem> where TItem : class
{
    private bool IsEmpty
    {
        get
        {
            if (Result?.Value is null) return true;
            if (Result.Value is ICollection collection) return collection.Count == 0;
            return false;
        }
    }
}

EmptyState

Visual para listas vazias com ação opcional.

Arquivo: Components/EmptyState.razor

Parâmetros

Parâmetro Tipo Descrição
Icon string Ícone MudBlazor
Message string Mensagem para usuário
ActionText string? Texto do botão de ação
OnAction EventCallback Callback do botão

Uso

<EmptyState Icon="@Icons.Material.Outlined.Inbox"
            Message="Nenhuma conversa encontrada"
            ActionText="Iniciar conversa"
            OnAction="OpenNewConversation" />

StatusChip

Chip colorido para status ativo/inativo.

Arquivo: Components/StatusChip.razor

Parâmetros

Parâmetro Tipo Descrição
IsActive bool Se está ativo

Uso

<StatusChip IsActive="@agent.IsActive" />

Renderização

  • Ativo: Chip verde com "Ativo"
  • Inativo: Chip cinza com "Inativo"

DetailPageHeader

Cabeçalho padrão para páginas de detalhe.

Arquivo: Components/DetailPageHeader.razor

Parâmetros

Parâmetro Tipo Descrição
Title string Título da página
IsActive bool Status para StatusChip
ShowStatus bool Exibir StatusChip
EditHref string? Link para edição (navegação)
OnEdit EventCallback Callback para edição (dialog)
OnDelete EventCallback Callback de exclusão

Uso (com navegação)

<DetailPageHeader Title="@_agent.Name"
                  IsActive="_agent.IsActive"
                  ShowStatus="true"
                  EditHref="@($"/agents/{Id}/edit")"
                  OnDelete="ConfirmDelete" />

Uso (com dialog)

<DetailPageHeader Title="@_agent.Name"
                  IsActive="_agent.IsActive"
                  ShowStatus="true"
                  OnEdit="OpenEditDialog"
                  OnDelete="ConfirmDelete" />

Comportamento

  • Se OnEdit tem delegate → botão abre dialog
  • Se EditHref definido → botão navega para página
  • OnEdit tem prioridade sobre EditHref

Renderização

[Título] [StatusChip]                    [Editar] [Excluir]

TableActionButtons

Botões de ação para tabelas (editar/excluir).

Arquivo: Components/TableActionButtons.razor

Parâmetros

Parâmetro Tipo Descrição
OnEdit EventCallback Callback de edição
OnDelete EventCallback Callback de exclusão

Uso

<TemplateColumn>
    <CellTemplate>
        <TableActionButtons OnEdit="@(() => Edit(context.Item))"
                            OnDelete="@(() => Delete(context.Item))" />
    </CellTemplate>
</TemplateColumn>

StatisticCard

Card para exibir métricas.

Arquivo: Components/StatisticCard.razor

Parâmetros

Parâmetro Tipo Descrição
Value object Valor da métrica
Label string Descrição
Color Color Cor MudBlazor

Uso

<MudGrid>
    <MudItem xs="6" md="3">
        <StatisticCard Value="@_totalConversations" Label="Total de Conversas" Color="Color.Primary" />
    </MudItem>
    <MudItem xs="6" md="3">
        <StatisticCard Value="@_todayConversations" Label="Conversas Hoje" Color="Color.Secondary" />
    </MudItem>
</MudGrid>

FluentForm\<TModel>

Wrapper de formulário com validação FluentValidation integrada.

Arquivo: Components/FluentForm.razor

Parâmetros

Parâmetro Tipo Descrição
Model TModel Modelo do formulário
ChildContent RenderFragment Conteúdo do form
OnValidSubmit EventCallback Submit quando válido
OnInvalidSubmit EventCallback Submit quando inválido

Propriedades

Propriedade Tipo Descrição
IsValid bool Se o form está válido
IsSubmitting bool Se está submetendo

Funcionamento

  1. FluentForm injeta IValidator<TModel> do DI
  2. Cada campo é validado por propriedade
  3. FormButtonGroup detecta o form via CascadingParameter
  4. No submit, valida todo o model antes de chamar OnValidSubmit

Uso

<FluentForm TModel="CreateAgentRequest" Model="_request" OnValidSubmit="Save">
    <MudTextField @bind-Value="_request.Name" Label="Nome" />
    <MudTextField @bind-Value="_request.SystemPrompt" Label="Prompt" Lines="5" />

    <FormButtonGroup CancelHref="/agents" SaveText="Criar" />
</FluentForm>
@code {
    private CreateAgentRequest _request = new();

    private async Task Save()
    {
        // Só é chamado se o form é válido
        var result = await ApiClient.CreateAgentAsync(_request);
        // ...
    }
}

Validação por Campo

O form valida cada propriedade individualmente usando FluentValidation:

private async Task<IEnumerable<string>> ValidatePropertyAsync(object _, string propertyName)
{
    var context = ValidationContext<TModel>.CreateWithOptions(
        Model,
        options => options.IncludeProperties(propertyName));

    var result = await Validator.ValidateAsync(context);

    return result.Errors
        .Where(e => e.PropertyName == propertyName)
        .Select(e => e.ErrorMessage);
}

MudModalForm\<TModel>

Dialog de formulário reutilizável com FluentForm integrado. Padroniza criação/edição de entidades em dialogs.

Arquivo: Components/MudModalForm.razor

Parâmetros

Parâmetro Tipo Descrição
Model TModel Modelo do formulário
Title string Título do dialog
OnSubmit EventCallback Callback quando form é válido e submetido
ChildContent RenderFragment Campos do formulário
SaveText string Texto do botão (default: "Salvar")
SaveColor Color Cor do botão (default: Color.Primary)

Estrutura

O componente encapsula: - FluentForm com validação automática - MudDialog com TitleContent, DialogContent, DialogActions - Botão X no título para fechar - Botões Cancelar/Salvar com loading automático

Comportamento

  • Botão X e Cancelar são desabilitados durante submit (IsSubmitting)
  • Botão Salvar exibe spinner durante submit
  • Validação via FluentValidation (busca IValidator<TModel> no DI)
  • Fecha dialog automaticamente ao cancelar

Uso (Dialog de criação)

@* AgentCreateDialog.razor *@
@using Ciba.Shared.Features.Agents.Create
@using Ciba.Web.Components

<MudModalForm TModel="CreateAgentRequest"
              Model="_model"
              Title="Novo Agente"
              OnSubmit="Save">
    <MudTextField @bind-Value="_model.Name"
                  For="@(() => _model.Name)"
                  Label="Nome"
                  Variant="Variant.Outlined" />

    <MudTextField @bind-Value="_model.Description"
                  For="@(() => _model.Description)"
                  Label="Descricao"
                  Variant="Variant.Outlined"
                  Lines="2" />

    <MudSwitch @bind-Value="_model.IsActive"
               Label="Ativo"
               Color="Color.Primary" />
</MudModalForm>
// AgentCreateDialog.razor.cs
public partial class AgentCreateDialog
{
    [Inject] private ApiClient Api { get; set; } = null!;
    [Inject] private ISnackbar Snackbar { get; set; } = null!;

    [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!;

    private readonly CreateAgentRequest _model = new();

    private async Task Save()
    {
        var result = await Api.CreateAgentAsync(_model);
        if (Snackbar.ShowIfError(result)) return;

        Snackbar.Add("Agente criado com sucesso!", Severity.Success);
        MudDialog.Close(DialogResult.Ok(result.Value!.Id));
    }
}

Uso (Dialog de edição)

@* AgentEditDialog.razor *@
<MudModalForm TModel="UpdateAgentRequest"
              Model="_model"
              Title="Editar Agente"
              OnSubmit="Save">
    @* campos *@
</MudModalForm>
// AgentEditDialog.razor.cs
public partial class AgentEditDialog
{
    [Parameter, EditorRequired] public AgentDetailResponse Agent { get; set; } = null!;

    private readonly UpdateAgentRequest _model = new();

    protected override void OnInitialized()
    {
        _model.Id = Agent.Id;
        _model.Name = Agent.Name;
        _model.Description = Agent.Description;
        _model.IsActive = Agent.IsActive;
    }

    private async Task Save()
    {
        var result = await Api.UpdateAgentAsync(Agent.Id, _model);
        if (Snackbar.ShowIfError(result)) return;

        Snackbar.Add("Agente atualizado!", Severity.Success);
        MudDialog.Close(DialogResult.Ok(true));
    }
}

Abrindo o Dialog

// Na página/componente que abre o dialog
private async Task OpenCreateDialog()
{
    var options = new DialogOptions
    {
        MaxWidth = MaxWidth.Small,
        FullWidth = true,
        BackdropClick = false,
        CloseOnEscapeKey = false
    };

    var dialog = await DialogService.ShowAsync<AgentCreateDialog>(null, options);
    var result = await dialog.Result;

    if (!result!.Canceled)
    {
        await LoadData();
        // Opcional: navegar para detalhe
        // Navigation.NavigateTo($"/agents/{result.Data}");
    }
}

private async Task OpenEditDialog(AgentDetailResponse agent)
{
    var options = new DialogOptions
    {
        MaxWidth = MaxWidth.Small,
        FullWidth = true,
        BackdropClick = false,
        CloseOnEscapeKey = false
    };

    var parameters = new DialogParameters<AgentEditDialog>
    {
        { x => x.Agent, agent }
    };

    var dialog = await DialogService.ShowAsync<AgentEditDialog>(null, parameters, options);
    var result = await dialog.Result;

    if (!result!.Canceled)
    {
        await LoadData();
    }
}

Vantagens sobre FluentForm em página

Aspecto Página MudModalForm
Navegação Navega para outra rota Permanece na mesma página
Feedback Snackbar após navegação Snackbar imediato + reload de dados
UX Mais passos (ir, editar, voltar) Menos cliques
Contexto Perde contexto da lista Mantém contexto da lista

FormButtonGroup

Botões padrão de formulário (Cancelar + Salvar).

Arquivo: Components/FormButtonGroup.razor

Parâmetros

Parâmetro Tipo Descrição
CancelHref string? Link de cancelamento
OnCancel EventCallback Callback de cancelamento
OnSave EventCallback Callback de salvar (fora de FluentForm)
SaveText string Texto do botão (default: "Salvar")
Saving bool Loading manual (fora de FluentForm)
IsValid bool Habilitar botão (fora de FluentForm)
FullWidth bool Botão largura total
Icon string? Ícone no botão
LoadingText string? Texto durante loading
HideCancel bool Ocultar botão cancelar

Comportamento

  • Dentro de FluentForm: Detecta form via CascadingParameter e chama Form.SubmitAsync()
  • Fora de FluentForm: Usa OnSave diretamente e controle manual via Saving/IsValid

Uso (dentro de FluentForm)

<FluentForm TModel="CreateAgentRequest" Model="_request" OnValidSubmit="Save">
    @* campos *@
    <FormButtonGroup CancelHref="/agents" SaveText="Criar Agente" />
</FluentForm>

Uso (fora de FluentForm)

<FormButtonGroup CancelHref="/agents"
                 SaveText="Salvar"
                 Saving="_saving"
                 IsValid="_isFormValid"
                 OnSave="Save" />

ConnectionStatusIndicator

Indicador visual de status de conexão WhatsApp.

Arquivo: Components/ConnectionStatusIndicator.razor

Parâmetros

Parâmetro Tipo Descrição
Status string Status da conexão

Estados

Status Visual Tooltip
Connected Círculo verde "Conectado"
Connecting Círculo amarelo pulsante "Conectando..."
Disconnected Círculo cinza "Desconectado"
Failed Círculo vermelho "Falha na conexão"

Uso

<ConnectionStatusIndicator Status="@_instance.Status" />

ContactAvatar

Avatar de contato com fallback para inicial.

Arquivo: Components/ContactAvatar.razor

Parâmetros

Parâmetro Tipo Descrição
ImageUrl string? URL da foto de perfil
Name string? Nome do contato
Phone string? Telefone (fallback para inicial)
Size Size Tamanho MudBlazor

Comportamento

  1. Se ImageUrl válida → exibe imagem
  2. Se imagem falha (onerror) → exibe inicial
  3. Se sem URL → exibe inicial baseada em Name ou Phone

Uso

<ContactAvatar ImageUrl="@contact.ProfilePictureUrl"
               Name="@contact.Name"
               Phone="@contact.Phone"
               Size="Size.Medium" />

MessageStatusIcon

Ícone de status de mensagem (estilo WhatsApp).

Arquivo: Components/MessageStatusIcon.razor

Parâmetros

Parâmetro Tipo Descrição
Status string Status da mensagem

Estados

Status Ícone Cor Descrição
pending/sending Relógio Cinza Enviando
sent Cinza Enviado
delivered ✓✓ Cinza Entregue
read ✓✓ Azul Lido
failed Erro Vermelho Falha

Uso

<div class="message-bubble">
    <span>@message.Content</span>
    <MessageStatusIcon Status="@message.Status" />
</div>

PromptVersionHistoryDialog

Dialog para visualizar histórico de versões de prompts, comparar versões side-by-side e restaurar versões anteriores.

Arquivo: Pages/Agents/Components/PromptVersionHistoryDialog.razor

Parâmetros

Parâmetro Tipo Descrição
AgentId Guid ID do agente
StepId Guid? ID do step (null = prompt do Agent)
CurrentContent string? Conteudo atual do prompt/instrucao (exibido como linha "Atual")

Funcionalidades

  1. Versao atual: Primeira linha da tabela com chip "Atual", sem botao restaurar, disponivel para comparacao
  2. Preview: Expandir conteúdo de uma versão inline
  3. Comparação: Selecionar duas versões para visualização side-by-side com MudGrid 2 colunas
  4. Restaurar: Botão com confirmação via DialogService.ShowMessageBox, aplica o conteúdo da versão selecionada

Comportamento

  • Carrega versões via ApiClient.GetAgentPromptVersionsAsync ou GetStepPromptVersionsAsync conforme StepId
  • Restaurar chama ApiClient.RestorePromptVersionAsync e fecha com DialogResult.Ok(true)
  • Erros exibidos via Snackbar.ShowIfError(result)
  • Somente Admin pode restaurar (endpoint protegido por AuthConstants.Roles.Admin)

Uso

// Abrir para Agent prompt
var parameters = new DialogParameters<PromptVersionHistoryDialog>
{
    { x => x.AgentId, agentId },
    { x => x.CurrentContent, agent.SystemPrompt }
};
await Dialog.ShowFormAsync<PromptVersionHistoryDialog>(
    "Historico de Versoes", parameters, MaxWidth.Large);

// Abrir para Step instructions
var parameters = new DialogParameters<PromptVersionHistoryDialog>
{
    { x => x.AgentId, agentId },
    { x => x.StepId, stepId },
    { x => x.CurrentContent, step.Instructions }
};
await Dialog.ShowFormAsync<PromptVersionHistoryDialog>(
    "Historico de Versoes", parameters, MaxWidth.Large);

Integração

  • AgentPromptTab: Botão History no cabeçalho ao lado do contador de palavras
  • AgentStepsTab: Botão History no menu overflow ("...") de cada step

Componentes de Chat (Components/Chat/)

Componentes visuais de chat compartilhados entre Conversations e Playground.

ChatMode

Arquivo: Components/Chat/ChatMode.cs

Enum que controla o comportamento dos componentes de chat conforme o contexto.

Valor Descrição
Conversation Modo padrão com reações, status de mensagem e avatar
Playground Modo simplificado sem reações, status ou avatar

MessageBubble

Bolha de mensagem estilo WhatsApp com suporte a texto, imagem, áudio, vídeo e documento.

Arquivo: Components/Chat/MessageBubble.razor

Parâmetros

Parâmetro Tipo Descrição
Message MessageItemResponse Dados da mensagem
IsUser bool Se é mensagem do usuário
ContactPhone string Telefone do contato
Mode ChatMode Modo de exibição (Conversation ou Playground)
OnMediaPreview EventCallback<(string, string)> Preview de mídia
DisplayLength int Limite de caracteres antes de "Ler mais"

Comportamento por ChatMode

  • Conversation: Exibe avatar, reações, status de mensagem (sent/delivered/read)
  • Playground: Oculta avatar, reações e status de mensagem

MessageInput

Campo de entrada de mensagem com suporte a texto, anexos e gravação de voz.

Arquivo: Components/Chat/MessageInput.razor

Parâmetros

Parâmetro Tipo Descrição
IsActive bool Se o input está ativo
MessageText string Texto atual
MessageTextChanged EventCallback<string> Callback de mudança de texto
PendingAttachments List<AttachmentPreview> Anexos pendentes
IsRecording bool Se está gravando áudio
RecordingDuration int Duração da gravação em segundos
OnSendMessage EventCallback Enviar mensagem
OnStartRecording EventCallback Iniciar gravação
OnStopRecording EventCallback Parar gravação
OnCancelRecording EventCallback Cancelar gravação
OnFilesSelected EventCallback<InputFileChangeEventArgs> Arquivos selecionados
OnRemoveAttachment EventCallback<AttachmentPreview> Remover anexo

AudioPlayer

Player de áudio estilo WhatsApp para mensagens de voz.

Arquivo: Components/Chat/AudioPlayer.razor

Parâmetros

Parâmetro Tipo Descrição
MessageId Guid ID da mensagem
MediaUrl string? URL do áudio
MediaFileName string? Nome do arquivo

DocumentPreview

Preview de documento com ícone por tipo de arquivo.

Arquivo: Components/Chat/DocumentPreview.razor

Parâmetros

Parâmetro Tipo Descrição
MediaUrl string? URL do documento
MediaFileName string? Nome do arquivo
MediaMimeType string? MIME type
ShowTimestamp bool Exibir timestamp
IsUser bool Se é do usuário
MessageTime string Hora da mensagem
MessageStatus string? Status da mensagem

Componentes de Dashboard (Components/Dashboard/)

Componentes visuais para o dashboard de relatórios. Usam ApexCharts para gráficos e MudBlazor para cards/filtros.

Nota: @using ApexCharts é adicionado por componente (não global em _Imports.razor) para evitar conflitos de namespace com MudBlazor (Size, Color, Mode).

SetupChecklist

Checklist de configuracao inicial exibido no Dashboard para administradores que ainda nao completaram o onboarding.

Arquivo: Components/Dashboard/SetupChecklist.razor + .razor.cs

Parametro Tipo Descricao
Status OnboardingStatusResponse? Status do onboarding com flags de progresso
OnDismiss EventCallback Callback ao clicar "Ocultar checklist"

Criterios exibidos:

# Criterio Campo Acao
1 Criar um agente HasAgents Navega para /agents
2 Criar instancia WhatsApp HasInstances Navega para /instances
3 Conectar WhatsApp HasConnectedInstance Navega para /instances

Comportamento: - Chip indica progresso (ex: "2/3") - Alerta de sucesso quando todos os 3 criterios sao atendidos - Botao "Ocultar checklist" chama OnDismiss (que dispara POST /api/onboarding/dismiss)


WelcomeCard

Card de boas-vindas para usuarios comuns (nao-admin) no Dashboard.

Arquivo: Components/Dashboard/WelcomeCard.razor + .razor.cs

Parametro Tipo Descricao
UserName string Nome do usuario para saudacao
OnDismiss EventCallback Callback ao clicar "Entendi"

Comportamento: - Exibe mensagem de boas-vindas personalizada - 3 links rapidos: Dashboard, Conversas, Perfil - Botao "Entendi" dispensa o card (armazenado em LocalStorage via welcomeCardDismissed)


DashboardDateFilter

Barra de filtros com DateRangePicker, botoes rapidos e seletor de agente.

Arquivo: Components/Dashboard/DashboardDateFilter.razor

Parâmetro Tipo Descrição
Agents List<AgentListResponse>? Lista de agentes para o select
OnFilterChanged EventCallback<(DateTime, DateTime, Guid?)> Callback quando filtro muda

Dispara OnFilterChanged automaticamente no OnInitializedAsync com os valores default (últimos 30 dias).

KpiCards

6 cards com métricas principais do período.

Arquivo: Components/Dashboard/KpiCards.razor

Parâmetro Tipo Descrição
Data KpiOverviewResponse? Dados dos KPIs

ConversationsOverTimeChart

Gráfico de área com 3 séries: Total, Resolvidas por IA, Escaladas.

Arquivo: Components/Dashboard/ConversationsOverTimeChart.razor

Parâmetro Tipo Descrição
Data ConversationsOverTimeResponse? Série temporal

StepFunnelChart

Gráfico de barras horizontal representando o funil de steps do agente.

Arquivo: Components/Dashboard/StepFunnelChart.razor

Parâmetro Tipo Descrição
Data StepFunnelResponse? Dados do funil

PeakHoursHeatmap

Heatmap de horários de pico (dia da semana × hora do dia).

Arquivo: Components/Dashboard/PeakHoursHeatmap.razor

Parâmetro Tipo Descrição
Data PeakHoursResponse? Dados do heatmap (168 pontos)

FollowUpEffectivenessCard

4 cards com métricas de efetividade de follow-ups automáticos.

Arquivo: Components/Dashboard/FollowUpEffectivenessCard.razor

Parâmetro Tipo Descrição
Data FollowUpEffectivenessResponse? Dados de follow-up

ScheduleGrid

Grid de horários permitidos por dia da semana. Componente local reutilizado em Agents (follow-up) e Instances (horários de atendimento da IA).

Arquivo: Pages/Agents/Components/ScheduleGrid.razor

Parâmetros

Parâmetro Tipo Descrição
Value List<ScheduleDay>? Horários configurados
ValueChanged EventCallback<List<ScheduleDay>?> Two-way binding
NoScheduleText string? Texto exibido quando sem horário configurado

Uso

<ScheduleGrid @bind-Value="_request.AllowedHours" />

<ScheduleGrid @bind-Value="_request.AiAvailabilityHours"
              NoScheduleText="Sem restricao — IA atende 24 horas." />

Funcionalidades

  • Botões rápidos: "Horário Comercial" (seg-sex 8h-18h), "Todos os Dias" (8h-22h), "Limpar"
  • Toggle por dia da semana com time pickers para início/fim
  • Emite null quando nenhum dia está habilitado

Critérios para Componentizar

Critério Ação
Usado em 2+ páginas diferentes Components/ (global)
Usado apenas em 1 contexto Pages/{Recurso}/Components/ (local)
Bloco grande (50+ linhas) Componentizar
Lógica própria (estado, eventos) Componentizar
Puramente visual e pequeno Manter inline

Boas Práticas

  1. Separação Razor/Code-behind: Sempre usar partial class
  2. Parâmetros tipados: Usar [Parameter, EditorRequired] quando obrigatório
  3. EventCallback: Preferir EventCallback a Action para binding assíncrono
  4. CascadingParameter: Usar para comunicação form → button
  5. Injeção de dependências: Via [Inject] no code-behind