Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

islamnouman's avatar

query regarding adding multiple sessions and show them on a single page with filament

<?php

namespace App\Filament\Pages;

use App\Services\GameTypeService;
use App\Services\SessionService;
use Filament\Actions\Action;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Pages\Page;

class Games extends Page
{
    protected static ?string $navigationIcon = 'heroicon-o-document-text';

    protected static string $view = 'filament.pages.games';

    public $sessions = [];

    public function mount(SessionService $sessionService): void
    {
        $this->sessions = $sessionService->getActiveSessions();
    }

    protected function getHeaderActions(): array
    {
        return [
            Action::make('add_session')
                ->form([
                    TextInput::make('customer_name')
                        ->required()
                        ->maxLength(255),
                ])->action(function (array $data, SessionService $sessionService): void {
                    $sessionService->create(array_merge([
                        'start_time' => now()->toDateTimeString()
                    ], $data));
                    $this->sessions = $sessionService->getActiveSessions();
                }),
        ];
    }
}
<?php

namespace App\Services;

use App\Enums\GeneralSettingEnum;
use App\Models\GameTable;
use App\Models\GameType;
use App\Models\Item;
use App\Models\ItemSession;
use App\Models\Session;
use App\Models\SessionGame;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;

class SessionService
{
    public function __construct(
        public ItemService $itemService,
        public SettingService $settingService,

    ) {
    }

    public function getSessionGames(Model $session, array $data)
    {
        return SessionGame::query()
            ->where('session_id', $session->id)
            ->where('game_type_id', $data['game_type_id'])
            ->where('game_table_id', $data['game_table_id'])
            ->first();
    }

    public function getActiveSessions(): Collection|array
    {
        return $this->getActiveSessionsQuery()->get();
    }

    public function getActiveSession(int $sessionId, int $gameTableId): Model|Builder|null
    {
        return $this->getActiveSessionsQuery($gameTableId)->where('id', $sessionId)->first();
    }

    public function getActiveSessionsWithGamesGrouped(): Collection|array
    {
        $sessions = $this->getActiveSessions();
        foreach ($sessions as &$session) {
            $session->sessionGames = $session->sessionGames->groupBy('game_table_id');
        }
        return $sessions;
    }

    public function getActiveSessionsQuery(int $gameTableId = null): Builder
    {
        return Session::with([
            'sessionGames' => function ($query) use ($gameTableId) {
                if ($gameTableId) {
                    $query->where('game_table_id', $gameTableId);
                }
            },
            'sessionGames.gameType',
            'sessionGames.gameTable'
        ])->latest();
    }

    public function sessionGamesQuery($sessionId, $gameTableId): Builder
    {
        return SessionGame::query()->with('gameTable', 'gameType')
            ->where('session_id', $sessionId)
            ->where('game_table_id', $gameTableId);
    }

    public function create(array $sessionData)
    {
        return Session::create($sessionData);
    }

    public function addGame(Session $session, array $data): Model
    {
        $data['start_time'] = Carbon::now()->toDateTimeString();
        $this->endOngoingGame($session, GameTable::find($data['game_table_id']));
        return $session->sessionGames()->create($data);
    }

    public function endOngoingGame(Session $session, GameTable $gameTable): void
    {
        $ongoingGame = $session->activeSessionGames()->where('game_table_id', $gameTable->id)->first();
        if ($ongoingGame) {
            $this->endGame($ongoingGame);
        }
    }

    public function endGame(SessionGame $sessionGame): void
    {
        $sessionGame->sub_total = $sessionGame->gameType->price;
        $sessionGame->overtime_amount = $this->calculateOvertimeAmount($sessionGame);
        $sessionGame->total = $sessionGame->sub_total + $sessionGame->overtime_amount;
        $sessionGame->end_time = Carbon::now()->toDateTimeString();
        $sessionGame->save();
    }

    public function calculateOvertimeAmount(SessionGame $sessionGame): float|int
    {
        $perMinutePrice = $this->settingService->getByKey(GeneralSettingEnum::PER_MINUTE_PRICE);
        $duration = Carbon::make($sessionGame->start_time)->diffInMinutes($sessionGame->end_time);
        $overtime = $duration - $sessionGame->gameType->time_limit;
        return $overtime > 0 ? $overtime * $perMinutePrice : 0;
    }

    public function addOrUpdateGame(Session $session, array $data): Model
    {
        return $session->sessionGames()->updateOrCreate([
            'session_id' => $session->id,
            'game_type_id' => $data['game_type_id'],
            'game_table_id' => $data['game_table_id'],
        ], $data);
    }

    public function addItems(Session $session, array $sessionItems): void
    {
        $itemsToAttach = [];
        $items = $this->itemService->getItemByIds(array_column($sessionItems, 'item_id'))->pluck(
            'price',
            'id'
        )->toArray();

        $existingItems = $session->items;
        foreach ($sessionItems as $item) {
            $existingItem = $existingItems->firstWhere('id', $item['item_id']);

            $existingQuantity = $existingItem ? $existingItem->pivot->quantity : 0;
            $existingSubTotal = $existingItem ? $existingItem->pivot->sub_total : 0;
            $existingTotal = $existingItem ? $existingItem->pivot->total : 0;

            $itemsToAttach[$item['item_id']] = [
                'sub_total' => $items[$item['item_id']],
                'quantity' => $item['quantity'] + $existingQuantity,
                'total' => ($items[$item['item_id']] * $item['quantity']) + $existingTotal,
            ];
        }
        $session->items()->syncWithoutDetaching($itemsToAttach);
    }

    public function updateSessionItemQuantity(Session $session, ItemSession $itemSession, string $item_quantity): void
    {
        $itemArray = ItemSession::query()->findOrFail($itemSession->id)->pluck(
            'sub_total',
            'id'
        )->toArray();

        $itemToAttach = [];

        $itemToAttach[$itemSession->id] = [
            'sub_total' => $itemArray[$itemSession->id],
            'quantity' => $item_quantity,
            'total' => $itemArray[$itemSession->id] * $item_quantity,
        ];

        $session->items()->syncWithoutDetaching($itemToAttach);
    }

    public function sessionItemsQuery(Session $session)
    {
        return ItemSession::where('session_id', $session->id)->with('item');
    }
}
<?php

namespace App\Models;

use App\Models\Scopes\OrderByDescScope;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;

class Session extends Model
{
    use HasFactory;

    protected $fillable = [
        'table_id',
        'customer_name',
        'start_time',
        'end_time',
        'total',
    ];

    protected static function booted()
    {
        static::addGlobalScope(new OrderByDescScope());
    }

    public function items(): BelongsToMany
    {
        return $this->belongsToMany(Item::class)->withPivot('quantity', 'sub_total', 'total')->withTimestamps();
    }

    public function sessionGames(): HasMany
    {
        return $this->hasMany(SessionGame::class);
    }

    public function activeSessionGames(): HasMany
    {
        return $this->hasMany(SessionGame::class)->where('end_time', null);
    }

    public function sessionGamesGrouped()
    {
        return $this->sessionGames->groupBy('game_table_id');
    }
}
<?php

namespace App\Models;

use App\Models\Scopes\OrderByDescScope;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class SessionGame extends Model
{
    use HasFactory;

    protected $fillable = [
        'session_id',
        'game_type_id',
        'game_table_id',
        'start_time',
        'end_time',
        'sub_total',
        'overtime_amount',
        'total',
    ];

    protected $appends = [
        'duration',
    ];

    protected static function booted()
    {
        static::addGlobalScope(new OrderByDescScope());
    }

    public function duration(): Attribute
    {
        return new Attribute(
            get: fn () => Carbon::make($this->start_time)->diffInMinutes($this->end_time),
        );
    }

    public function session(): BelongsTo
    {
        return $this->belongsTo(Session::class);
    }

    public function gameType(): BelongsTo
    {
        return $this->belongsTo(GameType::class);
    }

    public function gameTable(): BelongsTo
    {
        return $this->belongsTo(GameTable::class);
    }
}
<x-filament-panels::page>
    <div class="grid">
        @foreach($sessions as $session)
            <div class="mt-6">
                <livewire:session :session="$session" :id="'session'.$session->id"  />
            </div>
        @endforeach
    </div>
</x-filament-panels::page>
<x-filament::section>
    <div class="grid gap-y-2 mt-6">
        <div class="grid gap-y-2">
            <div class="flex gap-x-2 items-center justify-end">
                {{ $this->addGame }}
                {{ $this->addItems }}
                {{ $this->viewItems }}
            </div>
        </div>
        <div class="flex items-center justify-between">
                    <span class="text-sm font-medium text-gray-500 dark:text-gray-400">
                        <strong>Customer Name:</strong> {{ $session->customer_name }}
                    </span>
            <span class="flex gap-x-2 text-sm font-medium text-gray-500 dark:text-gray-400">
                        <strong>Total Time:</strong> <livewire:timer :id="'totalSessionTime'.$session->id" :startTime="$session->start_time" />
                    </span>
        </div>
    </div>
    <hr>
    <div class="grid gap-y-2 mt-6">
        <div class="flex items-center gap-x-2 justify-center">
            @foreach($session->sessionGamesGrouped() as $gameTableId => $sessionGames)
                <livewire:session-games  :id="'sessionGame'.$gameTableId" :sessionId="$session->id" :gameTableId="$gameTableId" />
            @endforeach
        </div>
    </div>
    <x-filament-actions::modals />
</x-filament::section>
<x-filament::section class="w-full">
    <div class="grid gap-y-6">
        <div class="grid gap-y-1">
            <div class="flex items-center gap-x-2 justify-start">
                                <span class="text-sm font-medium text-gray-500 dark:text-gray-400">
                                    <strong>Table Number:</strong> {{ $this->activeSessionGame->gameTable->name }}
                                </span>
            </div>
            <div class="flex items-center gap-x-2 justify-start">
                                <span class="text-sm font-medium text-gray-500 dark:text-gray-400">
                                    <strong>Game Type:</strong> {{ $this->activeSessionGame->gameType->name }}
                                </span>
            </div>
        </div>

        <div class="flex justify-center text-3xl font-semibold tracking-tight text-gray-950 dark:text-white">
            <livewire:timer :id="'thisGameTime'.$activeSessionGame->id" :key="$activeSessionGame->id"  :startTime="$activeSessionGame->start_time" />
        </div>

        <div class="flex items-center gap-x-2 justify-center">
            {{ $this->addGame }}
        </div>
        <div class="flex items-center gap-x-2 justify-between">
                                <span class="flex gap-x-2 text-sm font-medium text-gray-500 dark:text-gray-400">
                                    <strong>Table Games:</strong> {{ $this->sessionGames->count() }} {{ $this->viewGames }}
                                </span>
            <span class="flex gap-x-2 text-sm font-medium text-gray-500 dark:text-gray-400">
                                    <strong>Total Time:</strong> <livewire:timer :id="'sessionGameTotalTime'.$sessionGames->first()->id" :key="$sessionGames->first()->id" :startTime="$sessionGames->first()->start_time" />
                                </span>
        </div>
    </div>
    <x-filament-actions::modals />
</x-filament::section>
<div id="{{ $id }}">

</div>

@push('scripts')
    <script>
        function updateLiveClock() {
            // Get the current time and calculate the time difference
            let now = new Date();
            let startTime = new Date("{{ $startTime }}");
            let timeDifference = now - startTime;

            // Calculate hours, minutes, and seconds
            let hours = Math.floor(timeDifference / (1000 * 60 * 60));
            let minutes = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60));
            let seconds = Math.floor((timeDifference % (1000 * 60)) / 1000);

            // Format the time
            let formattedTime = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;

            // Update the clock on the page
            document.getElementById("{{ $id }}").innerHTML = formattedTime;
        }

        // Update clock every second
        setInterval(updateLiveClock, 1000);

        // Initial update
        updateLiveClock();
    </script>
@endpush

gives error after clicking on add_session link after first session is created

livewire.js?id=c4077c56:4193 Uncaught Snapshot missing on Livewire component with id: G2zQhlTmAXzr51sKc2eB

how to resolve it any suggestions

0 likes
0 replies

Please or to participate in this conversation.