First Commit
This commit is contained in:
Executable
+242
@@ -0,0 +1,242 @@
|
||||
# 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>';
|
||||
});
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user