Claude-skill-registry invoice-ui-components
Componentes de UI para faturas seguindo o padrão de components do Easy Budget.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/invoice-ui-components" ~/.claude/skills/majiayu000-claude-skill-registry-invoice-ui-components && rm -rf "$T"
skills/data/invoice-ui-components/SKILL.mdComponentes de UI para Faturas
Esta skill define os componentes Blade específicos para a gestão de faturas no Easy Budget, seguindo o padrão de components estabelecido no sistema.
Estrutura de Components
resources/views/components/ ├── invoice/ │ ├── invoice-card.blade.php # Card resumido de fatura │ ├── invoice-details.blade.php # Detalhes completos da fatura │ ├── invoice-form.blade.php # Formulário de criação/edição │ ├── invoice-status.blade.php # Badge de status da fatura │ ├── invoice-items.blade.php # Lista de itens da fatura │ ├── invoice-actions.blade.php # Ações disponíveis para fatura │ ├── invoice-filters.blade.php # Filtros específicos para faturas │ ├── invoice-payments.blade.php # Histórico de pagamentos │ └── invoice-totals.blade.php # Totais e valores da fatura └── ...
1. Invoice Card Component
Componente para exibição resumida de faturas em listas e dashboards.
Uso Básico
<x-invoice.invoice-card :invoice="$invoice" :showCustomer="true" :showDueDate="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo da fatura | Obrigatório |
| | Exibir informações do cliente | |
| | Exibir data de vencimento | |
| | Estilo do card (primary, secondary, etc.) | |
Estrutura
@props([ 'invoice', 'showCustomer' => true, 'showDueDate' => true, 'variant' => 'primary' ]) <div class="card border-0 shadow-sm h-100"> <div class="card-body"> <div class="d-flex justify-content-between align-items-start mb-3"> <div class="d-flex align-items-center gap-3"> <div class="avatar-circle bg-{{ $variant }} bg-gradient"> <i class="bi bi-receipt text-white"></i> </div> <div> <h6 class="mb-1 fw-bold">{{ $invoice->code }}</h6> <small class="text-muted">{{ $invoice->created_at->format('d/m/Y') }}</small> </div> </div> <x-invoice.invoice-status :invoice="$invoice" /> </div> @if($showCustomer && $invoice->customer) <div class="mb-3"> <small class="text-muted">Cliente:</small> <div class="fw-semibold">{{ $invoice->customer->display_name }}</div> </div> @endif @if($showDueDate) <div class="mb-3"> <small class="text-muted">Vencimento:</small> <div class="fw-bold {{ $invoice->isOverdue() ? 'text-danger' : '' }}"> {{ $invoice->due_date?->format('d/m/Y') ?? 'Indeterminado' }} @if($invoice->isOverdue()) <span class="badge bg-danger ms-2">Atrasado</span> @endif </div> </div> @endif <div class="d-flex justify-content-between align-items-center"> <div> <small class="text-muted">Valor Total</small> <div class="h6 mb-0 fw-bold text-{{ $variant }}"> R$ {{ number_format($invoice->total, 2, ',', '.') }} </div> </div> <div class="btn-group btn-group-sm" role="group"> <a href="{{ route('provider.invoices.show', $invoice) }}" class="btn btn-outline-primary"> <i class="bi bi-eye"></i> Ver </a> @if($invoice->status->isEditable()) <a href="{{ route('provider.invoices.edit', $invoice) }}" class="btn btn-outline-secondary"> <i class="bi bi-pencil"></i> Editar </a> @endif </div> </div> </div> </div>
2. Invoice Details Component
Componente para exibição detalhada de informações da fatura.
Uso Básico
<x-invoice.invoice-details :invoice="$invoice" :showItems="true" :showPayments="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo da fatura | Obrigatório |
| | Exibir itens da fatura | |
| | Exibir histórico de pagamentos | |
| | Permitir colapsar seções | |
Estrutura
@props([ 'invoice', 'showItems' => true, 'showPayments' => true, 'collapsible' => false ]) <div class="invoice-details"> <!-- Informações Básicas --> <div class="row g-3 mb-3"> <div class="col-md-3"> <div class="info-item"> <label class="text-muted small">Código</label> <div class="fw-bold">{{ $invoice->code }}</div> </div> </div> <div class="col-md-3"> <div class="info-item"> <label class="text-muted small">Data de Emissão</label> <div class="fw-bold">{{ $invoice->created_at->format('d/m/Y H:i') }}</div> </div> </div> <div class="col-md-3"> <div class="info-item"> <label class="text-muted small">Vencimento</label> <div class="fw-bold {{ $invoice->isOverdue() ? 'text-danger' : '' }}"> {{ $invoice->due_date?->format('d/m/Y') ?? 'Indeterminado' }} @if($invoice->isOverdue()) <span class="badge bg-danger ms-2">Atrasado</span> @endif </div> </div> </div> <div class="col-md-3"> <div class="info-item"> <label class="text-muted small">Status</label> <x-invoice.invoice-status :invoice="$invoice" /> </div> </div> </div> <!-- Cliente --> @if($invoice->customer) <div class="row mb-3"> <div class="col-12"> <div class="card border-0"> <div class="card-header bg-transparent border-0"> <h6 class="mb-0">Informações do Cliente</h6> </div> <div class="card-body"> <div class="row"> <div class="col-md-6"> <div class="mb-2"> <label class="text-muted small">Nome</label> <div class="fw-bold">{{ $invoice->customer->display_name }}</div> </div> @if($invoice->customer->contact) <div class="mb-2"> <label class="text-muted small">E-mail</label> <div class="fw-bold">{{ $invoice->customer->contact->email }}</div> </div> <div class="mb-2"> <label class="text-muted small">Telefone</label> <div class="fw-bold">{{ $invoice->customer->contact->phone }}</div> </div> @endif </div> <div class="col-md-6"> @if($invoice->customer->address) <div class="mb-2"> <label class="text-muted small">Endereço</label> <div class="fw-bold"> {{ $invoice->customer->address->address }}, {{ $invoice->customer->address->address_number }} </div> <div class="fw-bold"> {{ $invoice->customer->address->neighborhood }} - {{ $invoice->customer->address->city }}/{{ $invoice->customer->address->state }} </div> </div> @endif </div> </div> </div> </div> </div> </div> @endif <!-- Itens da Fatura --> @if($showItems && $invoice->invoiceItems->isNotEmpty()) <div class="row mb-3"> <div class="col-12"> <div class="card border-0"> <div class="card-header bg-transparent border-0"> <h6 class="mb-0">Itens da Fatura</h6> </div> <div class="card-body p-0"> <div class="table-responsive"> <table class="table table-hover mb-0"> <thead> <tr> <th>Descrição</th> <th>Quantidade</th> <th>Valor Unitário</th> <th>Valor Total</th> </tr> </thead> <tbody> @foreach($invoice->invoiceItems as $item) <tr> <td>{{ $item->description }}</td> <td>{{ $item->quantity }}</td> <td>R$ {{ number_format($item->unit_price, 2, ',', '.') }}</td> <td class="fw-bold"> R$ {{ number_format($item->total, 2, ',', '.') }} </td> </tr> @endforeach </tbody> </table> </div> </div> </div> </div> </div> @endif <!-- Histórico de Pagamentos --> @if($showPayments && $invoice->payments->isNotEmpty()) <div class="row mb-3"> <div class="col-12"> <div class="card border-0"> <div class="card-header bg-transparent border-0"> <h6 class="mb-0">Histórico de Pagamentos</h6> </div> <div class="card-body p-0"> <div class="table-responsive"> <table class="table table-hover mb-0"> <thead> <tr> <th>Data</th> <th>Método</th> <th>Valor</th> <th>Status</th> </tr> </thead> <tbody> @foreach($invoice->payments as $payment) <tr> <td>{{ $payment->transaction_date?->format('d/m/Y H:i') }}</td> <td>{{ $payment->payment_method }}</td> <td class="fw-bold"> R$ {{ number_format($payment->transaction_amount, 2, ',', '.') }} </td> <td> <span class="badge bg-success">{{ $payment->status }}</span> </td> </tr> @endforeach </tbody> </table> </div> </div> </div> </div> </div> @endif <!-- Totais --> <x-invoice.invoice-totals :invoice="$invoice" /> </div>
3. Invoice Status Component
Componente para exibição do status da fatura com cores e ícones apropriados.
Uso Básico
<x-invoice.invoice-status :invoice="$invoice" :showIcon="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo da fatura | Obrigatório |
| | Exibir ícone ao lado do status | |
| | Tamanho do badge (sm, md, lg) | |
Estrutura
@props([ 'invoice', 'showIcon' => true, 'size' => 'md' ]) @php $status = $invoice->status; $metadata = $status->getMetadata(); $color = $metadata['color'] ?? '#6c757d'; $icon = $metadata['icon'] ?? 'circle'; $description = $metadata['description'] ?? $status->value; $sizeClass = match($size) { 'sm' => 'badge-sm', 'lg' => 'badge-lg', default => 'badge-md' }; // Lógica para status de atraso $isOverdue = $invoice->isOverdue(); if ($isOverdue && $invoice->status->isPending()) { $color = '#dc3545'; // Vermelho para faturas atrasadas $icon = 'exclamation-triangle'; $description = 'Atrasada'; } @endphp <span class="badge modern-badge {{ $sizeClass }}" style="background-color: {{ $color }}20; color: {{ $color }}; border: 1px solid {{ $color }}40;"> @if($showIcon) <i class="bi {{ $icon }} me-1"></i> @endif {{ $description }} @if($isOverdue) <span class="badge bg-danger ms-1">+{{ $invoice->daysOverdue() }} dias</span> @endif </span>
4. Invoice Items Component
Componente para exibição da lista de itens de uma fatura.
Uso Básico
<x-invoice.invoice-items :invoice="$invoice" :editable="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo da fatura | Obrigatório |
| | Permitir edição dos itens | |
| | Exibir total geral | |
Estrutura
@props([ 'invoice', 'editable' => false, 'showTotal' => true ]) <div class="invoice-items"> <div class="table-responsive"> <table class="table table-hover"> <thead> <tr> <th>Descrição</th> <th class="text-center">Quantidade</th> <th class="text-end">Valor Unitário</th> <th class="text-end">Valor Total</th> @if($editable) <th class="text-center">Ações</th> @endif </tr> </thead> <tbody> @foreach($invoice->invoiceItems as $item) <tr> <td> @if($editable) <input type="text" name="items[{{ $item->id }}][description]" class="form-control form-control-sm" value="{{ $item->description }}" placeholder="Descrição do item"> @else <div class="fw-bold">{{ $item->description }}</div> @endif </td> <td class="text-center"> @if($editable) <input type="number" name="items[{{ $item->id }}][quantity]" class="form-control form-control-sm text-center" value="{{ $item->quantity }}" min="1" style="width: 80px;"> @else {{ $item->quantity }} @endif </td> <td class="text-end"> @if($editable) <input type="text" name="items[{{ $item->id }}][unit_price]" class="form-control form-control-sm text-end" value="{{ number_format($item->unit_price, 2, ',', '.') }}" style="width: 120px;"> @else R$ {{ number_format($item->unit_price, 2, ',', '.') }} @endif </td> <td class="text-end fw-bold"> R$ {{ number_format($item->total, 2, ',', '.') }} </td> @if($editable) <td class="text-center"> <button class="btn btn-outline-danger btn-sm" title="Remover item"> <i class="bi bi-trash"></i> </button> </td> @endif </tr> @endforeach </tbody> @if($showTotal) <tfoot> <tr class="table-active"> <td colspan="{{ $editable ? 3 : 2 }}" class="text-end fw-bold">Subtotal:</td> <td class="text-end fw-bold"> R$ {{ number_format($invoice->invoiceItems->sum('total'), 2, ',', '.') }} </td> @if($editable) <td></td> @endif </tr> @if($invoice->discount > 0) <tr> <td colspan="{{ $editable ? 3 : 2 }}" class="text-end text-muted">Desconto:</td> <td class="text-end text-danger fw-bold"> - R$ {{ number_format($invoice->discount, 2, ',', '.') }} </td> @if($editable) <td></td> @endif </tr> @endif <tr class="table-active"> <td colspan="{{ $editable ? 3 : 2 }}" class="text-end fw-bold">Total da Fatura:</td> <td class="text-end fw-bold h5"> R$ {{ number_format($invoice->total, 2, ',', '.') }} </td> @if($editable) <td></td> @endif </tr> </tfoot> @endif </table> </div> </div>
5. Invoice Form Component
Componente para formulário de criação e edição de faturas.
Uso Básico
<x-invoice.invoice-form :invoice="$invoice ?? null" :customers="$customers" :services="$services" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| `Invoice | null` | Modelo da fatura (para edição) |
| | Clientes disponíveis | Obrigatório |
| | Serviços disponíveis | Obrigatório |
| `Budget | null` | Orçamento vinculado |
Estrutura
@props([ 'invoice' => null, 'customers' => [], 'services' => [], 'budget' => null ]) <div class="invoice-form"> <form action="{{ isset($invoice) ? route('provider.invoices.update', $invoice) : route('provider.invoices.store') }}" method="POST" id="invoiceForm"> @csrf @if(isset($invoice)) @method('PUT') @endif <!-- Informações Básicas --> <div class="row g-3 mb-4"> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Cliente *</label> <select name="customer_id" class="form-select @error('customer_id') is-invalid @enderror" required> <option value="">Selecione um cliente</option> @foreach($customers as $customer) <option value="{{ $customer->id }}" {{ (old('customer_id', $invoice->customer_id ?? '') == $customer->id) ? 'selected' : '' }}> {{ $customer->display_name }} </option> @endforeach </select> @error('customer_id') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Data de Vencimento *</label> <input type="date" name="due_date" class="form-control @error('due_date') is-invalid @enderror" value="{{ old('due_date', $invoice->due_date?->format('Y-m-d') ?? '') }}" required> @error('due_date') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Método de Pagamento</label> <select name="payment_method" class="form-select @error('payment_method') is-invalid @enderror"> <option value="">Selecione um método</option> <option value="dinheiro" {{ (old('payment_method', $invoice->payment_method ?? '') == 'dinheiro') ? 'selected' : '' }}>Dinheiro</option> <option value="cartao" {{ (old('payment_method', $invoice->payment_method ?? '') == 'cartao') ? 'selected' : '' }}>Cartão</option> <option value="boleto" {{ (old('payment_method', $invoice->payment_method ?? '') == 'boleto') ? 'selected' : '' }}>Boleto</option> <option value="pix" {{ (old('payment_method', $invoice->payment_method ?? '') == 'pix') ? 'selected' : '' }}>PIX</option> <option value="transferencia" {{ (old('payment_method', $invoice->payment_method ?? '') == 'transferencia') ? 'selected' : '' }}>Transferência</option> </select> @error('payment_method') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Observações</label> <textarea name="notes" class="form-control @error('notes') is-invalid @enderror" rows="3" placeholder="Observações sobre a fatura...">{{ old('notes', $invoice->notes ?? '') }}</textarea> @error('notes') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> </div> <!-- Itens da Fatura --> <div class="card border-0 shadow-sm mb-4"> <div class="card-header bg-transparent border-0"> <h6 class="mb-0">Itens da Fatura</h6> </div> <div class="card-body"> <div id="invoiceItems"> @if(isset($invoice) && $invoice->invoiceItems->isNotEmpty()) @foreach($invoice->invoiceItems as $item) <div class="row g-3 item-row mb-3" data-item-id="{{ $item->id }}"> <div class="col-md-6"> <input type="text" name="items[{{ $item->id }}][description]" class="form-control description-input" placeholder="Descrição do item" value="{{ $item->description }}" required> </div> <div class="col-md-2"> <input type="number" name="items[{{ $item->id }}][quantity]" class="form-control quantity-input" placeholder="Qtd" value="{{ $item->quantity }}" min="1" required> </div> <div class="col-md-2"> <input type="text" name="items[{{ $item->id }}][unit_price]" class="form-control price-input" placeholder="Valor Unitário" value="{{ number_format($item->unit_price, 2, ',', '.') }}" required> </div> <div class="col-md-2"> <input type="text" class="form-control item-total" placeholder="Total" value="R$ {{ number_format($item->total, 2, ',', '.') }}" readonly> </div> </div> @endforeach @else <div class="row g-3 item-row mb-3"> <div class="col-md-6"> <input type="text" name="items[0][description]" class="form-control description-input" placeholder="Descrição do item" required> </div> <div class="col-md-2"> <input type="number" name="items[0][quantity]" class="form-control quantity-input" placeholder="Qtd" value="1" min="1" required> </div> <div class="col-md-2"> <input type="text" name="items[0][unit_price]" class="form-control price-input" placeholder="Valor Unitário" required> </div> <div class="col-md-2"> <input type="text" class="form-control item-total" placeholder="Total" readonly> </div> </div> @endif </div> <div class="row"> <div class="col-12"> <button type="button" class="btn btn-outline-primary btn-sm" id="addInvoiceItem"> <i class="bi bi-plus-circle me-2"></i>Adicionar Item </button> </div> </div> </div> </div> <!-- Totais --> <div class="row mb-4"> <div class="col-md-8"></div> <div class="col-md-4"> <div class="card border-0 bg-light"> <div class="card-body"> <div class="d-flex justify-content-between mb-2"> <span class="text-muted">Subtotal:</span> <span id="subtotalDisplay">R$ 0,00</span> </div> <div class="d-flex justify-content-between mb-2"> <span class="text-muted">Desconto:</span> <input type="text" name="discount" class="form-control form-control-sm text-end" id="discountInput" value="{{ old('discount', $invoice->discount ?? '0') }}" style="width: 120px; display: inline-block;"> </div> <div class="d-flex justify-content-between fw-bold"> <span>Total:</span> <span id="totalDisplay" class="h5">R$ 0,00</span> </div> </div> </div> </div> </div> <!-- Ações --> <div class="d-flex justify-content-between"> <a href="{{ route('provider.invoices.index') }}" class="btn btn-outline-secondary"> <i class="bi bi-arrow-left me-2"></i>Cancelar </a> <button type="submit" class="btn btn-primary"> <i class="bi bi-check-circle me-2"></i>{{ isset($invoice) ? 'Atualizar' : 'Criar' }} Fatura </button> </div> </form> </div> @push('scripts') <script> document.addEventListener('DOMContentLoaded', function() { // Lógica para cálculo de totais function calculateTotals() { let subtotal = 0; document.querySelectorAll('.item-row').forEach(row => { const quantity = parseFloat(row.querySelector('.quantity-input').value) || 0; const unitPrice = parseFloat(row.querySelector('.price-input').value.replace(',', '.')) || 0; const total = quantity * unitPrice; row.querySelector('.item-total').value = 'R$ ' + total.toLocaleString('pt-BR', { minimumFractionDigits: 2 }); subtotal += total; }); const discount = parseFloat(document.getElementById('discountInput').value.replace(',', '.')) || 0; const total = subtotal - discount; document.getElementById('subtotalDisplay').textContent = 'R$ ' + subtotal.toLocaleString('pt-BR', { minimumFractionDigits: 2 }); document.getElementById('totalDisplay').textContent = 'R$ ' + total.toLocaleString('pt-BR', { minimumFractionDigits: 2 }); } // Eventos document.getElementById('addInvoiceItem').addEventListener('click', function() { const itemsContainer = document.getElementById('invoiceItems'); const itemCount = itemsContainer.children.length; const newRow = document.createElement('div'); newRow.className = 'row g-3 item-row mb-3'; newRow.innerHTML = ` <div class="col-md-6"> <input type="text" name="items[${itemCount}][description]" class="form-control description-input" placeholder="Descrição do item" required> </div> <div class="col-md-2"> <input type="number" name="items[${itemCount}][quantity]" class="form-control quantity-input" placeholder="Qtd" value="1" min="1" required> </div> <div class="col-md-2"> <input type="text" name="items[${itemCount}][unit_price]" class="form-control price-input" placeholder="Valor Unitário" required> </div> <div class="col-md-2"> <input type="text" class="form-control item-total" placeholder="Total" readonly> </div> `; itemsContainer.appendChild(newRow); calculateTotals(); }); document.getElementById('invoiceItems').addEventListener('input', calculateTotals); document.getElementById('discountInput').addEventListener('input', calculateTotals); // Inicializar cálculos calculateTotals(); }); </script> @endpush
6. Invoice Actions Component
Componente para exibição de ações disponíveis para uma fatura.
Uso Básico
<x-invoice.invoice-actions :invoice="$invoice" :showPayment="true" :showSend="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo da fatura | Obrigatório |
| | Exibir botão de pagamento | |
| | Exibir botão de envio | |
| | Exibir botão de impressão | |
Estrutura
@props([ 'invoice', 'showPayment' => true, 'showSend' => true, 'showPrint' => true ]) <div class="invoice-actions btn-group" role="group"> <!-- Visualizar --> <a href="{{ route('provider.invoices.show', $invoice) }}" class="btn btn-outline-primary" title="Visualizar Fatura"> <i class="bi bi-eye"></i> Visualizar </a> <!-- Editar --> @if($invoice->status->isEditable()) <a href="{{ route('provider.invoices.edit', $invoice) }}" class="btn btn-outline-secondary" title="Editar Fatura"> <i class="bi bi-pencil"></i> Editar </a> @endif <!-- Receber Pagamento --> @if($showPayment && $invoice->status->isPending()) <button type="button" class="btn btn-outline-success" data-bs-toggle="modal" data-bs-target="#paymentModal-{{ $invoice->id }}" title="Receber Pagamento"> <i class="bi bi-cash"></i> Receber </button> @endif <!-- Enviar por E-mail --> @if($showSend) <button type="button" class="btn btn-outline-info" data-bs-toggle="modal" data-bs-target="#sendModal-{{ $invoice->id }}" title="Enviar por E-mail"> <i class="bi bi-envelope"></i> Enviar </button> @endif <!-- Imprimir --> @if($showPrint) <a href="{{ route('provider.invoices.pdf', $invoice) }}" class="btn btn-outline-warning" target="_blank" title="Imprimir Fatura"> <i class="bi bi-printer"></i> Imprimir </a> @endif <!-- Excluir --> @if($invoice->status->isDeletable()) <button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteModal-{{ $invoice->id }}" title="Excluir Fatura"> <i class="bi bi-trash"></i> Excluir </button> @endif </div>
7. Invoice Filters Component
Componente para filtros específicos de listagem de faturas.
Uso Básico
<x-invoice.invoice-filters :filters="$filters" :showCustomer="true" :showStatus="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Filtros atuais | |
| | Exibir filtro por cliente | |
| | Exibir filtro por status | |
| | Exibir filtro por período | |
Estrutura
@props([ 'filters' => [], 'showCustomer' => true, 'showStatus' => true, 'showDateRange' => true ]) <div class="invoice-filters"> <form action="{{ request()->url() }}" method="GET" class="row g-3"> @if($showCustomer) <div class="col-md-3"> <label class="form-label small fw-bold text-muted text-uppercase">Cliente</label> <select name="customer_id" class="form-select form-select-sm"> <option value="">Todos os clientes</option> @foreach($customers as $customer) <option value="{{ $customer->id }}" {{ ($filters['customer_id'] ?? '') == $customer->id ? 'selected' : '' }}> {{ $customer->display_name }} </option> @endforeach </select> </div> @endif @if($showStatus) <div class="col-md-3"> <label class="form-label small fw-bold text-muted text-uppercase">Status</label> <select name="status" class="form-select form-select-sm"> <option value="">Todos os status</option> @foreach(\App\Enums\InvoiceStatus::cases() as $status) @php $metadata = $status->getMetadata(); @endphp <option value="{{ $status->value }}" {{ ($filters['status'] ?? '') == $status->value ? 'selected' : '' }}> {{ $metadata['description'] }} </option> @endforeach </select> </div> @endif @if($showDateRange) <div class="col-md-3"> <label class="form-label small fw-bold text-muted text-uppercase">Período</label> <div class="input-group input-group-sm"> <input type="date" name="start_date" class="form-control" value="{{ $filters['start_date'] ?? '' }}"> <span class="input-group-text">até</span> <input type="date" name="end_date" class="form-control" value="{{ $filters['end_date'] ?? '' }}"> </div> </div> @endif <div class="col-md-3"> <label class="form-label small fw-bold text-muted text-uppercase">Busca</label> <input type="text" name="search" class="form-control form-control-sm" value="{{ $filters['search'] ?? '' }}" placeholder="Buscar por código..."> </div> <div class="col-12"> <div class="d-flex justify-content-between"> <div class="btn-group" role="group"> <button type="submit" class="btn btn-primary btn-sm"> <i class="bi bi-search me-2"></i>Filtrar </button> <a href="{{ route('provider.invoices.index') }}" class="btn btn-outline-secondary btn-sm"> <i class="bi bi-x-circle me-2"></i>Limpar </a> </div> <div class="btn-group" role="group"> <button type="button" class="btn btn-outline-success btn-sm" data-bs-toggle="modal" data-bs-target="#createModal"> <i class="bi bi-plus-circle me-2"></i>Nova Fatura </button> </div> </div> </div> </form> </div>
8. Invoice Payments Component
Componente para exibição do histórico de pagamentos da fatura.
Uso Básico
<x-invoice.invoice-payments :invoice="$invoice" :showAddPayment="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo da fatura | Obrigatório |
| | Exibir botão para adicionar pagamento | |
| | Exibir saldo devedor | |
Estrutura
@props([ 'invoice', 'showAddPayment' => true, 'showBalance' => true ]) <div class="invoice-payments"> <!-- Saldo Devedor --> @if($showBalance && $invoice->status->isPending()) <div class="row mb-3"> <div class="col-12"> <div class="card border-0 bg-warning bg-gradient text-white"> <div class="card-body"> <div class="row align-items-center"> <div class="col-md-8"> <div class="small">Saldo Devedor</div> <div class="h4 mb-0 fw-bold"> R$ {{ number_format($invoice->balance, 2, ',', '.') }} </div> </div> <div class="col-md-4 text-end"> <span class="badge bg-light text-dark">{{ $invoice->status->getDescription() }}</span> </div> </div> </div> </div> </div> </div> @endif <!-- Histórico de Pagamentos --> <div class="card border-0 shadow-sm"> <div class="card-header bg-transparent border-0"> <h6 class="mb-0">Histórico de Pagamentos</h6> </div> <div class="card-body p-0"> @if($invoice->payments->isNotEmpty()) <div class="table-responsive"> <table class="table table-hover mb-0"> <thead> <tr> <th>Data</th> <th>Método</th> <th>Valor</th> <th>Status</th> <th class="text-end">Ações</th> </tr> </thead> <tbody> @foreach($invoice->payments as $payment) <tr> <td>{{ $payment->transaction_date?->format('d/m/Y H:i') }}</td> <td>{{ $payment->payment_method }}</td> <td class="fw-bold"> R$ {{ number_format($payment->transaction_amount, 2, ',', '.') }} </td> <td> <span class="badge bg-success">{{ $payment->status }}</span> </td> <td class="text-end"> <button class="btn btn-outline-info btn-sm" title="Detalhes"> <i class="bi bi-eye"></i> </button> </td> </tr> @endforeach </tbody> </table> </div> @else <div class="text-center py-4"> <i class="bi bi-receipt text-muted" style="font-size: 3rem;"></i> <p class="text-muted mt-2">Nenhum pagamento registrado</p> </div> @endif </div> </div> <!-- Adicionar Pagamento --> @if($showAddPayment && $invoice->status->isPending()) <div class="row mt-3"> <div class="col-12"> <div class="card border-0"> <div class="card-body"> <div class="d-flex justify-content-between align-items-center"> <div> <h6 class="mb-0">Receber Pagamento</h6> <small class="text-muted">Registre o pagamento da fatura</small> </div> <button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#paymentModal-{{ $invoice->id }}"> <i class="bi bi-cash me-2"></i>Receber Pagamento </button> </div> </div> </div> </div> </div> @endif </div>
9. Invoice Totals Component
Componente para exibição detalhada dos totais e valores da fatura.
Uso Básico
<x-invoice.invoice-totals :invoice="$invoice" :showBreakdown="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo da fatura | Obrigatório |
| | Exibir detalhamento dos valores | |
| | Estilo visual (primary, success, etc.) | |
Estrutura
@props([ 'invoice', 'showBreakdown' => true, 'variant' => 'primary' ]) <div class="invoice-totals"> @if($showBreakdown) <div class="row g-3 mb-3"> <div class="col-md-4"> <div class="card border-0"> <div class="card-body text-center"> <div class="text-muted small">Subtotal</div> <div class="h5 mb-0 fw-bold"> R$ {{ number_format($invoice->invoiceItems->sum('total'), 2, ',', '.') }} </div> </div> </div> </div> <div class="col-md-4"> <div class="card border-0"> <div class="card-body text-center"> <div class="text-muted small">Desconto</div> <div class="h5 mb-0 fw-bold text-danger"> - R$ {{ number_format($invoice->discount, 2, ',', '.') }} </div> </div> </div> </div> <div class="col-md-4"> <div class="card border-0 bg-{{ $variant }} bg-gradient text-white"> <div class="card-body text-center"> <div class="small">Valor Total</div> <div class="h4 mb-0 fw-bold"> R$ {{ number_format($invoice->total, 2, ',', '.') }} </div> </div> </div> </div> </div> <!-- Pagamentos --> @if($invoice->payments->isNotEmpty()) <div class="row g-3 mb-3"> <div class="col-md-4"> <div class="card border-0"> <div class="card-body text-center"> <div class="text-muted small">Total Pago</div> <div class="h5 mb-0 fw-bold text-success"> R$ {{ number_format($invoice->payments->sum('transaction_amount'), 2, ',', '.') }} </div> </div> </div> </div> <div class="col-md-4"> <div class="card border-0"> <div class="card-body text-center"> <div class="text-muted small">Saldo Devedor</div> <div class="h5 mb-0 fw-bold {{ $invoice->balance > 0 ? 'text-warning' : 'text-success' }}"> R$ {{ number_format($invoice->balance, 2, ',', '.') }} </div> </div> </div> </div> <div class="col-md-4"> <div class="card border-0 bg-{{ $invoice->balance > 0 ? 'warning' : 'success' }} bg-gradient text-white"> <div class="card-body text-center"> <div class="small">Status</div> <div class="h4 mb-0 fw-bold"> {{ $invoice->status->getDescription() }} </div> </div> </div> </div> </div> @endif @else <div class="card border-0 bg-{{ $variant }} bg-gradient text-white"> <div class="card-body"> <div class="row align-items-center"> <div class="col-md-8"> <div class="small">Valor Total da Fatura</div> <div class="h3 mb-0 fw-bold"> R$ {{ number_format($invoice->total, 2, ',', '.') }} </div> </div> <div class="col-md-4 text-end"> <x-invoice.invoice-status :invoice="$invoice" /> </div> </div> </div> </div> @endif </div>
10. Integração com Padrões Existentes
Uso em Views
{{-- Dashboard --}} <x-invoice.invoice-card :invoice="$invoice" /> {{-- Listagem --}} <x-invoice.invoice-details :invoice="$invoice" /> {{-- Formulários --}} <x-invoice.invoice-form :invoice="$invoice" :customers="$customers" :services="$services" /> {{-- Ações --}} <x-invoice.invoice-actions :invoice="$invoice" />
Estilos CSS
/* Invoice Components Styles */ .invoice-form .price-input { text-align: right; } .invoice-form .item-total { background-color: #f8f9fa; font-weight: bold; } .invoice-actions .btn { transition: all 0.2s ease; } .invoice-actions .btn:hover { transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .invoice-payments .card { border-left: 4px solid #0d6efd; } .invoice-payments .card.bg-warning { border-left-color: #ffc107; }
11. JavaScript Interatividade
Formulário de Fatura
// invoice-form.js document.addEventListener('DOMContentLoaded', function() { // Formatar valores monetários document.querySelectorAll('.price-input').forEach(input => { input.addEventListener('input', function() { let value = this.value.replace(/\D/g, ''); value = (value / 100).toFixed(2); this.value = value.replace('.', ','); calculateTotals(); }); }); // Calcular totais function calculateTotals() { let subtotal = 0; document.querySelectorAll('.item-row').forEach(row => { const quantity = parseFloat(row.querySelector('.quantity-input').value) || 0; const unitPrice = parseFloat(row.querySelector('.price-input').value.replace(',', '.')) || 0; const total = quantity * unitPrice; row.querySelector('.item-total').value = 'R$ ' + total.toLocaleString('pt-BR', { minimumFractionDigits: 2 }); subtotal += total; }); const discount = parseFloat(document.getElementById('discountInput').value.replace(',', '.')) || 0; const total = subtotal - discount; document.getElementById('subtotalDisplay').textContent = 'R$ ' + subtotal.toLocaleString('pt-BR', { minimumFractionDigits: 2 }); document.getElementById('totalDisplay').textContent = 'R$ ' + total.toLocaleString('pt-BR', { minimumFractionDigits: 2 }); } // Adicionar item document.getElementById('addInvoiceItem').addEventListener('click', function() { const itemsContainer = document.getElementById('invoiceItems'); const itemCount = itemsContainer.children.length; const newRow = document.createElement('div'); newRow.className = 'row g-3 item-row mb-3'; newRow.innerHTML = ` <div class="col-md-6"> <input type="text" name="items[${itemCount}][description]" class="form-control description-input" placeholder="Descrição do item" required> </div> <div class="col-md-2"> <input type="number" name="items[${itemCount}][quantity]" class="form-control quantity-input" placeholder="Qtd" value="1" min="1" required> </div> <div class="col-md-2"> <input type="text" name="items[${itemCount}][unit_price]" class="form-control price-input" placeholder="Valor Unitário" required> </div> <div class="col-md-2"> <input type="text" class="form-control item-total" placeholder="Total" readonly> </div> `; itemsContainer.appendChild(newRow); calculateTotals(); }); // Eventos de cálculo document.getElementById('invoiceItems').addEventListener('input', calculateTotals); document.getElementById('discountInput').addEventListener('input', calculateTotals); // Inicializar cálculos calculateTotals(); });
12. Validação e Segurança
Autorização
// InvoicePolicy.php public function view(User $user, Invoice $invoice) { return $user->tenant_id === $invoice->tenant_id; } public function update(User $user, Invoice $invoice) { return $user->tenant_id === $invoice->tenant_id && $invoice->status->isEditable(); } public function delete(User $user, Invoice $invoice) { return $user->tenant_id === $invoice->tenant_id && $invoice->status->isDeletable(); } public function receivePayment(User $user, Invoice $invoice) { return $user->tenant_id === $invoice->tenant_id && $invoice->status->isPending(); }
Validations
{{-- Invoice Card com validação de permissões --}} @can('view', $invoice) <x-invoice.invoice-card :invoice="$invoice" /> @endcan {{-- Invoice Actions com validação de status --}} @can('update', $invoice) @if($invoice->status->isEditable()) <x-invoice.invoice-actions :invoice="$invoice" /> @endif @endcan
Este padrão de components para faturas garante consistência visual, reutilização de código e manutenibilidade, seguindo os mesmos princípios estabelecidos nos outros components do sistema.