ADD EV3-358 statisztika modul bővítése aktuális ár megjelenítéssel phase 6 excel export function
This commit is contained in:
parent
4f11e59fce
commit
834f64a011
48
app/Exports/CurrentPriceExport.php
Normal file
48
app/Exports/CurrentPriceExport.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
||||
|
||||
class CurrentPriceExport implements FromCollection, ShouldAutoSize, WithHeadings, WithMapping
|
||||
{
|
||||
/**
|
||||
* @param Collection<int, array{supplier: ?string, producer: ?string, mainGroup: string, subGroup1: string, subGroup2: string, name: string, price: ?float}> $rows
|
||||
*/
|
||||
public function __construct(private Collection $rows) {}
|
||||
|
||||
public function collection(): Collection
|
||||
{
|
||||
return $this->rows;
|
||||
}
|
||||
|
||||
public function headings(): array
|
||||
{
|
||||
return [
|
||||
'Beszállító',
|
||||
'Gyártó',
|
||||
'Termék Főcsoport',
|
||||
'Termék alcsoport 1',
|
||||
'Termék alcsoport 2',
|
||||
'Termék megnevezése',
|
||||
'Aktuális ár',
|
||||
];
|
||||
}
|
||||
|
||||
public function map($row): array
|
||||
{
|
||||
return [
|
||||
$row['supplier'] ?? '',
|
||||
$row['producer'] ?? '',
|
||||
$row['mainGroup'] ?? '',
|
||||
$row['subGroup1'] ?? '',
|
||||
$row['subGroup2'] ?? '',
|
||||
$row['name'],
|
||||
$row['price'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Exports\CurrentPriceExport;
|
||||
use App\Models\Producer;
|
||||
use App\Models\Product;
|
||||
use App\Models\ProductGroup;
|
||||
@ -13,12 +14,18 @@
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Pages\Page;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
class CurrentPrice extends Page implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
/** @var int[] */
|
||||
public const PER_PAGE_OPTIONS = [10, 25, 50, 100];
|
||||
|
||||
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-currency-dollar';
|
||||
|
||||
protected static ?string $title = 'Aktuális ár';
|
||||
@ -41,6 +48,17 @@ public function getBreadcrumbs(): array
|
||||
|
||||
public ?array $data = [];
|
||||
|
||||
public bool $hasSearched = false;
|
||||
|
||||
public int $perPage = 10;
|
||||
|
||||
public int $page = 1;
|
||||
|
||||
public int $totalResults = 0;
|
||||
|
||||
/** @var array<int, array{supplier: ?string, producer: ?string, mainGroup: string, subGroup1: string, subGroup2: string, name: string, price: ?float}> */
|
||||
public array $results = [];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->form->fill();
|
||||
@ -157,9 +175,151 @@ public function form(Schema $form): Schema
|
||||
|
||||
public function submit(): void
|
||||
{
|
||||
$data = $this->form->getState();
|
||||
// Itt lehet majd feldolgozni a szűrést
|
||||
// dd($data);
|
||||
$this->data = $this->form->getState();
|
||||
$this->page = 1;
|
||||
$this->runSearch();
|
||||
}
|
||||
|
||||
public function updatedPerPage(): void
|
||||
{
|
||||
$this->page = 1;
|
||||
|
||||
if ($this->hasSearched) {
|
||||
$this->runSearch();
|
||||
}
|
||||
}
|
||||
|
||||
public function goToPage(int $page): void
|
||||
{
|
||||
$this->page = max(1, min($page, $this->lastPage()));
|
||||
$this->runSearch();
|
||||
}
|
||||
|
||||
public function lastPage(): int
|
||||
{
|
||||
return max(1, (int) ceil($this->totalResults / $this->perPage));
|
||||
}
|
||||
|
||||
public function exportExcel(): BinaryFileResponse
|
||||
{
|
||||
$products = $this->filteredResultsQuery()->orderBy('name')->get();
|
||||
$rows = $this->mapProductsToRows($products);
|
||||
|
||||
return Excel::download(new CurrentPriceExport($rows), 'aktualis-ar-'.now()->format('Y-m-d_His').'.xlsx');
|
||||
}
|
||||
|
||||
private function runSearch(): void
|
||||
{
|
||||
$query = $this->filteredResultsQuery();
|
||||
|
||||
$this->totalResults = (clone $query)->count();
|
||||
$this->page = min($this->page, $this->lastPage());
|
||||
|
||||
$products = $query->orderBy('name')
|
||||
->skip(($this->page - 1) * $this->perPage)
|
||||
->take($this->perPage)
|
||||
->get();
|
||||
|
||||
$this->results = $this->mapProductsToRows($products)->toArray();
|
||||
|
||||
$this->hasSearched = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A szűrő űrlap aktuális állapota alapján felépített, még nem lapozott/rendezett Product query.
|
||||
*/
|
||||
private function filteredResultsQuery(): Builder
|
||||
{
|
||||
$data = $this->data;
|
||||
|
||||
$query = Product::query()->with(['Supplier', 'Producer']);
|
||||
|
||||
if (filled($data['supplier'] ?? [])) {
|
||||
$query->whereIn('supplier_id', $data['supplier']);
|
||||
}
|
||||
|
||||
if (filled($data['productGroup'] ?? [])) {
|
||||
$query->whereIn('product_group_id', $data['productGroup']);
|
||||
}
|
||||
|
||||
if (filled($data['producer'] ?? [])) {
|
||||
$query->whereIn('producer_id', $data['producer']);
|
||||
}
|
||||
|
||||
if (filled($data['product'] ?? [])) {
|
||||
$query->whereIn('name', $data['product']);
|
||||
}
|
||||
|
||||
if (filled($data['hooreycaName'] ?? [])) {
|
||||
$query->whereIn('buyerProductName', $data['hooreycaName']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Termékekhez tartozó aktuális ár és termékcsoport-hierarchia feloldása, és a táblázat/export
|
||||
* soraivá alakítása. Egyetlen extra lekérdezéssel dolgozza fel a kapott terméklistát (nincs N+1).
|
||||
*
|
||||
* @return Collection<int, array{supplier: ?string, producer: ?string, mainGroup: string, subGroup1: string, subGroup2: string, name: string, price: ?float}>
|
||||
*/
|
||||
private function mapProductsToRows(Collection $products): Collection
|
||||
{
|
||||
$today = now()->toDateString();
|
||||
|
||||
$currentPrices = DB::table('price_list_prices as plp')
|
||||
->join('price_lists as pl', 'pl.id', '=', 'plp.price_list_id')
|
||||
->whereIn('plp.product_id', $products->pluck('id'))
|
||||
->where('pl.available', '<=', $today)
|
||||
->orderBy('pl.available', 'desc')
|
||||
->orderBy('pl.id', 'desc')
|
||||
->get(['plp.product_id', 'plp.price'])
|
||||
->groupBy('product_id')
|
||||
->map(fn ($rows) => (float) $rows->first()->price);
|
||||
|
||||
$groupPaths = $this->getProductGroupPaths($products->pluck('product_group_id'));
|
||||
|
||||
return $products->map(function (Product $product) use ($currentPrices, $groupPaths) {
|
||||
$path = $groupPaths->get($product->product_group_id, []);
|
||||
|
||||
return [
|
||||
'supplier' => $product->Supplier?->name,
|
||||
'producer' => $product->Producer?->name,
|
||||
'mainGroup' => $path[0] ?? '',
|
||||
'subGroup1' => $path[1] ?? '',
|
||||
'subGroup2' => $path[2] ?? '',
|
||||
'name' => $product->name,
|
||||
'price' => $currentPrices->get($product->id),
|
||||
];
|
||||
})->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* A termékcsoport-hierarchia első 3 szintje (Termék Főcsoport / alcsoport 1 / alcsoport 2)
|
||||
* termékcsoport id-nként, ugyanaz a logika, mint a legacy Árváltozás statisztikában
|
||||
* (ProductGroup::allWithPath() — a technikai gyökér csomópont nélkül).
|
||||
*
|
||||
* @param \Illuminate\Support\Collection<int, int|null> $productGroupIds
|
||||
* @return \Illuminate\Support\Collection<int, array<int, string>>
|
||||
*/
|
||||
private function getProductGroupPaths($productGroupIds): \Illuminate\Support\Collection
|
||||
{
|
||||
$ids = $productGroupIds->filter()->unique()->values();
|
||||
|
||||
if ($ids->isEmpty()) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return ProductGroup::whereIn('id', $ids)
|
||||
->with('ancestors')
|
||||
->get()
|
||||
->mapWithKeys(function (ProductGroup $group) {
|
||||
$names = $group->ancestors->pluck('name')->all();
|
||||
array_shift($names); // technikai gyökér ("Főcsoportok") elhagyása
|
||||
$names[] = $group->name;
|
||||
|
||||
return [$group->id => $names];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -78,6 +78,89 @@
|
||||
color: #453821;
|
||||
}
|
||||
|
||||
/* Aktuális ár találati táblázat */
|
||||
.result-table-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.result-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.result-table th,
|
||||
.result-table td {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #dee2e6;
|
||||
text-align: left;
|
||||
}
|
||||
.result-table thead th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.result-table tbody tr:nth-child(even) {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.result-table td.price-cell {
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.result-table-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
.per-page-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.per-page-select select {
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.result-pager {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.result-pager button {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
.result-pager button:hover:not(:disabled) {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.result-pager button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.result-pager-info {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.excel-export-button {
|
||||
padding: 6px 14px;
|
||||
border: 1px solid #1e7e34;
|
||||
border-radius: 4px;
|
||||
background-color: #28a745;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.excel-export-button:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
/* Aktív menüpont stílusa a sidebar-ban */
|
||||
.sidebar-menu ul li.active > a {
|
||||
color: #b8bfce;
|
||||
@ -128,11 +211,74 @@
|
||||
</div>
|
||||
|
||||
<div class="statistic-card">
|
||||
<div class="statistic-card-header">
|
||||
Aktuális ár statisztika
|
||||
<div class="statistic-card-header" style="display: flex; align-items: center; justify-content: space-between;">
|
||||
<span>Aktuális ár statisztika</span>
|
||||
@if ($hasSearched && ! empty($results))
|
||||
<button type="button" wire:click="exportExcel" class="excel-export-button">
|
||||
Excel export
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
<div class="statistic-card-body">
|
||||
<p>Ez a statisztikai elem még fejlesztés alatt áll.</p>
|
||||
@if (! $hasSearched)
|
||||
<p>Válassza ki a szűrési feltételeket, majd nyomja meg a "Szűrés alkalmazása" gombot.</p>
|
||||
@elseif (empty($results))
|
||||
<p>Nincs a szűrésnek megfelelő találat.</p>
|
||||
@else
|
||||
<div class="result-table-toolbar">
|
||||
<div>
|
||||
Összesen <strong>{{ $totalResults }}</strong> találat
|
||||
</div>
|
||||
<div class="per-page-select">
|
||||
<label for="perPage">Sorok oldalanként:</label>
|
||||
<select id="perPage" wire:model.live="perPage">
|
||||
@foreach ($this::PER_PAGE_OPTIONS as $option)
|
||||
<option value="{{ $option }}">{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-table-wrapper">
|
||||
<table class="result-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Beszállító</th>
|
||||
<th>Gyártó</th>
|
||||
<th>Termék Főcsoport</th>
|
||||
<th>Termék alcsoport 1</th>
|
||||
<th>Termék alcsoport 2</th>
|
||||
<th>Termék megnevezése</th>
|
||||
<th>Aktuális ár</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($results as $row)
|
||||
<tr>
|
||||
<td>{{ $row['supplier'] ?? '-' }}</td>
|
||||
<td>{{ $row['producer'] ?? '-' }}</td>
|
||||
<td>{{ $row['mainGroup'] ?: '-' }}</td>
|
||||
<td>{{ $row['subGroup1'] ?: '-' }}</td>
|
||||
<td>{{ $row['subGroup2'] ?: '-' }}</td>
|
||||
<td>{{ $row['name'] }}</td>
|
||||
<td class="price-cell">{{ $row['price'] !== null ? number_format($row['price'], 0, ',', ' ') . ' Ft' : '-' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@php $lastPage = $this->lastPage(); @endphp
|
||||
@if ($lastPage > 1)
|
||||
<div class="result-pager">
|
||||
<button type="button" wire:click="goToPage(1)" @disabled($page <= 1)>«</button>
|
||||
<button type="button" wire:click="goToPage({{ $page - 1 }})" @disabled($page <= 1)>‹ Előző</button>
|
||||
<span class="result-pager-info">{{ $page }}. / {{ $lastPage }} oldal</span>
|
||||
<button type="button" wire:click="goToPage({{ $page + 1 }})" @disabled($page >= $lastPage)>Következő ›</button>
|
||||
<button type="button" wire:click="goToPage({{ $lastPage }})" @disabled($page >= $lastPage)>»</button>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user