Skip to content

ERP Pickers

Visão geral

Conjunto de componentes de seleção para entidades do ERP, com busca server-side em tempo real. Cada picker é um wrapper zero-config sobre o LxMultiSelect e se conecta à API via LxErpFiltrosPlugin.

Instalação do plugin

Os pickers dependem do LxErpFiltrosPlugin instalado na aplicação. O plugin fornece apenas o httpClient via provide — cada picker define internamente seu próprio endpoint:

js
import { createApp } from 'vue';
import axios from 'axios';
import { LxErpFiltrosPlugin } from '@lde/lxcomponents';

const httpClient = axios.create({
  baseURL: 'https://api.meuapp.com',
  headers: { Authorization: `Bearer ${token}` }
});

createApp(App)
  .use(LxErpFiltrosPlugin, { httpClient })
  .mount('#app');

O httpClient deve ser uma instância do axios (ou compatível) já configurada com baseURL e interceptors de autenticação. O plugin injeta um helper get que os pickers usam para chamar seus endpoints relativos.

Componentes disponíveis

ComponenteEndpointchaveObservação
LxFornecedorPickerFiltrosClientesFornec/FornecedoresId
LxClientePickerFiltrosClientesFornec/ClientesId
LxClienteFornecedorPickerFiltrosClientesFornec/ClientesFornecedoresId
LxTransportadoraPickerFiltrosClientesFornec/TransportadorasId
LxVendedorPickerFiltrosFinanceiro/VendedoresId
LxVendedorCompradorPickerFiltrosFinanceiro/VendedoresCompradoresId
LxDepositoPickerFiltrosProdutos/DepositosId
LxTabelaPrecoPickerFiltrosProdutos/TabelasPrecoIdFiltra pela empresa da sessão
LxCfopPickerFiltrosFiscal/CfopsCodigoProp tipo: all | entrada | saida | saida_alt
LxSeriePickerFiltrosFiscal/SeriesCodigo
LxSerieEntradaPickerFiltrosFiscal/SeriesEntradaCodigo
LxCstPickerFiltrosFiscal/CstsCodigo
LxFormaPagamentoPickerFiltrosFinanceiro/FormasPagamentoId
LxPlanoPagamentoPickerFiltrosFinanceiro/PlanosPagamentoIdFiltra pela empresa da sessão
LxCentroCustoPickerFiltrosFinanceiro/CentrosCustoIdFiltra pela empresa da sessão
LxHistoricoContabilPickerFiltrosFinanceiro/HistoricosContabeisIdFiltra pela empresa da sessão
LxEmpresaPickerFiltrosEmpresa/EmpresasIdLabel inclui cidade, estado, grupo e plano comercial. Empresas fora do plano ficam desabilitadas.
LxSetorPickerFiltrosEstruturaMercadologica/SetoresId
LxClassePickerFiltrosClientesFornec/ClassesClientesId
LxEstruturaMercadologicaFilterSetor → Linha → Marca → ColeçãoIdCompound — ver abaixo

Uso básico

Todos os pickers seguem o mesmo padrão com v-model de array:

vue
<LxFornecedorPicker v-model="fornecedoresSelecionados" label="Fornecedores" />

Controle de carregamento — lazy e fetchOnMount

vue
<!-- Dataset grande: busca por digitação (padrão do LxClientePicker) -->
<LxClientePicker v-model="clientes" />

<!-- Forçar carregamento completo ao abrir o dropdown -->
<LxClientePicker v-model="clientes" :lazy="false" />

<!-- Carregar opções imediatamente ao montar (ex: form que precisa dos dados de cara) -->
<LxFormaPagamentoPicker v-model="formas" fetch-on-mount />

<!-- v-model pré-carregado: as tags aparecem de imediato, sem esperar o fetch -->
<LxVendedorPicker v-model="vendedoresSelecionados" />
<!-- vendedoresSelecionados = [{ Id: 5, Descricao: 'João Silva' }] -->

Formato do v-model

⚠️ Todos os pickers usam v-model do tipo Array. O array contém os objetos completos retornados pela API.

javascript
const fornecedores = ref([])   // ✅ sempre array vazio, nunca null

// após selecionar fornecedores:
// fornecedores.value === [
//   { Id: 5, Descricao: 'Fornecedor ABC' },
//   { Id: 12, Descricao: 'Fornecedor XYZ' }
// ]

Para enviar ao backend, extraia apenas os IDs:

javascript
const payload = {
  IdsFornecedor: fornecedores.value.map(f => f.Id)
}

⚠️ Cada picker tem sua própria chave primária (ver tabela "Componentes disponíveis"). Exemplos: LxFornecedorPickerId, LxCfopPickerCodigo.

Inicialização: ref([]) — nunca null.

LxCfopPicker — prop tipo

vue
<!-- Apenas CFOPs de saída -->
<LxCfopPicker v-model="cfops" tipo="saida" label="CFOPs de Saída" />

LxEmpresaPicker

Picker de seleção de empresas com busca server-side. Cada opção exibe o nome da empresa junto com cidade/UF, grupo e plano comercial na mesma label. Isso permite filtrar por qualquer critério digitando no campo de busca e usando "Selecionar todos" nos resultados.

Empresas não disponíveis para o plano comercial contratado aparecem desabilitadas com tooltip explicativo.

vue
<LxEmpresaPicker v-model="empresasSelecionadas" label="Empresas" />

Exemplo de label no dropdown: 01-Loja Centro | Florianópolis/SC | Grupo Sul | Plano Premium

O usuário pode digitar "SC" para ver todas as empresas de Santa Catarina, ou "Grupo Sul" para filtrar por grupo, e então clicar "Selecionar todos".

Propriedades — LxEmpresaPicker

NomeTipoPadrãoDescrição
modelValueArray[]Empresas selecionadas (v-model)
fetchOnMountBooleantrueCarrega empresas ao montar o componente
disableUnavailableBooleantrueDesabilita empresas fora do plano comercial
empresaLogadaNumber|StringnullID da empresa da sessão. Quando informado, ao desmarcar todas as empresas via "Remover todos", a empresa logada permanece selecionada.
labelStringLabel do campo
placeholderString'Buscar por empresa, cidade, estado, grupo...'Texto de placeholder
disabledBooleanfalseDesabilita o campo

LxEstruturaMercadologicaFilter

Componente composto com 4 selects encadeados. Cada nível filtra automaticamente o próximo com base nos IDs selecionados. Ao limpar um nível pai, os filhos são resetados.

vue
<LxEstruturaMercadologicaFilter
  v-model:setores="filtros.setores"
  v-model:linhas="filtros.linhas"
  v-model:marcas="filtros.marcas"
  v-model:colecoes="filtros.colecoes"
/>

Propriedades — LxEstruturaMercadologicaFilter

NomeTipoPadrãoDescrição
setoresArray[]Setores selecionados (v-model:setores)
linhasArray[]Linhas selecionadas (v-model:linhas)
marcasArray[]Marcas selecionadas (v-model:marcas)
colecoesArray[]Coleções selecionadas (v-model:colecoes)

Propriedades comuns — pickers simples

NomeTipoPadrãoDescrição
modelValueArray[]Itens selecionados (v-model)
lazyBooleantrue para datasets grandes¹, false para os demaisQuando true, só busca ao digitar (mín. 3 chars). Quando false, carrega tudo ao abrir o dropdown.
fetchOnMountBooleanfalseQuando true, dispara o fetch imediatamente ao montar o componente, sem precisar abrir o dropdown. Útil em componentes encadeados ou formulários que precisam dos dados de cara.
⚠️ Não tem efeito quando lazy=true — no modo lazy nenhum fetch é disparado até o usuário digitar.
labelStringLabel do campo (passada via v-bind)
placeholderStringdefinido por pickerTexto de placeholder
disabledBooleanfalseDesabilita o campo
is-invalidBooleanfalseEstado de erro
error-textStringMensagem de erro
tipoString'all'Apenas LxCfopPicker — variante do CFOP

¹ Pickers com lazy: true por padrão: LxClientePicker, LxFornecedorPicker, LxTransportadoraPicker, LxClienteFornecedorPicker.

Todos os atributos adicionais são repassados ao LxMultiSelect via v-bind="$attrs".

Playground

Cole o JWT de uma sessão autenticada e teste os pickers contra a API real:

Configuração da API

Sem Base URL, as requisições passam pelo proxy do Vite → https://localhost:7020
Informe o JWT para conectar
Clique para exibir o código
vue
<script setup>
import { ref, provide, computed } from 'vue';
import {
	LxFornecedorPicker,
	LxClientePicker,
	LxClienteFornecedorPicker,
	LxTransportadoraPicker,
	LxVendedorPicker,
	LxVendedorCompradorPicker,
	LxTabelaPrecoPicker,
	LxDepositoPicker,
	LxCfopPicker,
	LxSeriePicker,
	LxSerieEntradaPicker,
	LxCstPicker,
	LxFormaPagamentoPicker,
	LxPlanoPagamentoPicker,
	LxCentroCustoPicker,
	LxHistoricoContabilPicker,
	LxEstruturaMercadologicaFilter,
	LxEmpresaPicker
} from '@lde/lxcomponents';

// --- Configuração da conexão ---
// Por padrão usa /api/... que o Vite proxy roteia para https://localhost:7020
const baseUrl = ref('');
const jwt = ref('');
const connected = ref(false);
const connectionError = ref(null);
const abaAtiva = ref('clientes');
const lastError = ref(null);

// URL e JWT "congelados" após clicar Conectar
let _baseUrl = '';
let _jwt = '';

const canConnect = computed(() => jwt.value.trim().length > 0);

const connect = () => {
	connectionError.value = null;
	_baseUrl = baseUrl.value.trim().replace(/\/$/, ''); // remove trailing slash
	_jwt = jwt.value.trim();
	connected.value = true;
};

const disconnect = () => {
	connected.value = false;
	lastError.value = null;
	connectionError.value = null;
};

// Adapter fetch — usa os valores congelados (_baseUrl / _jwt)
const get = (url, params) => {
	const qs = new URLSearchParams();
	Object.entries(params ?? {}).forEach(([k, v]) => {
		if (v != null && v !== '') qs.append(k, v);
	});
	const fullUrl = `${_baseUrl}${url}${qs.toString() ? '?' + qs.toString() : ''}`;
	return fetch(fullUrl, { headers: _jwt ? { Authorization: _jwt } : {} })
		.then((r) => {
			if (!r.ok) throw new Error(`HTTP ${r.status} — ${r.statusText} (${fullUrl})`);
			return r.json();
		})
		.then((data) => {
			lastError.value = null;
			return data;
		})
		.catch((err) => {
			lastError.value = err?.message ?? String(err);
			throw err;
		});
};

// O provide é registrado no setup (obrigatório pelo Vue).
// As funções capturam _baseUrl e _jwt que só ficam populados após connectar.
provide('lxErpFiltros', {
	get,
	fetchSetores: (query) => get('/api/FiltrosEstruturaMercadologica/Setores', { query }),
	fetchLinhas: (query, idSetor) => get('/api/FiltrosEstruturaMercadologica/Linhas', { query, idSetor }),
	fetchMarcas: (query, idLinha) => get('/api/FiltrosEstruturaMercadologica/Marcas', { query, idLinha }),
	fetchColecoes: (query, idMarca) => get('/api/FiltrosEstruturaMercadologica/Colecoes', { query, idMarca }),
	fetchCfops: (query, tipo = 'all') => get('/api/FiltrosFiscal/Cfops', { query, tipo }),
	fetchSeries: (query) => get('/api/FiltrosFiscal/Series', { query }),
	fetchSeriesEntrada: (query) => get('/api/FiltrosFiscal/SeriesEntrada', { query }),
	fetchCsts: (query) => get('/api/FiltrosFiscal/Csts', { query }),
	fetchClassificacoes: (query) => get('/api/FiltrosProdutos/Classificacoes', { query }),
	fetchEspessuras: (query) => get('/api/FiltrosProdutos/Espessuras', { query }),
	fetchTamanhos: (query) => get('/api/FiltrosProdutos/Tamanhos', { query }),
	fetchCores: (query) => get('/api/FiltrosProdutos/Cores', { query }),
	fetchUnidades: (query) => get('/api/FiltrosProdutos/Unidades', { query }),
	fetchDepositos: (query) => get('/api/FiltrosProdutos/Depositos', { query }),
	fetchTabelasPreco: (query) => get('/api/FiltrosProdutos/TabelasPreco', { query }),
	fetchFornecedores: (query) => get('/api/FiltrosClientesFornec/Fornecedores', { query }),
	fetchClientes: (query) => get('/api/FiltrosClientesFornec/Clientes', { query }),
	fetchClientesFornecedores: (query) => get('/api/FiltrosClientesFornec/ClientesFornecedores', { query }),
	fetchTransportadoras: (query) => get('/api/FiltrosClientesFornec/Transportadoras', { query }),
	fetchGruposEmpresariais: (query) => get('/api/FiltrosClientesFornec/GruposEmpresariais', { query }),
	fetchClassesClientes: (query) => get('/api/FiltrosClientesFornec/ClassesClientes', { query }),
	fetchSubClasses: (query) => get('/api/FiltrosClientesFornec/SubClasses', { query }),
	fetchFormasPagamento: (query) => get('/api/FiltrosFinanceiro/FormasPagamento', { query }),
	fetchPlanosPagamento: (query) => get('/api/FiltrosFinanceiro/PlanosPagamento', { query }),
	fetchCentrosCusto: (query) => get('/api/FiltrosFinanceiro/CentrosCusto', { query }),
	fetchHistoricosContabeis: (query) => get('/api/FiltrosFinanceiro/HistoricosContabeis', { query }),
	fetchVendedores: (query) => get('/api/FiltrosFinanceiro/Vendedores', { query }),
	fetchVendedoresCompradores: (query) => get('/api/FiltrosFinanceiro/VendedoresCompradores', { query }),
	fetchPortais: (query) => get('/api/FiltrosAdmin/Portais', { query }),
	fetchPortaisExportacao: (query) => get('/api/FiltrosAdmin/PortaisExportacao', { query }),
	fetchUsuarios: (query) => get('/api/FiltrosAdmin/Usuarios', { query }),
	fetchAparelhos: (query) => get('/api/FiltrosOms/Aparelhos', { query }),
	fetchEmpresas: (query) => get('/api/FiltrosEmpresa/Empresas', { query })
});

// --- Estado dos pickers ---
const fornecedores = ref([]);
const clientes = ref([]);
const clientesFornecedores = ref([]);
const transportadoras = ref([]);
const vendedores = ref([]);
const vendedoresCompradores = ref([]);
const tabelasPreco = ref([]);
const depositos = ref([]);
const cfops = ref([]);
const cfopTipo = ref('all');
const series = ref([]);
const seriesEntrada = ref([]);
const csts = ref([]);
const formasPagamento = ref([]);
const planosPagamento = ref([]);
const centrosCusto = ref([]);
const historicosContabeis = ref([]);
const setores = ref([]);
const linhas = ref([]);
const marcas = ref([]);
const colecoes = ref([]);
const empresas = ref([]);
const empresaLogada = ref('');

empresaLogada.value = '1'; // valor default só para facilitar os testes, pode ser editado ou removido

const abas = [
	{ key: 'empresas', label: 'Empresas' },
	{ key: 'clientes', label: 'Clientes / Fornec.' },
	{ key: 'produtos', label: 'Produtos' },
	{ key: 'fiscal', label: 'Fiscal' },
	{ key: 'financeiro', label: 'Financeiro' },
	{ key: 'estrutura-mercadologica', label: 'Estrutura Mercadológica' }
];
</script>

<template>
	<div class="lx-pickers-playground">
		<!-- Config de conexão -->
		<div class="playground-config">
			<h4 class="config-title"><i class="fas fa-plug" /> Configuração da API</h4>

			<div v-if="!connected">
				<div class="config-fields">
					<div class="config-field">
						<label>Base URL <span class="text-muted">(opcional)</span></label>
						<input v-model="baseUrl" class="form-control" type="text" placeholder="Ex: https://localhost:7020 (vazio = proxy Vite)" />
					</div>
					<div class="config-field config-field--jwt">
						<label>JWT Token <span class="text-danger">*</span></label>
						<textarea v-model="jwt" class="form-control" rows="3" placeholder="Cole o token JWT aqui (sem Bearer)..." />
					</div>
					<small class="text-muted">Sem Base URL, as requisições passam pelo proxy do Vite → <code>https://localhost:7020</code></small>
				</div>
				<div v-if="connectionError" class="config-error mt-2">
					<i class="fas fa-triangle-exclamation" /> <strong>Erro:</strong> {{ connectionError }}
				</div>
				<div class="config-actions">
					<button class="btn-connect" :disabled="!canConnect" @click="connect">
						<i class="fas fa-bolt" />
						Conectar
					</button>
					<small v-if="!canConnect" class="text-muted">Informe o JWT para conectar</small>
				</div>
			</div>

			<div v-else class="config-connected">
				<span class="status-ok"><i class="fas fa-circle-check" /> Conectado{{ _baseUrl ? ` em ${_baseUrl}` : ' via proxy local' }}</span>
				<button class="btn-disconnect" @click="disconnect"><i class="fas fa-xmark" /> Desconectar</button>
			</div>
		</div>

		<!-- Pickers só renderizam após conectar -->
		<div v-if="connected" class="playground-pickers">
			<div v-if="lastError" class="config-error"><i class="fas fa-triangle-exclamation" /> <strong>Erro:</strong> {{ lastError }}</div>

			<!-- Tabs de grupos -->
			<div class="playground-tabs">
				<button v-for="aba in abas" :key="aba.key" class="tab-btn" :class="{ 'tab-btn--active': abaAtiva === aba.key }" @click="abaAtiva = aba.key">
					{{ aba.label }}
				</button>
			</div>

		<!-- Empresas -->
		<div v-show="abaAtiva === 'empresas'" class="playground-section">
			<div class="row mb-3">
				<div class="col-4">
					<label class="form-label" style="font-size:0.8rem">Empresa logada (ID)</label>
					<input v-model="empresaLogada" class="form-control form-control-sm" type="text" placeholder="Ex: 1" />
				</div>
			</div>
			<div class="row">
				<div class="col-12">
					<LxEmpresaPicker v-model="empresas" :empresa-logada="empresaLogada || null" label="Empresas" />
					<small class="model-preview">{{ empresas }}</small>
				</div>
			</div>
		</div>

		<!-- Clientes / Fornecedores -->
		<div v-show="abaAtiva === 'clientes'" class="playground-section">
			<div class="row">
				<div class="col-6">
					<LxFornecedorPicker v-model="fornecedores" label="Fornecedores" />
					<small class="model-preview">{{ fornecedores }}</small>
				</div>
				<div class="col-6">
					<LxClientePicker v-model="clientes" label="Clientes" />
					<small class="model-preview">{{ clientes }}</small>
				</div>
			</div>
			<div class="row mt-3">
				<div class="col-6">
					<LxClienteFornecedorPicker v-model="clientesFornecedores" label="Clientes e Fornecedores" />
					<small class="model-preview">{{ clientesFornecedores }}</small>
				</div>
				<div class="col-6">
					<LxTransportadoraPicker v-model="transportadoras" label="Transportadoras" />
					<small class="model-preview">{{ transportadoras }}</small>
				</div>
			</div>
		</div>

		<!-- Produtos -->
		<div v-show="abaAtiva === 'produtos'" class="playground-section">
			<div class="row">
				<div class="col-6">
					<LxDepositoPicker v-model="depositos" label="Depósitos" />
					<small class="model-preview">{{ depositos }}</small>
				</div>
				<div class="col-6">
					<LxTabelaPrecoPicker v-model="tabelasPreco" label="Tabelas de Preço" />
					<small class="model-preview">{{ tabelasPreco }}</small>
				</div>
			</div>
			<div class="row mt-3">
				<div class="col-6">
					<LxVendedorPicker v-model="vendedores" label="Vendedores" />
					<small class="model-preview">{{ vendedores }}</small>
				</div>
				<div class="col-6">
					<LxVendedorCompradorPicker v-model="vendedoresCompradores" label="Vendedores / Compradores" />
					<small class="model-preview">{{ vendedoresCompradores }}</small>
				</div>
			</div>
		</div>

		<!-- Fiscal -->
		<div v-show="abaAtiva === 'fiscal'" class="playground-section">
			<div class="row">
				<div class="col-6">
					<div class="mb-2">
						<label class="form-label">Tipo do CFOP</label>
						<select v-model="cfopTipo" class="form-select">
							<option value="all">Todos</option>
							<option value="entrada">Entrada</option>
							<option value="saida">Saída</option>
							<option value="saida_alt">Saída alternativa</option>
						</select>
					</div>
					<LxCfopPicker v-model="cfops" :tipo="cfopTipo" label="CFOPs" />
					<small class="model-preview">{{ cfops }}</small>
				</div>
				<div class="col-6">
					<LxCstPicker v-model="csts" label="CSTs" />
					<small class="model-preview">{{ csts }}</small>
				</div>
			</div>
			<div class="row mt-3">
				<div class="col-6">
					<LxSeriePicker v-model="series" label="Séries" />
					<small class="model-preview">{{ series }}</small>
				</div>
				<div class="col-6">
					<LxSerieEntradaPicker v-model="seriesEntrada" label="Séries de Entrada" />
					<small class="model-preview">{{ seriesEntrada }}</small>
				</div>
			</div>
		</div>

		<!-- Financeiro -->
		<div v-show="abaAtiva === 'financeiro'" class="playground-section">
			<div class="row">
				<div class="col-6">
					<LxFormaPagamentoPicker v-model="formasPagamento" label="Formas de Pagamento" />
					<small class="model-preview">{{ formasPagamento }}</small>
				</div>
				<div class="col-6">
					<LxPlanoPagamentoPicker v-model="planosPagamento" label="Planos de Pagamento" />
					<small class="model-preview">{{ planosPagamento }}</small>
				</div>
			</div>
			<div class="row mt-3">
				<div class="col-6">
					<LxCentroCustoPicker v-model="centrosCusto" label="Centros de Custo" />
					<small class="model-preview">{{ centrosCusto }}</small>
				</div>
				<div class="col-6">
					<LxHistoricoContabilPicker v-model="historicosContabeis" label="Históricos Contábeis" />
					<small class="model-preview">{{ historicosContabeis }}</small>
				</div>
			</div>
		</div>

		<!-- Mercadológico -->
		<div v-show="abaAtiva === 'estrutura-mercadologica'" class="playground-section">
			<LxEstruturaMercadologicaFilter v-model:setores="setores" v-model:linhas="linhas" v-model:marcas="marcas" v-model:colecoes="colecoes" />
			<div class="row mt-3">
				<div class="col-3">
					<strong>Setores:</strong>
					<small class="model-preview d-block">{{ setores }}</small>
				</div>
				<div class="col-3">
					<strong>Linhas:</strong>
					<small class="model-preview d-block">{{ linhas }}</small>
				</div>
				<div class="col-3">
					<strong>Marcas:</strong>
					<small class="model-preview d-block">{{ marcas }}</small>
				</div>
				<div class="col-3">
					<strong>Coleções:</strong>
					<small class="model-preview d-block">{{ colecoes }}</small>
				</div>
			</div>
		</div>
		</div>
	</div>
</template>

<style scoped>
.lx-pickers-playground {
	display: flex;
	flex-direction: column;
	gap: 1.25rem;
}

.playground-config {
	border: 1px solid var(--vp-c-border);
	border-radius: 8px;
	padding: 1rem 1.25rem;
	background: var(--vp-c-bg-soft);
}

.config-title {
	font-size: 0.9rem;
	font-weight: 600;
	margin: 0 0 0.75rem;
	color: var(--vp-c-text-2);
}

.config-fields {
	display: flex;
	gap: 1rem;
	flex-wrap: wrap;
}

.config-field {
	display: flex;
	flex-direction: column;
	gap: 0.25rem;
	min-width: 220px;
}

.config-field label {
	font-size: 0.78rem;
	font-weight: 500;
	color: var(--vp-c-text-2);
}

.config-field--jwt {
	flex: 1;
}

.status-ok {
	color: var(--vp-c-green-1, #3dd68c);
	font-size: 0.85rem;
}

.config-error {
	margin-top: 0.5rem;
	padding: 0.5rem 0.75rem;
	border-radius: 6px;
	background: var(--vp-c-danger-soft, #fef2f2);
	color: var(--vp-c-danger-1, #ef4444);
	font-size: 0.8rem;
	word-break: break-all;
}

.config-actions {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	margin-top: 1rem;
}

.btn-connect {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	padding: 0.5rem 1.25rem;
	border-radius: 6px;
	border: none;
	background: var(--vp-c-brand-1);
	color: #fff;
	font-size: 0.85rem;
	font-weight: 600;
	cursor: pointer;
	transition: opacity 0.15s;
}

.btn-connect:hover:not(:disabled) {
	opacity: 0.85;
}

.btn-connect:disabled {
	opacity: 0.5;
	cursor: not-allowed;
}

.config-connected {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
}

.btn-disconnect {
	display: inline-flex;
	align-items: center;
	gap: 0.35rem;
	padding: 0.35rem 0.75rem;
	border-radius: 6px;
	border: 1px solid var(--vp-c-border);
	background: var(--vp-c-bg);
	color: var(--vp-c-text-2);
	font-size: 0.8rem;
	cursor: pointer;
	transition: all 0.15s;
}

.btn-disconnect:hover {
	border-color: var(--vp-c-danger-1, #ef4444);
	color: var(--vp-c-danger-1, #ef4444);
}

.playground-tabs {
	display: flex;
	gap: 0.5rem;
	flex-wrap: wrap;
	margin-bottom: 10px;
}

.tab-btn {
	padding: 0.35rem 0.9rem;
	border-radius: 6px;
	border: 1px solid var(--vp-c-border);
	background: var(--vp-c-bg);
	color: var(--vp-c-text-2);
	font-size: 0.85rem;
	cursor: pointer;
	transition: all 0.15s;
}

.tab-btn:hover {
	border-color: var(--vp-c-brand-1);
	color: var(--vp-c-brand-1);
}

.tab-btn--active {
	background: var(--vp-c-brand-1);
	border-color: var(--vp-c-brand-1);
	color: #fff;
}

.playground-section {
	border: 1px solid var(--vp-c-border);
	border-radius: 8px;
	padding: 1.25rem;
}

.model-preview {
	display: block;
	margin-top: 0.4rem;
	font-size: 0.72rem;
	color: var(--vp-c-text-3);
	word-break: break-all;
	min-height: 1.2em;
}
</style>

Desenvolvido pelo time Linx Microvix