Initial commit: Pireon Admin — headless admin panel and public API
Laravel 13 + Filament 5 admin panel and REST API for a non-profit developer collective, backed by the Pireon OAuth identity server. The public site is a separate, server-rendered application consuming /api/public/v1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
90
app/Filament/Support/TranslationStatusColumn.php
Normal file
90
app/Filament/Support/TranslationStatusColumn.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Support;
|
||||
|
||||
use App\Support\TranslationFreshness;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Shows at a glance which languages a record has been translated into.
|
||||
*
|
||||
* Each supported locale renders as a badge — solid when a translation exists,
|
||||
* muted when it is still missing — so an editor can spot untranslated content
|
||||
* without opening every record.
|
||||
*
|
||||
* A third state matters as much as the first two and is invisible everywhere
|
||||
* else: a translation that exists and has fallen behind the language it was
|
||||
* made from. Nothing else in the panel would ever mention it, because by every
|
||||
* other measure that record is finished.
|
||||
*/
|
||||
class TranslationStatusColumn extends TextColumn
|
||||
{
|
||||
/**
|
||||
* The translatable attribute whose presence stands in for "translated".
|
||||
* Defaults to the model's first translatable attribute.
|
||||
*/
|
||||
protected ?string $trackedAttribute = null;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->badge();
|
||||
$this->sortable(false);
|
||||
$this->searchable(false);
|
||||
|
||||
$this->state(function (Model $record): array {
|
||||
$attribute = $this->resolveTrackedAttribute($record);
|
||||
$translations = $record->getTranslations($attribute);
|
||||
$stale = TranslationFreshness::for($record);
|
||||
|
||||
$states = [];
|
||||
|
||||
foreach (array_keys(config('locales.supported')) as $locale) {
|
||||
if (blank($translations[$locale] ?? null)) {
|
||||
$states[] = strtolower($locale);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Translated, but the language it was translated from has
|
||||
// moved on since. The badge has to say something other than
|
||||
// "done", because as far as everything else is concerned this
|
||||
// record is finished.
|
||||
$states[] = isset($stale[$locale])
|
||||
? strtoupper($locale).'!'
|
||||
: strtoupper($locale);
|
||||
}
|
||||
|
||||
return $states;
|
||||
});
|
||||
|
||||
/*
|
||||
* Uppercase = translated, lowercase = missing, and a "!" for a
|
||||
* translation the source has since moved past. Colour carries the same
|
||||
* three states for anyone who does not notice the casing.
|
||||
*/
|
||||
$this->color(fn (string $state): string => match (true) {
|
||||
str_ends_with($state, '!') => 'warning',
|
||||
$state === strtoupper($state) => 'success',
|
||||
default => 'gray',
|
||||
});
|
||||
}
|
||||
|
||||
public function trackedAttribute(string $attribute): static
|
||||
{
|
||||
$this->trackedAttribute = $attribute;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function resolveTrackedAttribute(Model $record): string
|
||||
{
|
||||
return $this->trackedAttribute
|
||||
?? $record->getTranslatableAttributes()[0]
|
||||
?? 'title';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user