Files
javier 7d854ffb0a 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>
2026-06-16 18:05:53 +02:00

174 lines
9.3 KiB
PHP

<?php
namespace App\Livewire;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\Views\Filters\SelectFilter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use App\Models\Company;
class CompanyTable extends DataTableComponent
{
protected $model = Company::class;
public function configure(): void
{
$this->setPrimaryKey('id')
->setDefaultSort('name', 'asc')
->setSortingPillsEnabled(false)
->setAdditionalSelects([
'companies.id as id',
'companies.apodo as apodo',
'companies.tax_id as tax_id',
'companies.phone as phone',
'companies.email as email',
'companies.logo_path as logo_path',
'companies.created_at as created_at',
]);
}
public function builder(): Builder
{
return Company::withCount('projects');
}
public function columns(): array
{
return [
Column::make('Empresa', 'name')
->sortable()
->searchable()
->format(function ($value, $row) {
$logoHtml = '';
if ($row->logo_path && Storage::disk('public')->exists($row->logo_path)) {
$url = Storage::disk('public')->url($row->logo_path);
$logoHtml = '<img src="'.e($url).'" class="w-9 h-9 rounded object-contain border border-base-300 shrink-0" />';
} else {
$logoHtml = '<div class="w-9 h-9 rounded bg-base-200 flex items-center justify-center shrink-0 text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 opacity-40" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-2 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
</div>';
}
$html = '<div class="flex items-center gap-3">'.$logoHtml.'<div>';
$html .= '<p class="font-semibold text-sm leading-tight">'.e($value).'</p>';
if ($row->apodo) $html .= '<p class="text-xs text-gray-500">'.e($row->apodo).'</p>';
if ($row->tax_id) $html .= '<p class="text-xs text-gray-400">NIF: '.e($row->tax_id).'</p>';
$html .= '</div></div>';
return $html;
})
->html(),
Column::make('Tipo', 'type')
->sortable()
->format(function ($value) {
$map = [
'owner' => ['badge-success', 'Promotor'],
'constructor' => ['badge-primary', 'Constructor'],
'subcontractor' => ['badge-secondary', 'Subcontratista'],
'consultant' => ['badge-info', 'Consultor'],
'supplier' => ['badge-warning', 'Proveedor'],
];
[$cls, $label] = $map[$value] ?? ['badge-ghost', 'Otro'];
return '<span class="badge badge-sm '.$cls.'">'.$label.'</span>';
})
->html(),
Column::make('Contacto', 'phone')
->format(function ($value, $row) {
$html = '';
if ($row->phone) {
$html .= '<div class="flex items-center gap-1 text-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5 opacity-50 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/></svg>
'.e($row->phone).'</div>';
}
if ($row->email) {
$html .= '<div class="flex items-center gap-1 text-xs text-gray-500 max-w-[180px] truncate">
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5 opacity-50 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
'.e($row->email).'</div>';
}
return $html ?: '<span class="text-gray-300">—</span>';
})
->html(),
Column::make('Estado', 'estado')
->sortable()
->format(function ($value) {
$map = [
'activo' => ['badge-success', 'Activo'],
'inactivo' => ['badge-ghost', 'Inactivo'],
'suspendido' => ['badge-error', 'Suspendido'],
];
[$cls, $label] = $map[$value ?? 'activo'] ?? ['badge-ghost', ucfirst($value ?? 'activo')];
return '<span class="badge badge-sm '.$cls.'">'.$label.'</span>';
})
->html(),
Column::make('Proyectos')
->label(fn ($row) =>
'<span class="badge badge-outline badge-sm">'.(int)($row->projects_count ?? 0).'</span>'
)
->html(),
Column::make('Acciones')
->label(function ($row) {
$ver = route('companies.show', $row->id);
$editar = route('companies.edit', $row->id);
$name = addslashes($row->name);
$html = '<div class="flex items-center justify-end gap-1">';
$html .= '<a href="'.$ver.'" class="btn btn-xs btn-outline" title="Ver" wire:navigate>
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
</a>';
$html .= '<a href="'.$editar.'" class="btn btn-xs btn-outline btn-info" title="Editar" wire:navigate>
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
</a>';
$html .= '<button wire:click="deleteCompany('.$row->id.')"
wire:confirm="¿Eliminar \''.$name.'\'? Esta acción no se puede deshacer."
class="btn btn-xs btn-outline btn-error" title="Eliminar">
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" 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-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</button>';
$html .= '</div>';
return $html;
})
->html(),
];
}
public function filters(): array
{
return [
SelectFilter::make('Tipo', 'type')
->options([
'' => 'Tipo: todos',
'owner' => 'Promotor',
'constructor' => 'Constructor',
'subcontractor' => 'Subcontratista',
'consultant' => 'Consultor',
'supplier' => 'Proveedor',
'other' => 'Otro',
])
->filter(fn (Builder $query, string $value) => $query->where('type', $value)),
SelectFilter::make('Estado', 'estado')
->options([
'' => 'Estado: todos',
'activo' => 'Activo',
'inactivo' => 'Inactivo',
'suspendido' => 'Suspendido',
])
->filter(fn (Builder $query, string $value) => $query->where('estado', $value)),
];
}
public function deleteCompany(int $id): void
{
$company = Company::findOrFail($id);
if ($company->logo_path) {
Storage::disk('public')->delete($company->logo_path);
}
$company->delete();
}
}