Claude-skill-registry customer-ui-components
Componentes de UI para clientes 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/customer-ui-components" ~/.claude/skills/majiayu000-claude-skill-registry-customer-ui-components && rm -rf "$T"
skills/data/customer-ui-components/SKILL.mdComponentes de UI para Clientes
Esta skill define os componentes Blade específicos para a gestão de clientes no Easy Budget, seguindo o padrão de components estabelecido no sistema.
Estrutura de Components
resources/views/components/ ├── customer/ │ ├── customer-card.blade.php # Card resumido de cliente │ ├── customer-details.blade.php # Detalhes completos do cliente │ ├── customer-form.blade.php # Formulário de criação/edição │ ├── customer-status.blade.php # Badge de status do cliente │ ├── customer-contact.blade.php # Informações de contato │ ├── customer-address.blade.php # Endereço do cliente │ ├── customer-actions.blade.php # Ações disponíveis para cliente │ ├── customer-filters.blade.php # Filtros específicos para clientes │ ├── customer-interactions.blade.php # Histórico de interações │ └── customer-summary.blade.php # Resumo do cliente └── ...
1. Customer Card Component
Componente para exibição resumida de clientes em listas e dashboards.
Uso Básico
<x-customer.customer-card :customer="$customer" :showType="true" :showStatus="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo do cliente | Obrigatório |
| | Exibir tipo de cliente (PF/PJ) | |
| | Exibir status do cliente | |
| | Estilo do card (primary, secondary, etc.) | |
Estrutura
@props([ 'customer', 'showType' => true, 'showStatus' => 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"> @if($customer->type === 'pf') <i class="bi bi-person text-white"></i> @else <i class="bi bi-building text-white"></i> @endif </div> <div> <h6 class="mb-1 fw-bold">{{ $customer->display_name }}</h6> <small class="text-muted">{{ $customer->created_at->format('d/m/Y') }}</small> </div> </div> @if($showStatus) <x-customer.customer-status :customer="$customer" /> @endif </div> @if($showType) <div class="mb-3"> <span class="badge bg-secondary">{{ $customer->type === 'pf' ? 'Pessoa Física' : 'Pessoa Jurídica' }}</span> </div> @endif @if($customer->contact) <div class="mb-3"> <small class="text-muted">E-mail:</small> <div class="fw-semibold">{{ $customer->contact->email }}</div> </div> @endif @if($customer->address) <div class="mb-3"> <small class="text-muted">Endereço:</small> <div class="fw-semibold">{{ $customer->address->city }}/{{ $customer->address->state }}</div> </div> @endif <div class="d-flex justify-content-between align-items-center"> <div> <small class="text-muted">Total de Orçamentos</small> <div class="h6 mb-0 fw-bold text-{{ $variant }}"> {{ $customer->budgets->count() }} </div> </div> <div class="btn-group btn-group-sm" role="group"> <a href="{{ route('provider.customers.show', $customer) }}" class="btn btn-outline-primary"> <i class="bi bi-eye"></i> Ver </a> <a href="{{ route('provider.customers.edit', $customer) }}" class="btn btn-outline-secondary"> <i class="bi bi-pencil"></i> Editar </a> </div> </div> </div> </div>
2. Customer Details Component
Componente para exibição detalhada de informações do cliente.
Uso Básico
<x-customer.customer-details :customer="$customer" :showContact="true" :showAddress="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo do cliente | Obrigatório |
| | Exibir informações de contato | |
| | Exibir endereço | |
| | Permitir colapsar seções | |
Estrutura
@props([ 'customer', 'showContact' => true, 'showAddress' => true, 'collapsible' => false ]) <div class="customer-details"> <!-- Informações Básicas --> <div class="row g-3 mb-3"> <div class="col-md-4"> <div class="info-item"> <label class="text-muted small">Nome/Razão Social</label> <div class="fw-bold">{{ $customer->display_name }}</div> </div> </div> <div class="col-md-4"> <div class="info-item"> <label class="text-muted small">Tipo</label> <div class="fw-bold"> <span class="badge bg-secondary">{{ $customer->type === 'pf' ? 'Pessoa Física' : 'Pessoa Jurídica' }}</span> </div> </div> </div> <div class="col-md-4"> <div class="info-item"> <label class="text-muted small">Status</label> <x-customer.customer-status :customer="$customer" /> </div> </div> </div> <!-- Dados Pessoais/Empresariais --> @if($customer->commonData) <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">{{ $customer->type === 'pf' ? 'Dados Pessoais' : 'Dados Empresariais' }}</h6> </div> <div class="card-body"> <div class="row"> @if($customer->type === 'pf') <div class="col-md-6"> <div class="mb-2"> <label class="text-muted small">Nome Completo</label> <div class="fw-bold">{{ $customer->commonData->first_name }} {{ $customer->commonData->last_name }}</div> </div> <div class="mb-2"> <label class="text-muted small">Data de Nascimento</label> <div class="fw-bold">{{ $customer->commonData->birth_date?->format('d/m/Y') ?? 'Não informado' }}</div> </div> @if($customer->commonData->cpf) <div class="mb-2"> <label class="text-muted small">CPF</label> <div class="fw-bold">{{ \App\Helpers\DocumentHelper::formatCpf($customer->commonData->cpf) }}</div> </div> @endif </div> @else <div class="col-md-6"> <div class="mb-2"> <label class="text-muted small">Razão Social</label> <div class="fw-bold">{{ $customer->commonData->company_name }}</div> </div> @if($customer->commonData->cnpj) <div class="mb-2"> <label class="text-muted small">CNPJ</label> <div class="fw-bold">{{ \App\Helpers\DocumentHelper::formatCnpj($customer->commonData->cnpj) }}</div> </div> @endif @if($customer->commonData->cpf) <div class="mb-2"> <label class="text-muted small">CPF do Responsável</label> <div class="fw-bold">{{ \App\Helpers\DocumentHelper::formatCpf($customer->commonData->cpf) }}</div> </div> @endif </div> @endif <div class="col-md-6"> @if($customer->commonData->areaOfActivity) <div class="mb-2"> <label class="text-muted small">Área de Atuação</label> <div class="fw-bold">{{ $customer->commonData->areaOfActivity->name }}</div> </div> @endif @if($customer->commonData->profession) <div class="mb-2"> <label class="text-muted small">Profissão</label> <div class="fw-bold">{{ $customer->commonData->profession->name }}</div> </div> @endif @if($customer->commonData->description) <div class="mb-2"> <label class="text-muted small">Descrição</label> <div class="fw-bold">{{ $customer->commonData->description }}</div> </div> @endif </div> </div> </div> </div> </div> </div> @endif <!-- Contato --> @if($showContact && $customer->contact) <x-customer.customer-contact :contact="$customer->contact" /> @endif <!-- Endereço --> @if($showAddress && $customer->address) <x-customer.customer-address :address="$customer->address" /> @endif <!-- Resumo --> <x-customer.customer-summary :customer="$customer" /> </div>
3. Customer Status Component
Componente para exibição do status do cliente com cores e ícones apropriados.
Uso Básico
<x-customer.customer-status :customer="$customer" :showIcon="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo do cliente | Obrigatório |
| | Exibir ícone ao lado do status | |
| | Tamanho do badge (sm, md, lg) | |
Estrutura
@props([ 'customer', 'showIcon' => true, 'size' => 'md' ]) @php $status = $customer->status; $color = match($status) { 'active' => '#198754', // Verde 'inactive' => '#6c757d', // Cinza 'pending' => '#ffc107', // Amarelo default => '#6c757d' }; $icon = match($status) { 'active' => 'check-circle', 'inactive' => 'x-circle', 'pending' => 'clock', default => 'circle' }; $description = match($status) { 'active' => 'Ativo', 'inactive' => 'Inativo', 'pending' => 'Pendente', default => 'Status Desconhecido' }; $sizeClass = match($size) { 'sm' => 'badge-sm', 'lg' => 'badge-lg', default => 'badge-md' }; @endphp <span class="badge modern-badge {{ $sizeClass }}" style="background-color: {{ $color }}20; color: {{ $color }}; border: 1px solid {{ $color }}40;"> @if($showIcon) <i class="bi bi-{{ $icon }} me-1"></i> @endif {{ $description }} </span>
4. Customer Contact Component
Componente para exibição de informações de contato do cliente.
Uso Básico
<x-customer.customer-contact :contact="$contact" :showBusiness="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo de contato | Obrigatório |
| | Exibir contatos comerciais | |
| | Exibir website | |
Estrutura
@props([ 'contact', 'showBusiness' => true, 'showWebsite' => true ]) <div class="customer-contact"> <div class="card border-0 shadow-sm"> <div class="card-header bg-transparent border-0"> <h6 class="mb-0">Informações de Contato</h6> </div> <div class="card-body"> <div class="row"> <div class="col-md-6"> <div class="mb-3"> <label class="text-muted small">E-mail Principal</label> <div class="fw-bold">{{ $contact->email }}</div> </div> @if($contact->phone) <div class="mb-3"> <label class="text-muted small">Telefone</label> <div class="fw-bold">{{ $contact->phone }}</div> </div> @endif </div> @if($showBusiness) <div class="col-md-6"> @if($contact->email_business) <div class="mb-3"> <label class="text-muted small">E-mail Comercial</label> <div class="fw-bold">{{ $contact->email_business }}</div> </div> @endif @if($contact->phone_business) <div class="mb-3"> <label class="text-muted small">Telefone Comercial</label> <div class="fw-bold">{{ $contact->phone_business }}</div> </div> @endif </div> @endif </div> @if($showWebsite && $contact->website) <div class="row"> <div class="col-12"> <div class="mb-3"> <label class="text-muted small">Website</label> <div class="fw-bold"> <a href="{{ $contact->website }}" target="_blank" class="text-decoration-none"> {{ $contact->website }} </a> </div> </div> </div> </div> @endif </div> </div> </div>
5. Customer Address Component
Componente para exibição de endereço do cliente.
Uso Básico
<x-customer.customer-address :address="$address" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo de endereço | Obrigatório |
Estrutura
@props(['address']) <div class="customer-address"> <div class="card border-0 shadow-sm"> <div class="card-header bg-transparent border-0"> <h6 class="mb-0">Endereço</h6> </div> <div class="card-body"> <div class="row"> <div class="col-md-8"> <div class="mb-2"> <label class="text-muted small">Logradouro</label> <div class="fw-bold"> {{ $address->address }}, {{ $address->address_number }} @if($address->address_complement) - {{ $address->address_complement }} @endif </div> </div> <div class="mb-2"> <label class="text-muted small">Bairro</label> <div class="fw-bold">{{ $address->neighborhood }}</div> </div> </div> <div class="col-md-4"> <div class="mb-2"> <label class="text-muted small">CEP</label> <div class="fw-bold">{{ $address->cep }}</div> </div> </div> </div> <div class="row"> <div class="col-md-6"> <div class="mb-2"> <label class="text-muted small">Cidade</label> <div class="fw-bold">{{ $address->city }}</div> </div> </div> <div class="col-md-6"> <div class="mb-2"> <label class="text-muted small">Estado</label> <div class="fw-bold">{{ $address->state }}</div> </div> </div> </div> @if($address->country && $address->country !== 'Brasil') <div class="row"> <div class="col-12"> <div class="mb-2"> <label class="text-muted small">País</label> <div class="fw-bold">{{ $address->country }}</div> </div> </div> </div> @endif </div> </div> </div>
6. Customer Form Component
Componente para formulário de criação e edição de clientes.
Uso Básico
<x-customer.customer-form :customer="$customer ?? null" :areasOfActivity="$areasOfActivity" :professions="$professions" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| `Customer | null` | Modelo do cliente (para edição) |
| | Áreas de atuação disponíveis | Obrigatório |
| | Profissões disponíveis | Obrigatório |
| | Tipo de cliente (pf/pj) | |
Estrutura
@props([ 'customer' => null, 'areasOfActivity' => [], 'professions' => [], 'type' => 'pf' ]) <div class="customer-form"> <form action="{{ isset($customer) ? route('provider.customers.update', $customer) : route('provider.customers.store') }}" method="POST" id="customerForm"> @csrf @if(isset($customer)) @method('PUT') @endif <!-- Tipo de Cliente --> <div class="row g-3 mb-4"> <div class="col-12"> <div class="card border-0 shadow-sm"> <div class="card-body"> <div class="row"> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Tipo de Cliente *</label> <div class="btn-group w-100" role="group"> <input type="radio" class="btn-check" name="type" id="type_pf" value="pf" {{ (old('type', $customer->type ?? $type) == 'pf') ? 'checked' : '' }} required> <label class="btn btn-outline-primary" for="type_pf"> <i class="bi bi-person me-2"></i>Pessoa Física </label> <input type="radio" class="btn-check" name="type" id="type_pj" value="pj" {{ (old('type', $customer->type ?? $type) == 'pj') ? 'checked' : '' }} required> <label class="btn btn-outline-secondary" for="type_pj"> <i class="bi bi-building me-2"></i>Pessoa Jurídica </label> </div> </div> </div> </div> </div> </div> </div> <!-- Dados Pessoais/Empresariais --> <div class="row g-3 mb-4"> <div class="col-12"> <div class="card border-0 shadow-sm"> <div class="card-header bg-transparent border-0"> <h6 class="mb-0">{{ $type === 'pf' ? 'Dados Pessoais' : 'Dados Empresariais' }}</h6> </div> <div class="card-body"> @if($type === 'pf') <!-- Pessoa Física --> <div class="row"> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Nome *</label> <input type="text" name="first_name" class="form-control @error('first_name') is-invalid @enderror" value="{{ old('first_name', $customer->commonData->first_name ?? '') }}" required> @error('first_name') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Sobrenome *</label> <input type="text" name="last_name" class="form-control @error('last_name') is-invalid @enderror" value="{{ old('last_name', $customer->commonData->last_name ?? '') }}" required> @error('last_name') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-4"> <label class="form-label small fw-bold text-muted text-uppercase">Data de Nascimento</label> <input type="date" name="birth_date" class="form-control @error('birth_date') is-invalid @enderror" value="{{ old('birth_date', $customer->commonData->birth_date?->format('Y-m-d') ?? '') }}"> @error('birth_date') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-4"> <label class="form-label small fw-bold text-muted text-uppercase">CPF *</label> <input type="text" name="cpf" class="form-control @error('cpf') is-invalid @enderror" value="{{ old('cpf', $customer->commonData->cpf ?? '') }}" required> @error('cpf') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> </div> @else <!-- Pessoa Jurídica --> <div class="row"> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Razão Social *</label> <input type="text" name="company_name" class="form-control @error('company_name') is-invalid @enderror" value="{{ old('company_name', $customer->commonData->company_name ?? '') }}" required> @error('company_name') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">CNPJ *</label> <input type="text" name="cnpj" class="form-control @error('cnpj') is-invalid @enderror" value="{{ old('cnpj', $customer->commonData->cnpj ?? '') }}" required> @error('cnpj') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">CPF do Responsável</label> <input type="text" name="cpf" class="form-control @error('cpf') is-invalid @enderror" value="{{ old('cpf', $customer->commonData->cpf ?? '') }}"> @error('cpf') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> </div> @endif <div class="row mt-3"> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Área de Atuação</label> <select name="area_of_activity_id" class="form-select @error('area_of_activity_id') is-invalid @enderror"> <option value="">Selecione uma área</option> @foreach($areasOfActivity as $area) <option value="{{ $area->id }}" {{ (old('area_of_activity_id', $customer->commonData->area_of_activity_id ?? '') == $area->id) ? 'selected' : '' }}> {{ $area->name }} </option> @endforeach </select> @error('area_of_activity_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">Profissão</label> <select name="profession_id" class="form-select @error('profession_id') is-invalid @enderror"> <option value="">Selecione uma profissão</option> @foreach($professions as $profession) <option value="{{ $profession->id }}" {{ (old('profession_id', $customer->commonData->profession_id ?? '') == $profession->id) ? 'selected' : '' }}> {{ $profession->name }} </option> @endforeach </select> @error('profession_id') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> </div> <div class="row mt-3"> <div class="col-12"> <label class="form-label small fw-bold text-muted text-uppercase">Descrição</label> <textarea name="description" class="form-control @error('description') is-invalid @enderror" rows="3" placeholder="Descrição do cliente...">{{ old('description', $customer->commonData->description ?? '') }}</textarea> @error('description') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> </div> </div> </div> </div> </div> <!-- Contato --> <div class="row g-3 mb-4"> <div class="col-12"> <div class="card border-0 shadow-sm"> <div class="card-header bg-transparent border-0"> <h6 class="mb-0">Informações de Contato</h6> </div> <div class="card-body"> <div class="row"> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">E-mail Principal *</label> <input type="email" name="email" class="form-control @error('email') is-invalid @enderror" value="{{ old('email', $customer->contact->email ?? '') }}" required> @error('email') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Telefone</label> <input type="tel" name="phone" class="form-control @error('phone') is-invalid @enderror" value="{{ old('phone', $customer->contact->phone ?? '') }}"> @error('phone') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">E-mail Comercial</label> <input type="email" name="email_business" class="form-control @error('email_business') is-invalid @enderror" value="{{ old('email_business', $customer->contact->email_business ?? '') }}"> @error('email_business') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Telefone Comercial</label> <input type="tel" name="phone_business" class="form-control @error('phone_business') is-invalid @enderror" value="{{ old('phone_business', $customer->contact->phone_business ?? '') }}"> @error('phone_business') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Website</label> <input type="url" name="website" class="form-control @error('website') is-invalid @enderror" value="{{ old('website', $customer->contact->website ?? '') }}"> @error('website') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> </div> </div> </div> </div> </div> <!-- Endereço --> <div class="row g-3 mb-4"> <div class="col-12"> <div class="card border-0 shadow-sm"> <div class="card-header bg-transparent border-0"> <h6 class="mb-0">Endereço</h6> </div> <div class="card-body"> <div class="row"> <div class="col-md-8"> <label class="form-label small fw-bold text-muted text-uppercase">Logradouro *</label> <input type="text" name="address" class="form-control @error('address') is-invalid @enderror" value="{{ old('address', $customer->address->address ?? '') }}" required> @error('address') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-4"> <label class="form-label small fw-bold text-muted text-uppercase">Número *</label> <input type="text" name="address_number" class="form-control @error('address_number') is-invalid @enderror" value="{{ old('address_number', $customer->address->address_number ?? '') }}" required> @error('address_number') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Complemento</label> <input type="text" name="address_complement" class="form-control @error('address_complement') is-invalid @enderror" value="{{ old('address_complement', $customer->address->address_complement ?? '') }}"> @error('address_complement') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">Bairro *</label> <input type="text" name="neighborhood" class="form-control @error('neighborhood') is-invalid @enderror" value="{{ old('neighborhood', $customer->address->neighborhood ?? '') }}" required> @error('neighborhood') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-4"> <label class="form-label small fw-bold text-muted text-uppercase">CEP *</label> <input type="text" name="cep" class="form-control @error('cep') is-invalid @enderror" value="{{ old('cep', $customer->address->cep ?? '') }}" required> @error('cep') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-4"> <label class="form-label small fw-bold text-muted text-uppercase">Cidade *</label> <input type="text" name="city" class="form-control @error('city') is-invalid @enderror" value="{{ old('city', $customer->address->city ?? '') }}" required> @error('city') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-4"> <label class="form-label small fw-bold text-muted text-uppercase">Estado *</label> <input type="text" name="state" class="form-control @error('state') is-invalid @enderror" value="{{ old('state', $customer->address->state ?? '') }}" required> @error('state') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> <div class="col-md-6"> <label class="form-label small fw-bold text-muted text-uppercase">País</label> <input type="text" name="country" class="form-control @error('country') is-invalid @enderror" value="{{ old('country', $customer->address->country ?? 'Brasil') }}"> @error('country') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> </div> </div> </div> </div> </div> <!-- Ações --> <div class="d-flex justify-content-between"> <a href="{{ route('provider.customers.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($customer) ? 'Atualizar' : 'Criar' }} Cliente </button> </div> </form> </div> @push('scripts') <script> document.addEventListener('DOMContentLoaded', function() { // Formatar CPF/CNPJ const cpfInput = document.querySelector('input[name="cpf"]'); const cnpjInput = document.querySelector('input[name="cnpj"]'); if (cpfInput) { cpfInput.addEventListener('input', function() { let value = this.value.replace(/\D/g, ''); if (value.length > 11) value = value.slice(0, 11); if (value.length > 3) { value = value.replace(/(\d{3})(\d)/, '$1.$2'); } if (value.length > 7) { value = value.replace(/(\d{3})(\d)/, '$1.$2'); } if (value.length > 11) { value = value.replace(/(\d{3})(\d{1})$/, '$1-$2'); } this.value = value; }); } if (cnpjInput) { cnpjInput.addEventListener('input', function() { let value = this.value.replace(/\D/g, ''); if (value.length > 14) value = value.slice(0, 14); if (value.length > 2) { value = value.replace(/(\d{2})(\d)/, '$1.$2'); } if (value.length > 6) { value = value.replace(/(\d{3})(\d)/, '$1.$2'); } if (value.length > 10) { value = value.replace(/(\d{3})(\d)/, '$1/$2'); } if (value.length > 15) { value = value.replace(/(\d{4})(\d{1})$/, '$1-$2'); } this.value = value; }); } // Formatar CEP const cepInput = document.querySelector('input[name="cep"]'); if (cepInput) { cepInput.addEventListener('input', function() { let value = this.value.replace(/\D/g, ''); if (value.length > 8) value = value.slice(0, 8); if (value.length > 5) { value = value.replace(/(\d{5})(\d)/, '$1-$2'); } this.value = value; }); } }); </script> @endpush
7. Customer Actions Component
Componente para exibição de ações disponíveis para um cliente.
Uso Básico
<x-customer.customer-actions :customer="$customer" :showBudget="true" :showInvoice="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo do cliente | Obrigatório |
| | Exibir botão de novo orçamento | |
| | Exibir botão de nova fatura | |
| | Exibir botão de envio de e-mail | |
Estrutura
@props([ 'customer', 'showBudget' => true, 'showInvoice' => true, 'showSendEmail' => true ]) <div class="customer-actions btn-group" role="group"> <!-- Visualizar --> <a href="{{ route('provider.customers.show', $customer) }}" class="btn btn-outline-primary" title="Visualizar Cliente"> <i class="bi bi-eye"></i> Visualizar </a> <!-- Editar --> <a href="{{ route('provider.customers.edit', $customer) }}" class="btn btn-outline-secondary" title="Editar Cliente"> <i class="bi bi-pencil"></i> Editar </a> <!-- Novo Orçamento --> @if($showBudget) <a href="{{ route('provider.budgets.create') }}?customer_id={{ $customer->id }}" class="btn btn-outline-info" title="Novo Orçamento"> <i class="bi bi-file-earmark-text"></i> Orçamento </a> @endif <!-- Nova Fatura --> @if($showInvoice) <a href="{{ route('provider.invoices.create') }}?customer_id={{ $customer->id }}" class="btn btn-outline-success" title="Nova Fatura"> <i class="bi bi-receipt"></i> Fatura </a> @endif <!-- Enviar E-mail --> @if($showSendEmail) <button type="button" class="btn btn-outline-warning" data-bs-toggle="modal" data-bs-target="#emailModal-{{ $customer->id }}" title="Enviar E-mail"> <i class="bi bi-envelope"></i> E-mail </button> @endif <!-- Excluir --> <button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteModal-{{ $customer->id }}" title="Excluir Cliente"> <i class="bi bi-trash"></i> Excluir </button> </div>
8. Customer Filters Component
Componente para filtros específicos de listagem de clientes.
Uso Básico
<x-customer.customer-filters :filters="$filters" :showType="true" :showStatus="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Filtros atuais | |
| | Exibir filtro por tipo | |
| | Exibir filtro por status | |
| | Exibir filtro por período | |
Estrutura
@props([ 'filters' => [], 'showType' => true, 'showStatus' => true, 'showDateRange' => true ]) <div class="customer-filters"> <form action="{{ request()->url() }}" method="GET" class="row g-3"> @if($showType) <div class="col-md-3"> <label class="form-label small fw-bold text-muted text-uppercase">Tipo</label> <select name="type" class="form-select form-select-sm"> <option value="">Todos os tipos</option> <option value="pf" {{ ($filters['type'] ?? '') == 'pf' ? 'selected' : '' }}>Pessoa Física</option> <option value="pj" {{ ($filters['type'] ?? '') == 'pj' ? 'selected' : '' }}>Pessoa Jurídica</option> </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> <option value="active" {{ ($filters['status'] ?? '') == 'active' ? 'selected' : '' }}>Ativo</option> <option value="inactive" {{ ($filters['status'] ?? '') == 'inactive' ? 'selected' : '' }}>Inativo</option> <option value="pending" {{ ($filters['status'] ?? '') == 'pending' ? 'selected' : '' }}>Pendente</option> </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 nome..."> </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.customers.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>Novo Cliente </button> </div> </div> </div> </form> </div>
9. Customer Interactions Component
Componente para exibição do histórico de interações do cliente.
Uso Básico
<x-customer.customer-interactions :customer="$customer" :showRecent="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo do cliente | Obrigatório |
| | Exibir apenas interações recentes | |
| | Limite de interações a exibir | |
Estrutura
@props([ 'customer', 'showRecent' => true, 'limit' => 10 ]) <div class="customer-interactions"> <div class="card border-0 shadow-sm"> <div class="card-header bg-transparent border-0"> <h6 class="mb-0">Histórico de Interações</h6> </div> <div class="card-body p-0"> @if($customer->activities->isNotEmpty()) <div class="list-group list-group-flush"> @foreach($customer->activities->take($limit) as $activity) <div class="list-group-item"> <div class="d-flex w-100 justify-content-between"> <h6 class="mb-1">{{ $activity->description }}</h6> <small class="text-muted">{{ $activity->created_at->diffForHumans() }}</small> </div> <p class="mb-1 text-muted small">{{ $activity->metadata }}</p> <small class="text-muted">{{ $activity->action_type }}</small> </div> @endforeach </div> @else <div class="text-center py-4"> <i class="bi bi-chat-dots text-muted" style="font-size: 3rem;"></i> <p class="text-muted mt-2">Nenhuma interação registrada</p> </div> @endif </div> </div> </div>
10. Customer Summary Component
Componente para exibição de resumo do cliente.
Uso Básico
<x-customer.customer-summary :customer="$customer" :showStats="true" />
Parâmetros
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| | Modelo do cliente | Obrigatório |
| | Exibir estatísticas | |
| | Exibir última interação | |
Estrutura
@props([ 'customer', 'showStats' => true, 'showLastInteraction' => true ]) <div class="customer-summary"> <div class="row g-3"> @if($showStats) <!-- Estatísticas --> <div class="col-md-4"> <div class="card border-0 shadow-sm"> <div class="card-body text-center"> <div class="avatar-circle bg-primary bg-gradient mb-2 mx-auto"> <i class="bi bi-file-earmark-text text-white"></i> </div> <div class="text-muted small">Orçamentos</div> <div class="h4 mb-0 fw-bold">{{ $customer->budgets->count() }}</div> </div> </div> </div> <div class="col-md-4"> <div class="card border-0 shadow-sm"> <div class="card-body text-center"> <div class="avatar-circle bg-success bg-gradient mb-2 mx-auto"> <i class="bi bi-receipt text-white"></i> </div> <div class="text-muted small">Faturas</div> <div class="h4 mb-0 fw-bold">{{ $customer->invoices->count() }}</div> </div> </div> </div> <div class="col-md-4"> <div class="card border-0 shadow-sm"> <div class="card-body text-center"> <div class="avatar-circle bg-info bg-gradient mb-2 mx-auto"> <i class="bi bi-currency-dollar text-white"></i> </div> <div class="text-muted small">Valor Total</div> <div class="h4 mb-0 fw-bold"> R$ {{ number_format($customer->invoices->sum('total'), 2, ',', '.') }} </div> </div> </div> </div> @endif </div> @if($showLastInteraction && $customer->activities->isNotEmpty()) <div class="row mt-3"> <div class="col-12"> <div class="card border-0 shadow-sm"> <div class="card-header bg-transparent border-0"> <h6 class="mb-0">Última Interação</h6> </div> <div class="card-body"> <div class="d-flex w-100 justify-content-between"> <div> <h6 class="mb-1">{{ $customer->activities->first()->description }}</h6> <p class="mb-0 text-muted small">{{ $customer->activities->first()->metadata }}</p> </div> <div class="text-end"> <small class="text-muted">{{ $customer->activities->first()->created_at->format('d/m/Y H:i') }}</small> <br> <small class="text-muted">{{ $customer->activities->first()->action_type }}</small> </div> </div> </div> </div> </div> </div> @endif </div>
11. Integração com Padrões Existentes
Uso em Views
{{-- Dashboard --}} <x-customer.customer-card :customer="$customer" /> {{-- Listagem --}} <x-customer.customer-details :customer="$customer" /> {{-- Formulários --}} <x-customer.customer-form :customer="$customer" :areasOfActivity="$areasOfActivity" :professions="$professions" /> {{-- Ações --}} <x-customer.customer-actions :customer="$customer" />
Estilos CSS
/* Customer Components Styles */ .customer-form .btn-check:checked + .btn { background-color: #0d6efd; border-color: #0d6efd; color: white; } .customer-form .btn-check:checked + .btn-secondary { background-color: #6c757d; border-color: #6c757d; } .customer-actions .btn { transition: all 0.2s ease; } .customer-actions .btn:hover { transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .customer-summary .card { border-left: 4px solid #0d6efd; }
12. JavaScript Interatividade
Formulário de Cliente
// customer-form.js document.addEventListener('DOMContentLoaded', function() { // Alternar campos conforme tipo de cliente const typeRadios = document.querySelectorAll('input[name="type"]'); const cpfField = document.querySelector('input[name="cpf"]'); const cnpjField = document.querySelector('input[name="cnpj"]'); function toggleFields() { const selectedType = document.querySelector('input[name="type"]:checked').value; if (selectedType === 'pf') { cnpjField.closest('.row').style.display = 'none'; cpfField.closest('.row').style.display = 'block'; } else { cnpjField.closest('.row').style.display = 'block'; cpfField.closest('.row').style.display = 'block'; } } typeRadios.forEach(radio => { radio.addEventListener('change', toggleFields); }); // Inicializar campos toggleFields(); });
13. Validação e Segurança
Autorização
// CustomerPolicy.php public function view(User $user, Customer $customer) { return $user->tenant_id === $customer->tenant_id; } public function update(User $user, Customer $customer) { return $user->tenant_id === $customer->tenant_id; } public function delete(User $user, Customer $customer) { return $user->tenant_id === $customer->tenant_id && $customer->budgets->isEmpty() && $customer->invoices->isEmpty(); }
Validations
{{-- Customer Card com validação de permissões --}} @can('view', $customer) <x-customer.customer-card :customer="$customer" /> @endcan {{-- Customer Actions com validação de status --}} @can('update', $customer) <x-customer.customer-actions :customer="$customer" /> @endcan
Este padrão de components para clientes garante consistência visual, reutilização de código e manutenibilidade, seguindo os mesmos princípios estabelecidos nos outros components do sistema.