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
@@ -4,9 +4,9 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-500 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h10m-9-3h8m-7 0h7M8 13v2a2 2 0 002 2h5a2 2 0 002-2v-2m0 0V9a2 2 0 00-2-2H5a2 2 0 00-2 2v2Z" />
</svg>
Gestión de Empresas
{{ __('Company Management') }}
</h2>
<p class="text-gray-600 mt-2">Gestione las empresas que participan en los proyectos</p>
<p class="text-gray-600 mt-2">{{ __('Manage the companies that participate in projects') }}</p>
</div>
@if(session('message'))
@@ -22,7 +22,7 @@
<div class="w-full md:w-1/2">
<input type="text"
wire:model.live="search"
placeholder="Buscar empresas por nombre o NIF..."
placeholder="{{ __('Search companies by name or tax ID...') }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors">
</div>
<div class="w-full md:w-1/3 mt-4 md:mt-0">
@@ -31,7 +31,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
Nueva Empresa
{{ __('New Company') }}
</button>
</div>
</div>
@@ -47,16 +47,16 @@
class="bg-white rounded-lg shadow-md p-6">
<div class="mb-4">
<h3 class="text-xl font-semibold text-gray-800 flex items-center">
{{ $editingCompanyId ? 'Editar Empresa' : 'Crear Nueva Empresa' }}
{{ $editingCompanyId ? __('Edit Company') : __('New Company') }}
</h3>
<p class="text-gray-600 mt-1">
Complete la información de la empresa. Los campos marcados con * son obligatorios.
{{ __('Complete the company information. Fields marked with * are required.') }}
</p>
</div>
@if($errors->any())
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
<strong>Errores de validación:</strong>
<strong>{{ __('Validation errors') }}:</strong>
<ul class="list-disc pl-5 mt-2 text-sm text-red-600">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@@ -71,17 +71,17 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Nombre *</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('Name') }} *</label>
<input type="text"
wire:model="name"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">NIF/NIE/CIF *</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('Tax ID') }} *</label>
<input type="text"
wire:model="tax_id"
placeholder="Ej: B12345678"
placeholder="{{ __('E.g.: B12345678') }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors">
</div>
</div>
@@ -89,20 +89,20 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Apodo</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('Nickname') }}</label>
<input type="text"
wire:model="apodo"
placeholder="Ej: Acme Construct"
placeholder="{{ __('E.g.: Acme Construct') }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Estado *</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('Status') }} *</label>
<select wire:model="estado"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors">
<option value="">Seleccione un estado</option>
<option value="activo">Activo</option>
<option value="inactivo">Inactivo</option>
<option value="suspendido">Suspendido</option>
<option value="">{{ __('Select a status') }}</option>
<option value="activo">{{ __('Active') }}</option>
<option value="inactivo">{{ __('Inactive') }}</option>
<option value="suspendido">{{ __('Suspended') }}</option>
</select>
</div>
</div>
@@ -110,30 +110,30 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Dirección</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('Address') }}</label>
<textarea wire:model="address"
rows="3"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Tipo de Empresa *</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('Company Type') }} *</label>
<select wire:model="type"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors">
<option value="">Seleccione un tipo</option>
<option value="owner">Promotor/Propietario</option>
<option value="constructor">Constructor Principal</option>
<option value="subcontractor">Subcontratista</option>
<option value="consultant">Consultor/Ingeniería</option>
<option value="supplier">Proveedor</option>
<option value="other">Otro</option>
<option value="">{{ __('Select a type') }}</option>
<option value="owner">{{ __('Owner') }}</option>
<option value="constructor">{{ __('Constructor') }}</option>
<option value="subcontractor">{{ __('Subcontractor') }}</option>
<option value="consultant">{{ __('Consultant') }}</option>
<option value="supplier">{{ __('Supplier') }}</option>
<option value="other">{{ __('Other') }}</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Teléfono</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('Phone') }}</label>
<input type="tel"
wire:model="phone"
placeholder="+34 600 123 456"
@@ -141,31 +141,31 @@
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('Email') }}</label>
<input type="email"
wire:model="email"
placeholder="contacto@empresa.com"
placeholder="{{ __('contact@company.com') }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors">
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Sitio Web</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('Website') }}</label>
<input type="url"
wire:model="website"
placeholder="https://www.empresa.com"
placeholder="https://www.company.com"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Logo de la Empresa</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('Company Logo') }}</label>
<div class="flex flex-col">
<label class="cursor-pointer text-blue-600 hover:text-blue-800">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 inline" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16v-2a2 2 0 012-2h2a2 2 0 012 2v2m-4 0h.01M12 12a2 2 0 100-4 2 2 0 000 4zm4.5-6.75a.75.75 0 100-1.5.75.75 0 000 1.5zm0 0h.01M7 10h.01M14 10h.01M10.363 5.636a.75.75 0 10-1.06-1.06l-.47 1.242A12.038 12.038 0 0112 9.042c1.373 0 2.702.28 3.901.784l1.242-.47a.75.75 0 10-1.06-1.06l-.469-1.241a9.038 9.038 0 00-2.342-.348z" />
</svg>
Seleccionar archivo...
{{ __('Select file...') }}
</label>
<input type="file"
wire:model="logo"
@@ -173,13 +173,13 @@
class="mt-2 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100">
@if($logo)
<div class="mt-3 flex items-center">
<img src="{{ $logo->temporaryUrl() }}"
alt="Vista previa del logo"
<img src="{{ $logo->temporaryUrl() }}"
alt="{{ __('Logo preview') }}"
class="h-12 w-12 object-contain border border-gray-200 rounded-lg">
<button type="button"
wire:click="logo = null"
class="ml-3 text-xs text-red-600 hover:text-red-800">
Eliminar
{{ __('Remove') }}
</button>
</div>
@endif
@@ -188,7 +188,7 @@
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Notas Adicionales</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('Additional notes') }}</label>
<textarea wire:model="notes"
rows="4"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"></textarea>
@@ -198,11 +198,11 @@
<button type="button"
wire:click="resetForm"
class="px-5 py-3 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition-colors">
Cancelar
{{ __('Cancel') }}
</button>
<button type="submit"
class="px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-lg transition-colors">
{{$editingCompanyId ? 'Actualizar Empresa' : 'Crear Empresa'}}
{{ $editingCompanyId ? __('Update') : __('Create') }}
</button>
</div>
</form>
@@ -216,7 +216,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2z" />
</svg>
Lista de Empresas ({{ $companies->count() }})
{{ __('Company list') }} ({{ $companies->count() }})
</h3>
</div>
@@ -225,7 +225,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-gray-300 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2z" />
</svg>
<p class="mt-2">No hay empresas registradas. Cree su primera empresa usando el botón de arriba.</p>
<p class="mt-2">{{ __('No companies registered. Create your first company using the button above.') }}</p>
</div>
@else
<div class="divide-y divide-gray-200">
@@ -235,7 +235,7 @@
<div class="flex items-start space-x-3">
@if($company->logo_path && Storage::disk('public')->exists($company->logo_path))
<img src="{{ Storage::disk('public')->url($company->logo_path) }}"
alt="Logo de {{ $company->name }}"
alt="{{ __('Logo of') }} {{ $company->name }}"
class="h-12 w-12 object-contain border border-gray-200 rounded-lg flex-shrink-0">
@else
<div class="h-12 w-12 flex items-center justify-center bg-gray-100 rounded-lg text-gray-400 flex-shrink-0">
@@ -250,11 +250,11 @@
@if($company->tax_id)
{{ $company->tax_id }}
@else
Sin NIF/CIF
{{ __('No tax ID') }}
@endif
</p>
@if($company->type)
<span class="inline-block mt-1 px-2 py-0.5 text-xs font-medium
<span class="inline-block mt-1 px-2 py-0.5 text-xs font-medium
@if($company->type === 'owner') bg-green-100 text-green-800
@elseif($company->type === 'constructor') bg-blue-100 text-blue-800
@elseif($company->type === 'subcontractor') bg-purple-100 text-purple-800
@@ -304,15 +304,15 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg>
Editar
{{ __('Edit') }}
</button>
<button wire:click="deleteCompany({{ $company->id }})"
class="text-sm text-red-600 hover:text-red-800 font-medium flex items-center"
onclick="return confirm('¿Está seguro de que desea eliminar esta empresa? Esta acción no se puede deshacer.')">
wire:confirm="{{ __('Delete company confirmation') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10h-3a2 2 0 00-2 2v2a2 2 0 002 2h3zm-3-4h1a2 2 0 012 2v2a2 2 0 01-2 2h-1V9a2 2 0 012-2z" />
</svg>
Eliminar
{{ __('Delete') }}
</button>
</div>
</div>
@@ -324,4 +324,4 @@
@endif
</div>
</div>
</div>
</div>