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:
98
app/Filament/RelationManagers/ReviewsRelationManager.php
Normal file
98
app/Filament/RelationManagers/ReviewsRelationManager.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\RelationManagers;
|
||||
|
||||
use App\Filament\Support\ReviewActions;
|
||||
use App\Models\ReviewComment;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* The conversation about one record, on the record.
|
||||
*
|
||||
* Sits beside the change history and answers a different question. A revision
|
||||
* knows the title used to say something else; it cannot know that this went
|
||||
* back because the photo is missing, because that sentence was never a field
|
||||
* on the form. Kept together with the two review buttons so the reason and the
|
||||
* decision are written in the same place, rather than the decision here and
|
||||
* the reason in somebody's chat window.
|
||||
*/
|
||||
class ReviewsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'reviewComments';
|
||||
|
||||
public static function getTitle(Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('panel.reviews.title');
|
||||
}
|
||||
|
||||
/**
|
||||
* The badge says "waiting", not "how many": a count of comments is
|
||||
* trivia, and whether somebody is waiting for an answer is not.
|
||||
*/
|
||||
public static function getBadge(Model $ownerRecord, string $pageClass): ?string
|
||||
{
|
||||
return $ownerRecord->isAwaitingReview() ? '!' : null;
|
||||
}
|
||||
|
||||
public static function getBadgeColor(Model $ownerRecord, string $pageClass): ?string
|
||||
{
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->defaultSort('id', 'desc')
|
||||
->modifyQueryUsing(fn ($query) => $query->with('user'))
|
||||
->columns([
|
||||
TextColumn::make('event')
|
||||
->label(__('panel.reviews.fields.event'))
|
||||
->badge(),
|
||||
|
||||
TextColumn::make('user_name')
|
||||
->label(__('panel.reviews.fields.who'))
|
||||
->state(fn (ReviewComment $record) => $record->author()),
|
||||
|
||||
TextColumn::make('body')
|
||||
->label(__('panel.reviews.fields.note'))
|
||||
->wrap()
|
||||
->placeholder('—'),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('panel.reviews.fields.when'))
|
||||
->since()
|
||||
->dateTimeTooltip()
|
||||
->sortable(),
|
||||
])
|
||||
->headerActions([
|
||||
ReviewActions::request(),
|
||||
ReviewActions::returnForChanges(),
|
||||
|
||||
// Plain comments, for the half of a review that is neither a
|
||||
// rejection nor an approval: a question, or an answer to one.
|
||||
Action::make('comment')
|
||||
->label(__('panel.reviews.actions.comment'))
|
||||
->icon('heroicon-o-chat-bubble-left-ellipsis')
|
||||
->color('gray')
|
||||
->visible(fn () => $this->getOwnerRecord()->exists)
|
||||
->schema([
|
||||
Textarea::make('body')
|
||||
->label(__('panel.reviews.fields.note'))
|
||||
->rows(3)
|
||||
->required()
|
||||
->maxLength(2000),
|
||||
])
|
||||
->action(fn (array $data) => $this->getOwnerRecord()->comment($data['body'])),
|
||||
])
|
||||
->emptyStateHeading(__('panel.reviews.empty.heading'))
|
||||
->emptyStateDescription(__('panel.reviews.empty.description'))
|
||||
->emptyStateIcon('heroicon-o-chat-bubble-left-right');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user