|
@@ -1,4 +1,6 @@
|
|
|
<script>
|
|
<script>
|
|
|
|
|
+ import { onMount } from 'svelte';
|
|
|
|
|
+
|
|
|
export let formData = {};
|
|
export let formData = {};
|
|
|
export let onFieldChange = () => {};
|
|
export let onFieldChange = () => {};
|
|
|
export let requiredFields = new Set();
|
|
export let requiredFields = new Set();
|
|
@@ -14,7 +16,41 @@
|
|
|
{ label: 'Não', value: 'N' }
|
|
{ label: 'Não', value: 'N' }
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
|
|
+ const STATE_FIELD = 'cpr_deliveryPlace_state_acronym';
|
|
|
|
|
+ const CITY_FIELD = 'cpr_deliveryPlace_city_name';
|
|
|
|
|
+ const CIDADES_ESTADOS_SRC = 'https://cdn.jsdelivr.net/npm/cidades-estados@1.4.1/cidades-estados.js';
|
|
|
|
|
+ const isBrowser = typeof window !== 'undefined';
|
|
|
|
|
+
|
|
|
|
|
+ let estadosOptions = [];
|
|
|
|
|
+ let cidadesPorEstado = {};
|
|
|
|
|
+ let estadosCarregando = false;
|
|
|
|
|
+ let estadosErro = '';
|
|
|
|
|
+ let selectedStateValue = '';
|
|
|
|
|
+ let selectedCityValue = '';
|
|
|
|
|
+ let availableCities = [];
|
|
|
|
|
+
|
|
|
const sections = [
|
|
const sections = [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: 'Emissão e Entrega',
|
|
|
|
|
+ description: 'Local de entrega e prazos do documento.',
|
|
|
|
|
+ columns: 2,
|
|
|
|
|
+ fields: [
|
|
|
|
|
+ {
|
|
|
|
|
+ key: 'cpr_place_name',
|
|
|
|
|
+ label: 'Local de entrega',
|
|
|
|
|
+ type: 'select',
|
|
|
|
|
+ options: [
|
|
|
|
|
+ { label: 'Selecione...', value: '' },
|
|
|
|
|
+ { label: 'EX WORKS (NA FAZENDA EM PRODUÇÃO)', value: 'EX WORKS (NA FAZENDA EM PRODUÇÃO)' },
|
|
|
|
|
+ { label: 'ARMAZEM', value: 'ARMAZEM' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ { key: 'cpr_document_deadline_days_number', label: 'Prazo para documentos (dias)' },
|
|
|
|
|
+ { key: STATE_FIELD, label: 'Estado da entrega' },
|
|
|
|
|
+ { key: CITY_FIELD, label: 'Cidade da entrega' },
|
|
|
|
|
+ // { key: 'cpr_deliveryPlace_ibge_code', label: 'IBGE da entrega' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
{
|
|
{
|
|
|
title: 'Produto e lastro',
|
|
title: 'Produto e lastro',
|
|
|
description: 'Características do produto e da produção.',
|
|
description: 'Características do produto e da produção.',
|
|
@@ -45,6 +81,77 @@
|
|
|
// }
|
|
// }
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
|
|
+ onMount(() => {
|
|
|
|
|
+ if (!isBrowser) return;
|
|
|
|
|
+ hydrateCidadesEstadosOptions();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ $: selectedStateValue = formData?.[STATE_FIELD] ?? '';
|
|
|
|
|
+ $: selectedCityValue = formData?.[CITY_FIELD] ?? '';
|
|
|
|
|
+ $: availableCities =
|
|
|
|
|
+ selectedStateValue && cidadesPorEstado[selectedStateValue]
|
|
|
|
|
+ ? cidadesPorEstado[selectedStateValue]
|
|
|
|
|
+ : [];
|
|
|
|
|
+
|
|
|
|
|
+ async function hydrateCidadesEstadosOptions() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ estadosCarregando = true;
|
|
|
|
|
+ const ctor = await loadCidadesEstadosScript();
|
|
|
|
|
+ const proto = ctor?.prototype;
|
|
|
|
|
+ if (!proto?.estados || !proto?.cidades) {
|
|
|
|
|
+ throw new Error('Estrutura inválida da biblioteca cidades-estados.');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ estadosOptions = [];
|
|
|
|
|
+ cidadesPorEstado = {};
|
|
|
|
|
+
|
|
|
|
|
+ proto.estados.forEach(([value, label], index) => {
|
|
|
|
|
+ if (!value) return;
|
|
|
|
|
+ estadosOptions.push({ value, label });
|
|
|
|
|
+ const cidades = proto.cidades?.[index] ?? [];
|
|
|
|
|
+ cidadesPorEstado[value] = cidades.filter(Boolean);
|
|
|
|
|
+ });
|
|
|
|
|
+ estadosErro = '';
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Erro ao carregar cidades-estados:', error);
|
|
|
|
|
+ estadosErro = error?.message ?? 'Não foi possível carregar estados e cidades.';
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ estadosCarregando = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function loadCidadesEstadosScript() {
|
|
|
|
|
+ if (!isBrowser) {
|
|
|
|
|
+ return Promise.reject(new Error('Ambiente indisponível para carregar cidades-estados.'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (window.__cidadesEstadosPromise) {
|
|
|
|
|
+ return window.__cidadesEstadosPromise;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ window.__cidadesEstadosPromise = new Promise((resolve, reject) => {
|
|
|
|
|
+ if (window.dgCidadesEstados) {
|
|
|
|
|
+ resolve(window.dgCidadesEstados);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const script = document.createElement('script');
|
|
|
|
|
+ script.src = CIDADES_ESTADOS_SRC;
|
|
|
|
|
+ script.async = true;
|
|
|
|
|
+ script.onload = () => {
|
|
|
|
|
+ if (window.dgCidadesEstados) {
|
|
|
|
|
+ resolve(window.dgCidadesEstados);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ reject(new Error('Biblioteca cidades-estados não expôs dgCidadesEstados.'));
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ script.onerror = () => reject(new Error('Falha ao carregar biblioteca cidades-estados.'));
|
|
|
|
|
+ document.head.appendChild(script);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return window.__cidadesEstadosPromise;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function getValue(key) {
|
|
function getValue(key) {
|
|
|
return formData?.[key] ?? '';
|
|
return formData?.[key] ?? '';
|
|
|
}
|
|
}
|
|
@@ -60,6 +167,23 @@
|
|
|
onRepeatingFieldChange(groupKey, index, fieldKey, event.currentTarget.value);
|
|
onRepeatingFieldChange(groupKey, index, fieldKey, event.currentTarget.value);
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ function isDeliveryStateField(key) {
|
|
|
|
|
+ return key === STATE_FIELD;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function isDeliveryCityField(key) {
|
|
|
|
|
+ return key === CITY_FIELD;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function handleDeliveryStateChange(value) {
|
|
|
|
|
+ onFieldChange(STATE_FIELD, value);
|
|
|
|
|
+ onFieldChange(CITY_FIELD, '');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function handleDeliveryCityChange(value) {
|
|
|
|
|
+ onFieldChange(CITY_FIELD, value);
|
|
|
|
|
+ }
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm">
|
|
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm">
|
|
@@ -79,7 +203,50 @@
|
|
|
<span class="text-red-500">*</span>
|
|
<span class="text-red-500">*</span>
|
|
|
{/if}
|
|
{/if}
|
|
|
</label>
|
|
</label>
|
|
|
- {#if field.type === 'select'}
|
|
|
|
|
|
|
+
|
|
|
|
|
+ {#if isDeliveryStateField(field.key)}
|
|
|
|
|
+ <select
|
|
|
|
|
+ class="w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:cursor-not-allowed disabled:opacity-60"
|
|
|
|
|
+ on:change={(event) => handleDeliveryStateChange(event.currentTarget.value)}
|
|
|
|
|
+ bind:value={selectedStateValue}
|
|
|
|
|
+ required={requiredFields?.has(field.key)}
|
|
|
|
|
+ disabled={estadosCarregando || !!estadosErro}
|
|
|
|
|
+ >
|
|
|
|
|
+ <option value="">{estadosCarregando ? 'Carregando estados...' : 'Selecione um estado'}</option>
|
|
|
|
|
+ {#each estadosOptions as estado}
|
|
|
|
|
+ <option value={estado.value}>{estado.label}</option>
|
|
|
|
|
+ {/each}
|
|
|
|
|
+ </select>
|
|
|
|
|
+ {#if estadosErro}
|
|
|
|
|
+ <p class="text-xs text-red-500 mt-1">{estadosErro}</p>
|
|
|
|
|
+ {/if}
|
|
|
|
|
+ {:else if isDeliveryCityField(field.key)}
|
|
|
|
|
+ <select
|
|
|
|
|
+ class="w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:cursor-not-allowed disabled:opacity-60"
|
|
|
|
|
+ on:change={(event) => handleDeliveryCityChange(event.currentTarget.value)}
|
|
|
|
|
+ bind:value={selectedCityValue}
|
|
|
|
|
+ required={requiredFields?.has(field.key)}
|
|
|
|
|
+ disabled={!selectedStateValue || !availableCities.length || !!estadosErro}
|
|
|
|
|
+ >
|
|
|
|
|
+ <option value="">
|
|
|
|
|
+ {#if !selectedStateValue}
|
|
|
|
|
+ Selecione um estado primeiro
|
|
|
|
|
+ {:else if estadosCarregando}
|
|
|
|
|
+ Carregando cidades...
|
|
|
|
|
+ {:else if !availableCities.length}
|
|
|
|
|
+ Nenhuma cidade disponível
|
|
|
|
|
+ {:else}
|
|
|
|
|
+ Selecione uma cidade
|
|
|
|
|
+ {/if}
|
|
|
|
|
+ </option>
|
|
|
|
|
+ {#each availableCities as cidade}
|
|
|
|
|
+ <option value={cidade}>{cidade}</option>
|
|
|
|
|
+ {/each}
|
|
|
|
|
+ </select>
|
|
|
|
|
+ {#if estadosErro}
|
|
|
|
|
+ <p class="text-xs text-red-500 mt-1">{estadosErro}</p>
|
|
|
|
|
+ {/if}
|
|
|
|
|
+ {:else if field.type === 'select'}
|
|
|
<select
|
|
<select
|
|
|
class="w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
class="w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
|
value={getValue(field.key)}
|
|
value={getValue(field.key)}
|