feat: i18n, language switcher fix, DataTable improvements, blade translations

- Translation system: lang/es/ PHP files (auth, validation, pagination, passwords)
- Rappasoft vendor translations published (lang/vendor/livewire-tables/es/)
- JSON files synced to 391 keys (EN + ES, full parity)
- APP_LOCALE changed to 'es', users.locale column default changed to 'es'
- Language switcher fixed: JS event + window.location.reload() avoids /livewire/update redirect
- SetLocale middleware fallback uses config('app.locale') instead of hardcoded 'en'
- setSortingPillsEnabled(false) on ProjectTable, CompanyTable, UserTable
- Translated 17 blade views: project-map, template-manager, layer-manager,
  company-management, phase-list, media-manager, reports-dashboard,
  client-projects, layer-upload, project-form, project-map-editor-tab,
  admin/users, projects/media, projects/templates, layouts/client
- Navigation 'Empresas' link uses __('Companies')
- Fixed typo key 'Fases and layers' -> 'Phases and layers'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 18:05:53 +02:00
parent 052e1397df
commit 7d854ffb0a
85 changed files with 8499 additions and 1339 deletions
+86 -52
View File
@@ -3,79 +3,113 @@
namespace App\Livewire;
use Livewire\Component;
use Livewire\Attributes\Layout;
use App\Models\Project;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
#[Layout('layouts.app')]
class ProjectForm extends Component
{
public $projectId = null;
public $name = '';
public $address = '';
public $lat = null;
public $lng = null;
public $country = '';
public $start_date = '';
public $end_date_estimated = '';
public $status = 'planning';
public ?Project $project = null;
protected $rules = [
'name' => 'required|string|max:255',
'address' => 'required|string',
'lat' => 'nullable|numeric',
'lng' => 'nullable|numeric',
'start_date' => 'required|date',
'end_date_estimated' => 'nullable|date',
'status' => 'required|in:planning,in_progress,paused,completed',
];
// Identification
public string $name = '';
public string $reference = '';
public string $status = 'planning';
public function mount($projectId = null)
// Location
public string $address = '';
public string $country = '';
public string $lat = '';
public string $lng = '';
// Planning
public string $startDate = '';
public string $endDateEstimated = '';
public function mount(?Project $project = null): void
{
if ($projectId) {
$this->projectId = $projectId;
$project = Project::findOrFail($projectId);
$this->name = $project->name;
$this->address = $project->address;
$this->lat = $project->lat;
$this->lng = $project->lng;
$this->start_date = $project->start_date->format('Y-m-d');
$this->end_date_estimated = $project->end_date_estimated?->format('Y-m-d');
$this->status = $project->status;
// country? we don't have stored, maybe we can leave blank or compute from lat/lng? We'll leave blank for now.
if ($project && $project->exists) {
Gate::authorize('edit projects', $project);
$this->project = $project;
$this->name = $project->name;
$this->reference = $project->reference ?? '';
$this->status = $project->status;
$this->address = $project->address;
$this->country = $project->country ?? '';
$this->lat = (string) ($project->lat ?? '');
$this->lng = (string) ($project->lng ?? '');
$this->startDate = $project->start_date->format('Y-m-d');
$this->endDateEstimated = $project->end_date_estimated?->format('Y-m-d') ?? '';
} else {
Gate::authorize('create projects');
$this->startDate = today()->format('Y-m-d');
}
}
public function setCoordinates($lat, $lng)
// Called from JS after map click / marker drag + reverse geocode
public function setLocation(string $lat, string $lng, string $address = '', string $country = ''): void
{
$this->lat = $lat;
$this->lng = $lng;
// Optionally, we could trigger reverse geocoding here via JS and update address and country.
// But we'll do that entirely in JavaScript for better UX.
// We'll emit an event to JS to fetch address.
$this->dispatch('coordinatesUpdated', lat: $lat, lng: $lng);
if ($address) $this->address = $address;
if ($country) $this->country = strtolower($country);
}
public function save()
protected function rules(): array
{
return [
'name' => 'required|string|max:255',
'reference' => 'nullable|string|max:100',
'status' => 'required|in:planning,in_progress,paused,completed',
'address' => 'required|string',
'country' => 'nullable|string|size:2',
'lat' => 'nullable|numeric|between:-90,90',
'lng' => 'nullable|numeric|between:-180,180',
'startDate' => 'required|date',
'endDateEstimated' => 'nullable|date|after_or_equal:startDate',
];
}
protected $validationAttributes = [
'name' => 'nombre',
'reference' => 'referencia',
'status' => 'estado',
'address' => 'dirección',
'country' => 'país',
'lat' => 'latitud',
'lng' => 'longitud',
'startDate' => 'fecha de inicio',
'endDateEstimated' => 'fecha de fin estimada',
];
public function save(): void
{
$this->validate();
if ($this->projectId) {
$project = Project::findOrFail($this->projectId);
$data = [
'name' => $this->name,
'reference' => $this->reference ?: null,
'status' => $this->status,
'address' => $this->address,
'country' => $this->country ?: null,
'lat' => $this->lat ?: null,
'lng' => $this->lng ?: null,
'start_date' => $this->startDate,
'end_date_estimated' => $this->endDateEstimated ?: null,
];
if ($this->project && $this->project->exists) {
$this->project->update($data);
session()->flash('notify', 'Proyecto actualizado correctamente.');
} else {
$project = new Project();
$project->created_by = auth()->id();
$project = Project::create(array_merge($data, ['created_by' => Auth::id()]));
$project->users()->attach(Auth::id(), ['role_in_project' => 'supervisor']);
session()->flash('notify', 'Proyecto creado correctamente.');
}
$project->name = $this->name;
$project->address = $this->address;
$project->lat = $this->lat;
$project->lng = $this->lng;
$project->start_date = $this->start_date;
$project->end_date_estimated = $this->end_date_estimated;
$project->status = $this->status;
$project->save();
session()->flash('message', 'Project saved successfully.');
return redirect()->route('projects.index');
$this->redirect(route('projects.index'), navigate: true);
}
public function render()