243 lines
9.2 KiB
Markdown
Executable File
243 lines
9.2 KiB
Markdown
Executable File
# Integração com Módulo GlobalLists (Endereços)
|
|
|
|
Todos os módulos que utilizam formulários com campos de endereço (País, Estado, Cidade) DEVEM obrigatoriamente utilizar as funcionalidades do módulo `globallists` para padronização e consistência dos dados.
|
|
|
|
Esta regra visa eliminar a duplicação de tabelas de localidades e garantir uma experiência unificada de seleção de endereços em todo o sistema.
|
|
|
|
## 1. Banco de Dados
|
|
|
|
### Migração
|
|
As tabelas do módulo devem possuir as colunas `_country_id`, `_state_id` e `_city_id` (prefixadas com o identificador da tabela do módulo) configuradas como `unsignedInteger` e `nullable`.
|
|
|
|
É **obrigatório** o uso de chaves estrangeiras (Foreign Keys) apontando para as tabelas `lists_countries`, `lists_states` e `lists_cities`.
|
|
|
|
Exemplo (supondo um módulo com prefixo `events`):
|
|
```php
|
|
// Adição das colunas
|
|
$table->unsignedInteger('events_country_id')->nullable();
|
|
$table->unsignedInteger('events_state_id')->nullable();
|
|
$table->unsignedInteger('events_city_id')->nullable();
|
|
|
|
// Definição das Foreign Keys
|
|
$table->foreign('events_country_id')->references('countries_id')->on('lists_countries')->onDelete('set null');
|
|
$table->foreign('events_state_id')->references('states_id')->on('lists_states')->onDelete('set null');
|
|
$table->foreign('events_city_id')->references('cities_id')->on('lists_cities')->onDelete('set null');
|
|
```
|
|
|
|
## 2. Model
|
|
|
|
No Model do seu módulo, adicione os relacionamentos `belongsTo` e inclua as novas colunas no array `$fillable`.
|
|
|
|
```php
|
|
use Modules\GlobalLists\Models\Country;
|
|
use Modules\GlobalLists\Models\State;
|
|
use Modules\GlobalLists\Models\City;
|
|
|
|
class Event extends Model
|
|
{
|
|
protected $fillable = [
|
|
// ... outros campos
|
|
'events_country_id',
|
|
'events_state_id',
|
|
'events_city_id',
|
|
];
|
|
|
|
public function country()
|
|
{
|
|
return $this->belongsTo(Country::class, 'events_country_id', 'countries_id');
|
|
}
|
|
|
|
public function state()
|
|
{
|
|
return $this->belongsTo(State::class, 'events_state_id', 'states_id');
|
|
}
|
|
|
|
public function city()
|
|
{
|
|
return $this->belongsTo(City::class, 'events_city_id', 'cities_id');
|
|
}
|
|
}
|
|
```
|
|
|
|
## 3. Controller
|
|
|
|
Nos métodos responsáveis por exibir o formulário (`create`, `edit`), busque a lista completa de países. **IMPORTANTE**: Use o método `sortBy` com o acessor `name` para garantir que os países sejam listados em ordem alfabética de acordo com o idioma do sistema (o campo `countries_name` é um JSON).
|
|
|
|
```php
|
|
use Modules\GlobalLists\Models\Country;
|
|
use Modules\GlobalLists\Models\State;
|
|
use Modules\GlobalLists\Models\City;
|
|
|
|
public function create(): Response
|
|
{
|
|
// Ordenação correta por idioma
|
|
$countries = Country::all()->sortBy(function ($country) {
|
|
return $country->name;
|
|
});
|
|
|
|
return $this->renderAdminView('@seumodulo/form.twig', [
|
|
'countries' => $countries,
|
|
// ... outros dados
|
|
]);
|
|
}
|
|
|
|
public function edit($id): Response
|
|
{
|
|
$item = SeuModelo::find($id);
|
|
|
|
$countries = Country::all()->sortBy(function ($country) {
|
|
return $country->name;
|
|
});
|
|
|
|
// Pre-carregar estados e cidades se já houver seleção
|
|
$states = [];
|
|
$cities = [];
|
|
if ($item->prefixo_country_id) {
|
|
$states = State::where('states_country_id', $item->prefixo_country_id)->orderBy('states_name')->get();
|
|
}
|
|
if ($item->prefixo_state_id) {
|
|
$cities = City::where('cities_state_id', $item->prefixo_state_id)->orderBy('cities_name')->get();
|
|
}
|
|
|
|
return $this->renderAdminView('@seumodulo/form.twig', [
|
|
'item' => $item,
|
|
'countries' => $countries,
|
|
'states' => $states,
|
|
'cities' => $cities
|
|
]);
|
|
}
|
|
```
|
|
|
|
## 4. View (Twig)
|
|
|
|
Utilize a estrutura HTML abaixo para os selects. Substitua os IDs e Names conforme o seu módulo. Observe o uso de `country.name` para obter o nome traduzido.
|
|
|
|
```twig
|
|
<div class="row">
|
|
<!-- País -->
|
|
<div class="col-md-4 mb-3">
|
|
<label for="events_country_id" class="form-label">País</label>
|
|
<select class="form-select" id="events_country_id" name="events_country_id" onchange="loadStates(this.value)" required>
|
|
<option value="">Selecione...</option>
|
|
{% for country in countries %}
|
|
<option value="{{ country.countries_id }}" {{ item.events_country_id == country.countries_id ? 'selected' : '' }}>
|
|
{{ country.name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Estado -->
|
|
<div class="col-md-4 mb-3">
|
|
<label for="events_state_id" class="form-label">Estado</label>
|
|
<select class="form-select" id="events_state_id" name="events_state_id" onchange="loadCities(this.value)" required {{ not item.events_country_id ? 'disabled' : '' }}>
|
|
<option value="">Selecione...</option>
|
|
{% for state in states %}
|
|
<option value="{{ state.states_id }}" {{ item.events_state_id == state.states_id ? 'selected' : '' }}>
|
|
{{ state.states_name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Cidade -->
|
|
<div class="col-md-4 mb-3">
|
|
<label for="events_city_id" class="form-label">Cidade</label>
|
|
<select class="form-select" id="events_city_id" name="events_city_id" required {{ not item.events_state_id ? 'disabled' : '' }}>
|
|
<option value="">Selecione...</option>
|
|
{% for city in cities %}
|
|
<option value="{{ city.cities_id }}" {{ item.events_city_id == city.cities_id ? 'selected' : '' }}>
|
|
{{ city.cities_name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
## 5. JavaScript (Vanilla JS Obrigatório)
|
|
|
|
Utilize **EXCLUSIVAMENTE JavaScript Puro** para esta lógica. O uso de **Alpine.js** é estritamente proibido para os selects de localização do GlobalLists devido a conflitos de reatividade e performance.
|
|
|
|
As rotas da API `/api/globallists/states/{countryId}` e `/api/globallists/cities/{stateId}` já retornam os dados formatados corretamente.
|
|
|
|
```javascript
|
|
// Defina os valores salvos (caso seja edição)
|
|
const savedStateId = "{{ item.events_state_id }}";
|
|
const savedCityId = "{{ item.events_city_id }}";
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const countryId = document.getElementById('events_country_id').value;
|
|
if (countryId) {
|
|
// Carrega os estados e, em seguida, as cidades se houver um estado salvo
|
|
loadStates(countryId, savedStateId);
|
|
}
|
|
});
|
|
|
|
function loadStates(countryId, selectedStateId = null) {
|
|
const stateSelect = document.getElementById('events_state_id');
|
|
const citySelect = document.getElementById('events_city_id');
|
|
|
|
// Reset e estado de loading
|
|
stateSelect.innerHTML = '<option value="">Carregando...</option>';
|
|
stateSelect.disabled = true;
|
|
|
|
citySelect.innerHTML = '<option value="">Selecione o estado primeiro...</option>';
|
|
citySelect.disabled = true;
|
|
|
|
if (!countryId) {
|
|
stateSelect.innerHTML = '<option value="">Selecione o país primeiro...</option>';
|
|
return;
|
|
}
|
|
|
|
fetch(`/api/globallists/states/${countryId}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
stateSelect.innerHTML = '<option value="">Selecione...</option>';
|
|
data.forEach(state => {
|
|
// A API retorna {id, name}
|
|
const selected = selectedStateId && selectedStateId == state.id ? 'selected' : '';
|
|
stateSelect.innerHTML += `<option value="${state.id}" ${selected}>${state.name}</option>`;
|
|
});
|
|
stateSelect.disabled = false;
|
|
|
|
// Se houve um estado pré-selecionado, carrega as cidades
|
|
if (selectedStateId) {
|
|
loadCities(selectedStateId, savedCityId);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading states:', error);
|
|
stateSelect.innerHTML = '<option value="">Erro ao carregar</option>';
|
|
});
|
|
}
|
|
|
|
function loadCities(stateId, selectedCityId = null) {
|
|
const citySelect = document.getElementById('events_city_id');
|
|
|
|
citySelect.innerHTML = '<option value="">Carregando...</option>';
|
|
citySelect.disabled = true;
|
|
|
|
if (!stateId) {
|
|
citySelect.innerHTML = '<option value="">Selecione o estado primeiro...</option>';
|
|
return;
|
|
}
|
|
|
|
fetch(`/api/globallists/cities/${stateId}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
citySelect.innerHTML = '<option value="">Selecione...</option>';
|
|
data.forEach(city => {
|
|
// A API retorna {id, name}
|
|
const selected = selectedCityId && selectedCityId == city.id ? 'selected' : '';
|
|
citySelect.innerHTML += `<option value="${city.id}" ${selected}>${city.name}</option>`;
|
|
});
|
|
citySelect.disabled = false;
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading cities:', error);
|
|
citySelect.innerHTML = '<option value="">Erro ao carregar</option>';
|
|
});
|
|
}
|
|
```
|