Duplicate records as drafts, and preview them from the form
The copy keeps the content, stack and team, but is unpublished, out of any review, and gets a title marked as a copy and a free slug. It replaces Filament's replicate action, which left the copy without a slug and still published. The same copy is available through the API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ use App\Filament\Resources\Calls\Pages\CreateCall;
|
||||
use App\Filament\Resources\Calls\Pages\EditCall;
|
||||
use App\Filament\Resources\Calls\Pages\ListCalls;
|
||||
use App\Filament\Resources\Calls\RelationManagers\ApplicationsRelationManager;
|
||||
use App\Filament\Support\DuplicateAction;
|
||||
use App\Filament\Support\PreviewAction;
|
||||
use App\Filament\Support\TranslatableTabs;
|
||||
use App\Filament\Support\TranslatedOptionLabel;
|
||||
@@ -20,7 +21,6 @@ use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ReplicateAction;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
@@ -230,17 +230,9 @@ class CallResource extends Resource
|
||||
->preload(),
|
||||
])
|
||||
->recordActions([
|
||||
// A near-copy is a real workflow: the third similar service,
|
||||
// next year's call. The slug is cleared because two records
|
||||
// cannot answer at the same URL.
|
||||
ReplicateAction::make()
|
||||
->label(__('panel.actions.duplicate'))
|
||||
->icon('heroicon-o-document-duplicate')
|
||||
->excludeAttributes(['slug', 'published_at', 'unpublish_at'])
|
||||
->modalHeading(__('panel.actions.duplicate_heading'))
|
||||
->successNotificationTitle(__('panel.actions.duplicated')),
|
||||
|
||||
PreviewAction::for('call'),
|
||||
DuplicateAction::make(),
|
||||
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace App\Filament\Resources\Calls\Pages;
|
||||
|
||||
use App\Filament\Concerns\WarnsOfConcurrentEditing;
|
||||
use App\Filament\Resources\Calls\CallResource;
|
||||
use App\Filament\Support\DuplicateAction;
|
||||
use App\Filament\Support\PreviewAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
@@ -18,6 +20,9 @@ class EditCall extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
// Seeing it on the real site without leaving the form.
|
||||
PreviewAction::for('call'),
|
||||
DuplicateAction::make(),
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace App\Filament\Resources\Pages\Pages;
|
||||
use App\Enums\PageStatus;
|
||||
use App\Filament\Concerns\WarnsOfConcurrentEditing;
|
||||
use App\Filament\Resources\Pages\PageResource;
|
||||
use App\Filament\Support\DuplicateAction;
|
||||
use App\Filament\Support\PreviewAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ForceDeleteAction;
|
||||
use Filament\Actions\RestoreAction;
|
||||
@@ -21,6 +23,9 @@ class EditPage extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
// Seeing it on the real site without leaving the form.
|
||||
PreviewAction::for('page'),
|
||||
DuplicateAction::make(),
|
||||
DeleteAction::make(),
|
||||
ForceDeleteAction::make(),
|
||||
RestoreAction::make(),
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\Pages\Tables;
|
||||
|
||||
use App\Enums\PageStatus;
|
||||
use App\Filament\Support\DuplicateAction;
|
||||
use App\Filament\Support\PreviewAction;
|
||||
use App\Filament\Support\ReviewActions;
|
||||
use App\Filament\Support\ReviewStatusColumn;
|
||||
@@ -20,7 +21,6 @@ use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ForceDeleteBulkAction;
|
||||
use Filament\Actions\ReplicateAction;
|
||||
use Filament\Actions\RestoreBulkAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
@@ -211,17 +211,8 @@ class PagesTable
|
||||
&& Auth::user()?->can('publish', $record))
|
||||
->action(fn (Page $record) => $record->update(['status' => PageStatus::Draft])),
|
||||
|
||||
// A near-copy is a real workflow: the third similar service,
|
||||
// next year's call. The slug is cleared because two records
|
||||
// cannot answer at the same URL.
|
||||
ReplicateAction::make()
|
||||
->label(__('panel.actions.duplicate'))
|
||||
->icon('heroicon-o-document-duplicate')
|
||||
->excludeAttributes(['slug', 'published_at', 'unpublish_at'])
|
||||
->modalHeading(__('panel.actions.duplicate_heading'))
|
||||
->successNotificationTitle(__('panel.actions.duplicated')),
|
||||
|
||||
PreviewAction::for('page'),
|
||||
DuplicateAction::make(),
|
||||
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace App\Filament\Resources\Projects\Pages;
|
||||
|
||||
use App\Filament\Concerns\WarnsOfConcurrentEditing;
|
||||
use App\Filament\Resources\Projects\ProjectResource;
|
||||
use App\Filament\Support\DuplicateAction;
|
||||
use App\Filament\Support\PreviewAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
@@ -18,6 +20,9 @@ class EditProject extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
// Seeing it on the real site without leaving the form.
|
||||
PreviewAction::for('project'),
|
||||
DuplicateAction::make(),
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\Projects\Tables;
|
||||
|
||||
use App\Enums\ProjectStatus;
|
||||
use App\Filament\Support\DuplicateAction;
|
||||
use App\Filament\Support\PreviewAction;
|
||||
use App\Filament\Support\ReviewActions;
|
||||
use App\Filament\Support\ReviewStatusColumn;
|
||||
@@ -19,7 +20,6 @@ use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ForceDeleteBulkAction;
|
||||
use Filament\Actions\ReplicateAction;
|
||||
use Filament\Actions\RestoreBulkAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
@@ -197,17 +197,8 @@ class ProjectsTable
|
||||
&& Auth::user()?->can('publish', $record))
|
||||
->action(fn (Project $record) => $record->update(['published_at' => now()])),
|
||||
|
||||
// A near-copy is a real workflow: the third similar service,
|
||||
// next year's call. The slug is cleared because two records
|
||||
// cannot answer at the same URL.
|
||||
ReplicateAction::make()
|
||||
->label(__('panel.actions.duplicate'))
|
||||
->icon('heroicon-o-document-duplicate')
|
||||
->excludeAttributes(['slug', 'published_at', 'unpublish_at'])
|
||||
->modalHeading(__('panel.actions.duplicate_heading'))
|
||||
->successNotificationTitle(__('panel.actions.duplicated')),
|
||||
|
||||
PreviewAction::for('project'),
|
||||
DuplicateAction::make(),
|
||||
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace App\Filament\Resources\Services\Pages;
|
||||
|
||||
use App\Filament\Concerns\WarnsOfConcurrentEditing;
|
||||
use App\Filament\Resources\Services\ServiceResource;
|
||||
use App\Filament\Support\DuplicateAction;
|
||||
use App\Filament\Support\PreviewAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
@@ -18,6 +20,9 @@ class EditService extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
// Seeing it on the real site without leaving the form.
|
||||
PreviewAction::for('service'),
|
||||
DuplicateAction::make(),
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Filament\RelationManagers\RevisionsRelationManager;
|
||||
use App\Filament\Resources\Services\Pages\CreateService;
|
||||
use App\Filament\Resources\Services\Pages\EditService;
|
||||
use App\Filament\Resources\Services\Pages\ListServices;
|
||||
use App\Filament\Support\DuplicateAction;
|
||||
use App\Filament\Support\PreviewAction;
|
||||
use App\Filament\Support\ReviewActions;
|
||||
use App\Filament\Support\ReviewStatusColumn;
|
||||
@@ -26,7 +27,6 @@ use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ForceDeleteBulkAction;
|
||||
use Filament\Actions\ReplicateAction;
|
||||
use Filament\Actions\RestoreBulkAction;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
@@ -323,17 +323,8 @@ class ServiceResource extends Resource
|
||||
ReviewActions::request(),
|
||||
ReviewActions::returnForChanges(),
|
||||
|
||||
// A near-copy is a real workflow: the third similar service,
|
||||
// next year's call. The slug is cleared because two records
|
||||
// cannot answer at the same URL.
|
||||
ReplicateAction::make()
|
||||
->label(__('panel.actions.duplicate'))
|
||||
->icon('heroicon-o-document-duplicate')
|
||||
->excludeAttributes(['slug', 'published_at', 'unpublish_at'])
|
||||
->modalHeading(__('panel.actions.duplicate_heading'))
|
||||
->successNotificationTitle(__('panel.actions.duplicated')),
|
||||
|
||||
PreviewAction::for('service'),
|
||||
DuplicateAction::make(),
|
||||
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
|
||||
38
app/Filament/Support/DuplicateAction.php
Normal file
38
app/Filament/Support/DuplicateAction.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Support;
|
||||
|
||||
use App\Support\Duplicator;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* "Duplicate": a draft copy to start the next one from, opened straight away.
|
||||
*/
|
||||
final class DuplicateAction
|
||||
{
|
||||
public static function make(): Action
|
||||
{
|
||||
return Action::make('duplicate')
|
||||
->label(__('panel.duplicate.label'))
|
||||
->icon('heroicon-o-document-duplicate')
|
||||
->color('gray')
|
||||
->requiresConfirmation()
|
||||
->modalHeading(__('panel.duplicate.heading'))
|
||||
->modalDescription(__('panel.duplicate.hint'))
|
||||
->authorize('create')
|
||||
->action(function (Model $record, $livewire): void {
|
||||
$copy = Duplicator::duplicate($record);
|
||||
|
||||
Notification::make()
|
||||
->title(__('panel.duplicate.done'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
$livewire->redirect($livewire::getResource()::getUrl('edit', ['record' => $copy]));
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user