F
Filament5mo ago
DrByte

Tracking Read/Unread or Viewed/NotViewed

This is a how-to, not a help-request: Tracking Views of a record, per user. views migration:
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('views', function (Blueprint $table) {
$table->id();
$table->foreignUlid('user_id')->nullable()->constrained()->cascadeOnDelete()->cascadeOnUpdate();
$table->foreignUlid('post_id')->nullable()->constrained()->cascadeOnDelete()->cascadeOnUpdate();
$table->timestamps();
});
}
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('views', function (Blueprint $table) {
$table->id();
$table->foreignUlid('user_id')->nullable()->constrained()->cascadeOnDelete()->cascadeOnUpdate();
$table->foreignUlid('post_id')->nullable()->constrained()->cascadeOnDelete()->cascadeOnUpdate();
$table->timestamps();
});
}
View model:
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class View extends Model
{
use HasFactory;

protected $fillable = [
'post_id',
'user_id',
];

public function post(): BelongsTo
{
return $this->belongsTo(Post::class, 'post_id');
}

public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
}
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class View extends Model
{
use HasFactory;

protected $fillable = [
'post_id',
'user_id',
];

public function post(): BelongsTo
{
return $this->belongsTo(Post::class, 'post_id');
}

public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
}
User model:
public function views(): HasMany
{
return $this->hasMany(View::class, 'user_id', 'id');
}
public function views(): HasMany
{
return $this->hasMany(View::class, 'user_id', 'id');
}
Post model:
public function trackView(?User $user): void
{
$user_id = $user?->id ?? Auth::user()->id;
View::create([
'user_id' => $user_id,
'post_id' => $this->id,
]);
}

public function getIsViewedAttribute(?User $user)
{
$user_id = $user?->id ?? Auth::user()->id;
return $this->views->where('user_id', $user_id)->count();
}
public function trackView(?User $user): void
{
$user_id = $user?->id ?? Auth::user()->id;
View::create([
'user_id' => $user_id,
'post_id' => $this->id,
]);
}

public function getIsViewedAttribute(?User $user)
{
$user_id = $user?->id ?? Auth::user()->id;
return $this->views->where('user_id', $user_id)->count();
}
Then, for the component that "shows" the record, set its mountUsing() to track the "view":
->mountUsing(function (Post $record) {
$record->trackView(Auth::user());
})
->mountUsing(function (Post $record) {
$record->trackView(Auth::user());
})
CSS can be used to set a class when the isViewed() attribute is truthy.
4 Replies
DrByte
DrByte5mo ago
So, for example combining both of these view indicators and tracking open-clicks on a Table that uses a Split/Grid (to show Cards instead of rows), define the $table with
->recordClasses(fn (Post $record): string => $record->isViewed ? 'normalBorder' : 'unreadBorder')
->action([
Tables\Actions\ViewAction::make()
//..
->mountUsing(function (Post $record) {
$record->trackView(Auth::user());
})
])
->recordClasses(fn (Post $record): string => $record->isViewed ? 'normalBorder' : 'unreadBorder')
->action([
Tables\Actions\ViewAction::make()
//..
->mountUsing(function (Post $record) {
$record->trackView(Auth::user());
})
])
And to register the css: AppServiceProvider.php
FilamentView::registerRenderHook(
'panels::styles.after',
static fn (): View => view('custom-css'),
);
FilamentView::registerRenderHook(
'panels::styles.after',
static fn (): View => view('custom-css'),
);
/resources/views/custom-css.blade.php:
.unreadBorder {
border-color: rgba(var(--info-200),var(--tw-bg-opacity));
border-width: 4px;
}
.unreadBorder {
border-color: rgba(var(--info-200),var(--tw-bg-opacity));
border-width: 4px;
}
@Jocka ^ The code above doesn't show how to mark it as "unread"/"unviewed", but you could toggle it from another action.
Jocka
Jocka5mo ago
Thank you so much i already did an implementation which i could also send to you
DrByte
DrByte5mo ago
Sure, I always like seeing the solutions others come up with for related problems!
justlasse
justlasse5mo ago
Why not make it a relationship and use that to create views $this->views()->create()