d2d.emegrendeles.hu/app/Filament/Resources/PricelistFiles/RelationManagers/LinesRelationManager.php
2026-03-14 07:39:38 +01:00

284 lines
14 KiB
PHP

<?php
namespace App\Filament\Resources\PricelistFiles\RelationManagers;
use App\Enums\PricelistFileLineStatusEnum;
use App\Enums\PricelistFileStatusEnum;
use App\Services\PriceListService;
use Filament\Actions\ViewAction;
use Filament\Infolists\Components\TextEntry;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Components\EmbeddedTable;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\RenderHook;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Text;
use Filament\Schemas\Schema;
use Filament\Support\Facades\FilamentView;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Filament\Tables\View\TablesRenderHook;
use Filament\View\PanelsRenderHook;
use Filament\Support\Enums\Width;
use Illuminate\Support\HtmlString;
use Filament\Schemas\Components\Tabs\Tab;
use Illuminate\Database\Eloquent\Builder;
class LinesRelationManager extends RelationManager
{
protected static string $relationship = 'lines';
protected static ?string $title = 'Feldolgozott sorok';
public function booted()
{
FilamentView::registerRenderHook(
TablesRenderHook::TOOLBAR_START,
fn () => $this->getTabsContentComponent()->container($this->makeSchema())->toHtml(),
scopes: static::class,
);
}
public function content(Schema $schema): Schema
{
return $schema
->components([
RenderHook::make(PanelsRenderHook::RESOURCE_RELATION_MANAGER_BEFORE),
EmbeddedTable::make(),
RenderHook::make(PanelsRenderHook::RESOURCE_RELATION_MANAGER_AFTER),
]);
}
public function getTabs(): array
{
$ownerRecord = $this->getOwnerRecord();
$counts = $ownerRecord->lines()
->selectRaw('status, count(*) as count')
->groupBy('status')
->pluck('count', 'status');
return [
'all' => Tab::make('Mind')
->badge($counts->sum()),
'hibas' => Tab::make('Hibás')
->icon(PricelistFileLineStatusEnum::error->icon())
->badgeColor(PricelistFileLineStatusEnum::error->color())
->badge($counts->get(PricelistFileLineStatusEnum::error->value, 0))
->modifyQueryUsing(fn (Builder $query) => $query->where('status', PricelistFileLineStatusEnum::error)),
'warning' => Tab::make('Figyelmeztetések')
->icon(PricelistFileLineStatusEnum::warning->icon())
->badgeColor(PricelistFileLineStatusEnum::warning->color())
->badge($counts->get(PricelistFileLineStatusEnum::warning->value, 0))
->modifyQueryUsing(fn (Builder $query) => $query->where('status', PricelistFileLineStatusEnum::warning)),
'uj' => Tab::make('Új')
->icon(PricelistFileLineStatusEnum::new_product->icon())
->badgeColor(PricelistFileLineStatusEnum::new_product->color())
->badge($counts->get(PricelistFileLineStatusEnum::new_product->value, 0))
->modifyQueryUsing(fn (Builder $query) => $query->where('status', PricelistFileLineStatusEnum::new_product)),
'modosult' => Tab::make('Módosult')
->icon(PricelistFileLineStatusEnum::updated->icon())
->badgeColor(PricelistFileLineStatusEnum::updated->color())
->badge($counts->get(PricelistFileLineStatusEnum::updated->value, 0))
->modifyQueryUsing(fn (Builder $query) => $query->where('status', PricelistFileLineStatusEnum::updated)),
];
}
public function form(Schema $schema): Schema
{
return $schema
->components([
Grid::make(1)
->schema([
Section::make('Módosulások')
->schema([
Text::make(fn ($record) => new HtmlString(
'<div class="grid grid-cols-1 gap-4">' .
collect($record->diff ?? [])->map(function ($d, $field) {
$old = $d['old_label'] ?? $d['old'] ?? 'N/A';
$new = $d['new_label'] ?? $d['new'] ?? 'N/A';
$label = $d['label'] ?? $field;
if ($old === '' || $old === null) $old = '<i>(üres)</i>';
if ($new === '' || $new === null) $new = '<i>(üres)</i>';
// Ár formázása ha árról van szó
if ($field === 'price') {
$old = number_format((float)($d['old'] ?? 0), 2, ',', ' ') . ' Ft';
$new = number_format((float)($d['new'] ?? 0), 2, ',', ' ') . ' Ft';
}
return "<div class='text-sm border-l-4 border-info-500 pl-3 py-1 bg-info-50/50 dark:bg-info-950/20'>
<div class='font-bold text-gray-700 dark:text-gray-300'>$label</div>
<div class='flex items-center gap-2 mt-1'>
<span class='text-gray-500 line-through'>$old</span>
<span class='text-gray-400'>→</span>
<span class='text-info-600 font-bold'>$new</span>
</div>
</div>";
})->join('') .
'</div>'
)),
])
->visible(fn ($record) => !empty($record->diff)),
Section::make('Validációs üzenetek')
->schema([
Text::make(fn ($record) => new HtmlString(
'<ul class="list-disc ml-5">' .
collect($record->validation_messages ?? [])->map(fn ($msg) => "<li>$msg</li>")->join('') .
'</ul>'
)),
])
->visible(fn ($record) => !empty($record->validation_messages)),
])
->columnSpan(1)
->visible(fn ($record) => !empty($record->diff) || !empty($record->validation_messages)),
Section::make('Adatok')
->schema([
Text::make(fn ($record) => new HtmlString(
'
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
<thead class="bg-gray-50 dark:bg-white/5">
<tr>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-1/3">Oszlop</th>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Érték</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-transparent divide-y divide-gray-200 dark:divide-white/10">
' . collect($record->payload ?? [])->map(function ($value, $key) {
$valStr = is_array($value) ? json_encode($value) : (string)$value;
return '<tr>
<td class="px-3 py-2 text-sm text-gray-500 dark:text-gray-400 font-medium bg-gray-50/50 dark:bg-white/5">' . e($key) . '</td>
<td class="px-3 py-2 text-sm text-gray-900 dark:text-gray-200 whitespace-normal break-words">' . e($valStr) . '</td>
</tr>';
})->join('') . '
</tbody>
</table>
'
)),
])
->columnSpan(fn ($record) => (empty($record->diff) && empty($record->validation_messages)) ? 2 : 1),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('id')
->columns([
TextColumn::make('row_number')
->label('#')
->sortable()
->width('50px'),
TextColumn::make('status')
->label('Státusz')
->badge()
->color(fn (PricelistFileLineStatusEnum $state) => $state->color())
->formatStateUsing(fn (PricelistFileLineStatusEnum $state) => $state->label())
->icon(fn (PricelistFileLineStatusEnum $state) => $state->icon())
->sortable(),
TextColumn::make('product_info')
->label('Termék')
->description(fn ($record) => $record->payload[PriceListService::EXPECTED_HEADERS[4]] ?? '')
->getStateUsing(fn ($record) => $record->payload[PriceListService::EXPECTED_HEADERS[0]] ?? 'N/A')
->color(fn ($record) => !empty($record->diff) ? 'info' : null)
->wrap()
->searchable(query: function ($query, string $search) {
$sku = PriceListService::EXPECTED_HEADERS[0];
$name = PriceListService::EXPECTED_HEADERS[4];
$query->where("payload->$sku", 'like', "%{$search}%")
->orWhere("payload->$name", 'like', "%{$search}%");
}),
TextColumn::make('price_diff')
->label('Árváltozás')
->html()
->getStateUsing(function ($record) {
// Biztonságos elérés data_get-tel, mert a diff lehet null
$oldPrice = data_get($record->diff, 'price.old');
// Az új árat a diff-ből vesszük (már float), vagy a payload-ból (string lehet)
$newPriceRaw = data_get($record->diff, 'price.new') ?? data_get($record->payload, PriceListService::EXPECTED_HEADERS[14]);
// Normalizálás: szóközök eltávolítása és tizedesvessző cseréje
$newPrice = is_string($newPriceRaw)
? (float) str_replace([' ', ','], ['', '.'], $newPriceRaw)
: (float) $newPriceRaw;
// Ha nincs régi ár, vagy az nulla, akkor nincs százalékos számítás
if (empty($oldPrice) || (float)$oldPrice === 0.0) {
return number_format($newPrice, 2, ',', ' ') . ' Ft';
}
$oldPrice = (float) $oldPrice;
$diffPercent = (($newPrice / $oldPrice) - 1) * 100;
$colorClass = $diffPercent > 0 ? 'text-danger-600' : ($diffPercent < 0 ? 'text-success-600' : 'text-gray-600');
$icon = $diffPercent > 0 ? '↑' : ($diffPercent < 0 ? '↓' : '');
return sprintf(
'<div class="flex flex-col">
<span class="text-xs text-gray-500 line-through">%s Ft</span>
<span class="font-bold">%s Ft <span class="%s">%s %s%%</span></span>
</div>',
number_format($oldPrice, 2, ',', ' '),
number_format($newPrice, 2, ',', ' '),
$colorClass,
$icon,
number_format(abs($diffPercent), 1, ',', ' ')
);
})
->visible(fn ($livewire) => $livewire->getOwnerRecord()->status !== PricelistFileStatusEnum::todo),
TextColumn::make('validation_messages')
->label('Megjegyzés')
->listWithLineBreaks()
->bulleted()
->wrap()
->color(fn ($state) => collect($state)->contains(fn ($msg) => str_contains($msg, 'Hiányzó') || str_contains($msg, 'Ismeretlen') || str_contains($msg, 'Érvénytelen')) ? 'danger' : 'info')
->getStateUsing(function ($record) {
$messages = collect($record->validation_messages ?? [])->values();
$diffMessages = collect($record->diff ?? [])->map(function ($d, $field) {
$label = $d['label'] ?? $field;
$old = $d['old_label'] ?? $d['old'] ?? 'N/A';
$new = $d['new_label'] ?? $d['new'] ?? 'N/A';
if ($field === 'price') {
$old = number_format((float)($d['old'] ?? 0), 0, ',', ' ') . ' Ft';
$new = number_format((float)($d['new'] ?? 0), 0, ',', ' ') . ' Ft';
}
return "Módosult $label: $old -> $new";
})->values();
return $messages->merge($diffMessages)->toArray();
}),
])
->filters([
//
])
->headerActions([
//
])
->actions([
ViewAction::make()
->slideOver()
->modalWidth(Width::FourExtraLarge)
->iconButton()
->modalHeading(fn ($record) => "Sor száma: #{$record->row_number}")
->modalDescription(fn ($record) => "Státusz: " . $record->status->label()),
])
->bulkActions([
//
])
->defaultSort('row_number', 'asc')
->striped()
->poll('5s');
}
}