mirror of
https://github.com/filamentphp/filament.git
synced 2026-09-24 15:42:09 +08:00
Merge pull request #281 from laravel-filament/refactor/pages
refactor: Pages
This commit is contained in:
Generated
+307
-384
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,11 @@ use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateFilamentUsersTable extends Migration
|
||||
{
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('filament_users');
|
||||
}
|
||||
|
||||
public function up()
|
||||
{
|
||||
Schema::create('filament_users', function (Blueprint $table) {
|
||||
@@ -20,9 +25,4 @@ class CreateFilamentUsersTable extends Migration
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('filament_users');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@ use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateFilamentPasswordResetsTable extends Migration
|
||||
{
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('filament_password_resets');
|
||||
}
|
||||
|
||||
public function up()
|
||||
{
|
||||
Schema::create('filament_password_resets', function (Blueprint $table) {
|
||||
@@ -14,9 +19,4 @@ class CreateFilamentPasswordResetsTable extends Migration
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('filament_password_resets');
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-3
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"/js/filament.js": "/js/filament.js?id=f6f25b9e29038b97ab02",
|
||||
"/css/filament.css": "/css/filament.css?id=e3264b5b30c72fdb723e"
|
||||
"/js/filament.js": "/js/filament.js?id=d0400b4255850498d8d2",
|
||||
"/css/filament.css": "/css/filament.css?id=48fc5bb45100c9be4f17"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use Symplify\MonorepoBuilder\Release\ReleaseWorker\SetNextMutualDependenciesRele
|
||||
use Symplify\MonorepoBuilder\Release\ReleaseWorker\TagVersionReleaseWorker;
|
||||
use Symplify\MonorepoBuilder\Release\ReleaseWorker\UpdateBranchAliasReleaseWorker;
|
||||
use Symplify\MonorepoBuilder\Release\ReleaseWorker\UpdateReplaceReleaseWorker;
|
||||
use Symplify\MonorepoBuilder\ValueObject\Option;
|
||||
|
||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
$services = $containerConfigurator->services();
|
||||
|
||||
Generated
+1492
-16569
File diff suppressed because it is too large
Load Diff
+8
-7
@@ -12,19 +12,20 @@
|
||||
"start": "npm run watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@popperjs/core": "^2.7.0",
|
||||
"@popperjs/core": "^2.9.1",
|
||||
"@tailwindcss/forms": "^0.2.1",
|
||||
"@tailwindcss/jit": "^0.1.7",
|
||||
"@tailwindcss/typography": "^0.4.0",
|
||||
"alpinejs": "^2.8.0",
|
||||
"autoprefixer": "^10.2.4",
|
||||
"alpinejs": "^2.8.2",
|
||||
"autoprefixer": "^10.2.5",
|
||||
"dotenv": "^8.2.0",
|
||||
"focus-visible": "^5.2.0",
|
||||
"laravel-mix": "^6.0.11",
|
||||
"postcss": "^8.2.6",
|
||||
"laravel-mix": "^6.0.13",
|
||||
"postcss": "^8.2.8",
|
||||
"postcss-import": "^14.0.0",
|
||||
"postcss-nested": "^5.0.3",
|
||||
"postcss-nested": "^5.0.5",
|
||||
"prettier": "^2.2.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"tailwindcss": "^2.0.3"
|
||||
"tailwindcss": "^2.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="bg-white space-y-6 shadow-xl rounded p-4 md:p-6">
|
||||
<x-forms::form :schema="$formComponent->getSchema()" />
|
||||
</div>
|
||||
@@ -1,12 +0,0 @@
|
||||
@props([
|
||||
'form',
|
||||
])
|
||||
|
||||
<form
|
||||
wire:submit.prevent="{{ $form->getSubmitMethod() }}"
|
||||
{{ $attributes }}
|
||||
>
|
||||
<x-forms::layout :schema="$form->getSchema()" :columns="$form->getColumns()" />
|
||||
|
||||
{{ $slot }}
|
||||
</form>
|
||||
@@ -24,5 +24,5 @@
|
||||
</legend>
|
||||
@endif
|
||||
|
||||
<x-forms::layout :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
|
||||
<x-forms::form :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
|
||||
</fieldset>
|
||||
|
||||
@@ -35,57 +35,6 @@
|
||||
<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.js"></script>
|
||||
<script src="https://unpkg.com/filepond-plugin-image-resize/dist/filepond-plugin-image-resize.js"></script>
|
||||
<script src="https://unpkg.com/filepond-plugin-image-transform/dist/filepond-plugin-image-transform.js"></script>
|
||||
<script>
|
||||
function fileUpload(config) {
|
||||
return {
|
||||
disk: config.disk,
|
||||
|
||||
name: config.name,
|
||||
|
||||
pond: null,
|
||||
|
||||
value: config.value,
|
||||
|
||||
init: function () {
|
||||
FilePond.registerPlugin(FilePondPluginFileValidateSize)
|
||||
FilePond.registerPlugin(FilePondPluginFileValidateType)
|
||||
FilePond.registerPlugin(FilePondPluginImageCrop)
|
||||
FilePond.registerPlugin(FilePondPluginImageExifOrientation)
|
||||
FilePond.registerPlugin(FilePondPluginImagePreview)
|
||||
FilePond.registerPlugin(FilePondPluginImageResize)
|
||||
FilePond.registerPlugin(FilePondPluginImageTransform)
|
||||
|
||||
this.$wire.getUploadedFileUrl(this.name, this.disk).then((uploadedFileUrl) => {
|
||||
if (uploadedFileUrl) {
|
||||
config.files = [{
|
||||
source: uploadedFileUrl,
|
||||
options: {
|
||||
type: 'local',
|
||||
},
|
||||
}]
|
||||
}
|
||||
|
||||
this.pond = FilePond.create(this.$refs.input, config)
|
||||
})
|
||||
|
||||
this.$watch('value', () => {
|
||||
this.$wire.getUploadedFileUrl(this.name, this.disk).then((uploadedFileUrl) => {
|
||||
if (uploadedFileUrl) {
|
||||
this.pond.files = [{
|
||||
source: uploadedFileUrl,
|
||||
options: {
|
||||
type: 'local',
|
||||
},
|
||||
}]
|
||||
} else {
|
||||
this.pond.files = []
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endpushonce
|
||||
|
||||
<x-forms::field-group
|
||||
@@ -98,55 +47,93 @@
|
||||
:required="$formComponent->isRequired()"
|
||||
>
|
||||
<div
|
||||
x-data="fileUpload({
|
||||
acceptedFileTypes: {{ json_encode($formComponent->getAcceptedFileTypes()) }},
|
||||
x-data="{
|
||||
disk: '{{ $formComponent->getDiskName() }}',
|
||||
files: [],
|
||||
{{ $formComponent->getImageCropAspectRatio() !== null ? "imageCropAspectRatio: '{$formComponent->getImageCropAspectRatio()}'," : null }}
|
||||
{{ $formComponent->getImagePreviewHeight() !== null ? "imagePreviewHeight: {$formComponent->getImagePreviewHeight()}," : null }}
|
||||
{{ $formComponent->getImageResizeTargetHeight() !== null ? "imageResizeTargetHeight: {$formComponent->getImageResizeTargetHeight()}," : null }}
|
||||
{{ $formComponent->getImageResizeTargetWidth() !== null ? "imageResizeTargetWidth: {$formComponent->getImageResizeTargetWidth()}," : null }}
|
||||
{{ __($formComponent->getPlaceholder()) !== null ? 'labelIdle: \'' . __($formComponent->getPlaceholder()) . '\',' : null }}
|
||||
{{ $formComponent->getMaxSize() !== null ? "maxFileSize: '{$formComponent->getMaxSize()} KB'," : null }}
|
||||
{{ $formComponent->getMinSize() !== null ? "minFileSize: '{$formComponent->getMinSize()} KB'," : null }}
|
||||
initialUrl: {{ $formComponent->getUploadedFileUrl() ? "'{$formComponent->getUploadedFileUrl()}'" : 'null' }},
|
||||
name: '{{ $formComponent->getName() }}',
|
||||
server: {
|
||||
load: (source, load, error, progress, abort, headers) => {
|
||||
fetch(source).then((response) => {
|
||||
response.blob().then((blob) => {
|
||||
load(blob)
|
||||
})
|
||||
})
|
||||
},
|
||||
process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
|
||||
@this.upload(
|
||||
'{{ $formComponent->getTemporaryUploadedFilePropertyName() }}',
|
||||
file,
|
||||
(uploadedFilename) => load(uploadedFilename),
|
||||
() => error(),
|
||||
(event) => progress(event.detail.progress)
|
||||
)
|
||||
},
|
||||
remove: (source, load, error) => {
|
||||
@this.removeUploadedFile('{{ $formComponent->getName() }}').then(() => {
|
||||
load()
|
||||
})
|
||||
},
|
||||
revert: (uniqueFileId, load, error) => {
|
||||
@this.clearTemporaryUploadedFile('{{ $formComponent->getName() }}').then(() => {
|
||||
load()
|
||||
})
|
||||
},
|
||||
},
|
||||
styleButtonProcessItemPosition: '{{ $formComponent->getUploadButtonPosition() }}',
|
||||
styleButtonRemoveItemPosition: '{{ $formComponent->getRemoveUploadButtonPosition() }}',
|
||||
styleLoadIndicatorPosition: '{{ $formComponent->getLoadingIndicatorPosition() }}',
|
||||
{{ $formComponent->getPanelAspectRatio() !== null ? "stylePanelAspectRatio: '{$formComponent->getPanelAspectRatio()}'," : null }}
|
||||
{{ $formComponent->getPanelLayout() !== null ? "stylePanelLayout: '{$formComponent->getPanelLayout()}'," : null }}
|
||||
styleProgressIndicatorPosition: '{{ $formComponent->getUploadProgressIndicatorPosition() }}',
|
||||
pond: null,
|
||||
value: @entangle($formComponent->getName()){{ Str::of($formComponent->getBindingAttribute())->after('wire:model') }},
|
||||
})"
|
||||
x-init="init()"
|
||||
}"
|
||||
x-init="
|
||||
FilePond.registerPlugin(FilePondPluginFileValidateSize)
|
||||
FilePond.registerPlugin(FilePondPluginFileValidateType)
|
||||
FilePond.registerPlugin(FilePondPluginImageCrop)
|
||||
FilePond.registerPlugin(FilePondPluginImageExifOrientation)
|
||||
FilePond.registerPlugin(FilePondPluginImagePreview)
|
||||
FilePond.registerPlugin(FilePondPluginImageResize)
|
||||
FilePond.registerPlugin(FilePondPluginImageTransform)
|
||||
|
||||
if (initialUrl) {
|
||||
files = [{
|
||||
source: initialUrl,
|
||||
options: {
|
||||
type: 'local',
|
||||
},
|
||||
}]
|
||||
}
|
||||
|
||||
pond = FilePond.create($refs.input, {
|
||||
acceptedFileTypes: {{ json_encode($formComponent->getAcceptedFileTypes()) }},
|
||||
files: files,
|
||||
{{ $formComponent->getImageCropAspectRatio() !== null ? "imageCropAspectRatio: '{$formComponent->getImageCropAspectRatio()}'," : null }}
|
||||
{{ $formComponent->getImagePreviewHeight() !== null ? "imagePreviewHeight: {$formComponent->getImagePreviewHeight()}," : null }}
|
||||
{{ $formComponent->getImageResizeTargetHeight() !== null ? "imageResizeTargetHeight: {$formComponent->getImageResizeTargetHeight()}," : null }}
|
||||
{{ $formComponent->getImageResizeTargetWidth() !== null ? "imageResizeTargetWidth: {$formComponent->getImageResizeTargetWidth()}," : null }}
|
||||
{{ __($formComponent->getPlaceholder()) !== null ? 'labelIdle: \'' . __($formComponent->getPlaceholder()) . '\',' : null }}
|
||||
{{ $formComponent->getMaxSize() !== null ? "maxFileSize: '{$formComponent->getMaxSize()} KB'," : null }}
|
||||
{{ $formComponent->getMinSize() !== null ? "minFileSize: '{$formComponent->getMinSize()} KB'," : null }}
|
||||
server: {
|
||||
load: (source, load, error, progress, abort, headers) => {
|
||||
fetch(source).then((response) => {
|
||||
response.blob().then((blob) => {
|
||||
load(blob)
|
||||
})
|
||||
})
|
||||
},
|
||||
process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
|
||||
@this.upload(
|
||||
'{{ $formComponent->getTemporaryUploadedFilePropertyName() }}',
|
||||
file,
|
||||
(uploadedFilename) => load(uploadedFilename),
|
||||
() => error(),
|
||||
(event) => progress(event.detail.progress)
|
||||
)
|
||||
},
|
||||
remove: (source, load, error) => {
|
||||
@this.removeUploadedFile('{{ $formComponent->getName() }}').then(() => {
|
||||
load()
|
||||
})
|
||||
},
|
||||
revert: (uniqueFileId, load, error) => {
|
||||
@this.clearTemporaryUploadedFile('{{ $formComponent->getName() }}').then(() => {
|
||||
load()
|
||||
})
|
||||
},
|
||||
},
|
||||
styleButtonProcessItemPosition: '{{ $formComponent->getUploadButtonPosition() }}',
|
||||
styleButtonRemoveItemPosition: '{{ $formComponent->getRemoveUploadButtonPosition() }}',
|
||||
styleLoadIndicatorPosition: '{{ $formComponent->getLoadingIndicatorPosition() }}',
|
||||
{{ $formComponent->getPanelAspectRatio() !== null ? "stylePanelAspectRatio: '{$formComponent->getPanelAspectRatio()}'," : null }}
|
||||
{{ $formComponent->getPanelLayout() !== null ? "stylePanelLayout: '{$formComponent->getPanelLayout()}'," : null }}
|
||||
styleProgressIndicatorPosition: '{{ $formComponent->getUploadProgressIndicatorPosition() }}',
|
||||
})
|
||||
|
||||
$watch('value', () => {
|
||||
@this.getUploadedFileUrl(name, disk).then((uploadedFileUrl) => {
|
||||
if (uploadedFileUrl) {
|
||||
pond.files = [{
|
||||
source: uploadedFileUrl,
|
||||
options: {
|
||||
type: 'local',
|
||||
},
|
||||
}]
|
||||
} else {
|
||||
pond.files = []
|
||||
}
|
||||
})
|
||||
})
|
||||
"
|
||||
wire:ignore
|
||||
{!! Filament\format_attributes($formComponent->getExtraAttributes()) !!}
|
||||
>
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
|
||||
@if (count($schema))
|
||||
<div {{ $attributes }}>
|
||||
<div class="grid {{ $columnsClasses }} gap-6">
|
||||
<div class="grid gap-6 {{ $columnsClasses }}">
|
||||
@foreach ($schema as $component)
|
||||
{{ $component->render() }}
|
||||
@endforeach
|
||||
@@ -17,5 +17,5 @@
|
||||
@endphp
|
||||
|
||||
<div class="{{ $columnSpanClass }}">
|
||||
<x-forms::layout :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
|
||||
<x-forms::form :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
|
||||
</div>
|
||||
|
||||
@@ -1 +1 @@
|
||||
<x-forms::layout :schema="$formComponent->getSchema()" />
|
||||
<x-forms::form :schema="$formComponent->getSchema()" />
|
||||
|
||||
@@ -55,6 +55,6 @@
|
||||
</div>
|
||||
|
||||
<div id="{{ $formComponent->getId() }}-content" x-show.transition="open" :aria-expanded="open.toString()">
|
||||
<x-forms::layout :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
|
||||
<x-forms::form :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
x-show="tab === '{{ "{$formComponent->getId()}" }}'"
|
||||
class="p-4 md:p-6"
|
||||
>
|
||||
<x-forms::layout :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
|
||||
<x-forms::form :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Forms\Components;
|
||||
|
||||
class Card extends Component
|
||||
{
|
||||
public static function make($schema = [])
|
||||
{
|
||||
return (new static())->schema($schema);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Filament\Forms\Components;
|
||||
|
||||
use Filament\Forms\Form;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Traits\Tappable;
|
||||
|
||||
@@ -148,9 +147,9 @@ class Component
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function getModel()
|
||||
public function getLivewire()
|
||||
{
|
||||
return $this->getForm()->getModel();
|
||||
return $this->getForm()->getLivewire();
|
||||
}
|
||||
|
||||
public function getParent()
|
||||
@@ -158,11 +157,6 @@ class Component
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function getRecord()
|
||||
{
|
||||
return $this->getForm()->getRecord();
|
||||
}
|
||||
|
||||
public function getRules($field = null)
|
||||
{
|
||||
if ($field !== null) {
|
||||
@@ -175,12 +169,8 @@ class Component
|
||||
|
||||
$rules = $this instanceof Field ? $this->rules : [];
|
||||
|
||||
foreach ($rules as $field => $conditions) {
|
||||
$rules[$field] = $this->transformConditions($conditions);
|
||||
}
|
||||
|
||||
foreach ($this->getSubform()->getRules() as $field => $conditions) {
|
||||
$rules[$field] = array_merge($rules[$field] ?? [], $this->transformConditions($conditions));
|
||||
$rules[$field] = array_merge($rules[$field] ?? [], $conditions);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
@@ -193,11 +183,7 @@ class Component
|
||||
|
||||
public function getSubform()
|
||||
{
|
||||
return Form::make()
|
||||
->context($this->getContext())
|
||||
->model($this->getModel())
|
||||
->record($this->getRecord())
|
||||
->schema($this->getSchema());
|
||||
return Form::extend($this)->schema($this->getSchema());
|
||||
}
|
||||
|
||||
public function getValidationAttributes()
|
||||
@@ -331,12 +317,8 @@ class Component
|
||||
$callback = fn ($component) => $component->visible();
|
||||
}
|
||||
|
||||
if ($this->getRecord() === null) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
try {
|
||||
$shouldExecuteCallback = $condition($this->getRecord());
|
||||
$shouldExecuteCallback = $condition($this->getLivewire());
|
||||
} catch (\Exception $exception) {
|
||||
$shouldExecuteCallback = false;
|
||||
}
|
||||
@@ -348,17 +330,4 @@ class Component
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function transformConditions($conditions)
|
||||
{
|
||||
return collect($conditions)
|
||||
->map(function ($condition) {
|
||||
if (! is_string($condition)) {
|
||||
return $condition;
|
||||
}
|
||||
|
||||
return (string) Str::of($condition)->replace('{{ record }}', $this->getRecord() instanceof Model ? $this->getRecord()->getKey() : '');
|
||||
})
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@ namespace Filament\Forms\Components\Concerns;
|
||||
|
||||
trait CanBeUnique
|
||||
{
|
||||
public function unique($table, $column = null, $exceptCurrentRecord = false)
|
||||
public function unique($table, $column = null, $except = false)
|
||||
{
|
||||
$this->configure(function () use ($column, $exceptCurrentRecord, $table) {
|
||||
$this->configure(function () use ($column, $except, $table) {
|
||||
$rule = "unique:$table,$column";
|
||||
if ($exceptCurrentRecord) {
|
||||
$rule .= ',{{ record }}';
|
||||
|
||||
if ($except) {
|
||||
$rule .= ",{$except}";
|
||||
}
|
||||
|
||||
$this->addRules([$this->getName() => [$rule]]);
|
||||
|
||||
@@ -8,7 +8,7 @@ class Field extends Component
|
||||
{
|
||||
protected $bindingAttribute = 'wire:model.defer';
|
||||
|
||||
protected $defaultValue = null;
|
||||
protected $defaultValue;
|
||||
|
||||
protected $extraAttributes = [];
|
||||
|
||||
@@ -196,6 +196,11 @@ class Field extends Component
|
||||
return $this->validationAttribute;
|
||||
}
|
||||
|
||||
public function getValue()
|
||||
{
|
||||
return $this->getLivewire()->getPropertyValue($this->getName());
|
||||
}
|
||||
|
||||
public function helpMessage($message)
|
||||
{
|
||||
$this->configure(function () use ($message) {
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace Filament\Forms\Components;
|
||||
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use League\Flysystem\AwsS3v3\AwsS3Adapter;
|
||||
|
||||
class FileUpload extends Field
|
||||
{
|
||||
@@ -28,9 +30,9 @@ class FileUpload extends Field
|
||||
|
||||
protected $minSize;
|
||||
|
||||
protected $panelAspectRatio = null;
|
||||
protected $panelAspectRatio;
|
||||
|
||||
protected $panelLayout = null;
|
||||
protected $panelLayout;
|
||||
|
||||
protected $removeUploadButtonPosition = 'left';
|
||||
|
||||
@@ -109,6 +111,11 @@ class FileUpload extends Field
|
||||
return $this->directory;
|
||||
}
|
||||
|
||||
public function getDisk()
|
||||
{
|
||||
return Storage::disk($this->getDiskName());
|
||||
}
|
||||
|
||||
public function getDiskName()
|
||||
{
|
||||
return $this->diskName ?? config('forms.default_filesystem_disk');
|
||||
@@ -174,6 +181,29 @@ class FileUpload extends Field
|
||||
return $this->uploadButtonPosition;
|
||||
}
|
||||
|
||||
public function getUploadedFileUrl()
|
||||
{
|
||||
$path = $this->getValue();
|
||||
|
||||
if ($path === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$storage = $this->getDisk();
|
||||
|
||||
if (
|
||||
$storage->getDriver()->getAdapter() instanceof AwsS3Adapter &&
|
||||
$storage->getVisibility($path) === 'private'
|
||||
) {
|
||||
return $storage->temporaryUrl(
|
||||
$path,
|
||||
now()->addMinutes(5),
|
||||
);
|
||||
}
|
||||
|
||||
return $storage->url($path);
|
||||
}
|
||||
|
||||
public function getUploadProgressIndicatorPosition()
|
||||
{
|
||||
return $this->uploadProgressIndicatorPosition;
|
||||
|
||||
@@ -17,7 +17,7 @@ class Grid extends Component
|
||||
|
||||
public function getColumns()
|
||||
{
|
||||
return $this->columns;
|
||||
return value($this->columns);
|
||||
}
|
||||
|
||||
public function getSubform()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Forms\Concerns;
|
||||
|
||||
use Filament\Forms\Components\Select;
|
||||
|
||||
trait CanHandleSelectFields
|
||||
{
|
||||
public function getSelectFieldOptionSearchResults($fieldName, $search = '')
|
||||
{
|
||||
$field = collect($this->getForm()->getFlatSchema())
|
||||
->first(fn ($field) => $field instanceof Select && $field->getName() === $fieldName);
|
||||
|
||||
if (! $field) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $field->getOptionSearchResults($search);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Forms\Concerns;
|
||||
|
||||
trait CanManipulateProperties
|
||||
{
|
||||
public function fillWithFormDefaults()
|
||||
{
|
||||
$this->fill($this->getPropertyDefaults());
|
||||
}
|
||||
|
||||
public function getPropertyDefaults()
|
||||
{
|
||||
return $this->getForm()->getDefaultValues();
|
||||
}
|
||||
|
||||
public function reset(...$properties)
|
||||
{
|
||||
parent::reset(...$properties);
|
||||
|
||||
$defaults = $this->getPropertyDefaults();
|
||||
|
||||
if (count($properties) && is_array($properties[0])) {
|
||||
$properties = $properties[0];
|
||||
}
|
||||
|
||||
if (empty($properties)) {
|
||||
$properties = array_keys($defaults);
|
||||
}
|
||||
|
||||
$propertiesToFill = collect($properties)
|
||||
->filter(fn ($property) => in_array($property, $defaults))
|
||||
->mapWithKeys(fn ($property) => [$property => $defaults[$property]])
|
||||
->toArray();
|
||||
|
||||
$this->fill($propertiesToFill);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Forms\Concerns;
|
||||
|
||||
use Filament\Forms\Components\Field;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use League\Flysystem\AwsS3v3\AwsS3Adapter;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
trait CanUploadFiles
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public $temporaryUploadedFiles = [];
|
||||
|
||||
public function clearTemporaryUploadedFile($name)
|
||||
{
|
||||
$this->syncInput(
|
||||
static::getTemporaryUploadedFilePropertyName($name),
|
||||
null,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
public function getTemporaryUploadedFile($name)
|
||||
{
|
||||
return $this->getPropertyValue(
|
||||
static::getTemporaryUploadedFilePropertyName($name)
|
||||
);
|
||||
}
|
||||
|
||||
public static function getTemporaryUploadedFilePropertyName($fieldName)
|
||||
{
|
||||
return "temporaryUploadedFiles.{$fieldName}";
|
||||
}
|
||||
|
||||
public function getUploadedFileUrl($name, $disk)
|
||||
{
|
||||
$path = $this->getPropertyValue($name);
|
||||
|
||||
if (! $path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$storage = Storage::disk($disk);
|
||||
|
||||
if (
|
||||
$storage->getDriver()->getAdapter() instanceof AwsS3Adapter &&
|
||||
$storage->getVisibility($path) === 'private'
|
||||
) {
|
||||
return $storage->temporaryUrl(
|
||||
$path,
|
||||
now()->addMinutes(5),
|
||||
);
|
||||
}
|
||||
|
||||
return $storage->url($path);
|
||||
}
|
||||
|
||||
public function removeUploadedFile($name)
|
||||
{
|
||||
$this->syncInput($name, null, false);
|
||||
$this->clearTemporaryUploadedFile($name);
|
||||
}
|
||||
|
||||
public function resetTemporaryUploadedFiles()
|
||||
{
|
||||
$this->temporaryUploadedFiles = [];
|
||||
}
|
||||
|
||||
public function storeTemporaryUploadedFiles()
|
||||
{
|
||||
foreach ($this->getForm()->getFlatSchema() as $field) {
|
||||
if (! $field instanceof FileUpload) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$temporaryUploadedFile = $this->getTemporaryUploadedFile($field->getName());
|
||||
if (! $temporaryUploadedFile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$storeMethod = $field->getVisibility() === 'public' ? 'storePublicly' : 'store';
|
||||
$path = $temporaryUploadedFile->{$storeMethod}($field->getDirectory(), $field->getDiskName());
|
||||
$this->syncInput($field->getName(), $path, false);
|
||||
}
|
||||
|
||||
$this->resetTemporaryUploadedFiles();
|
||||
}
|
||||
|
||||
public function validateTemporaryUploadedFiles()
|
||||
{
|
||||
$rules = collect($this->getRules())
|
||||
->filter(function ($conditions, $field) {
|
||||
return Str::of($field)->startsWith('temporaryUploadedFiles.');
|
||||
})
|
||||
->toArray();
|
||||
|
||||
if (! count($rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
return parent::validate($rules);
|
||||
} catch (ValidationException $exception) {
|
||||
$fieldToFocus = collect($this->getForm()->getFlatSchema())
|
||||
->first(function ($component) use ($exception) {
|
||||
return (
|
||||
$component instanceof Field &&
|
||||
array_key_exists(
|
||||
static::getTemporaryUploadedFilePropertyName($component->getName()),
|
||||
$exception->validator->failed()
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
if ($fieldToFocus) {
|
||||
$this->focusConcealedField($fieldToFocus);
|
||||
}
|
||||
|
||||
$this->setErrorBag($exception->validator->errors());
|
||||
|
||||
foreach ($this->getErrorBag()->messages() as $field => $messages) {
|
||||
$field = (string) Str::of($field)->after('temporaryUploadedFiles.');
|
||||
|
||||
foreach ($messages as $message) {
|
||||
$this->addError($field, $message);
|
||||
}
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Forms\Concerns;
|
||||
|
||||
use Filament\Forms\Components\Concerns\CanConcealFields;
|
||||
use Filament\Forms\Components\Field;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
trait CanValidateInput
|
||||
{
|
||||
public function focusConcealedField($field)
|
||||
{
|
||||
$possiblyConcealingComponent = $field->getParent();
|
||||
|
||||
while ($possiblyConcealingComponent) {
|
||||
if (in_array(
|
||||
CanConcealFields::class,
|
||||
class_uses_recursive($possiblyConcealingComponent),
|
||||
)) {
|
||||
$this->dispatchBrowserEvent(
|
||||
'open',
|
||||
$possiblyConcealingComponent->getId(),
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$possiblyConcealingComponent = $possiblyConcealingComponent->getParent();
|
||||
}
|
||||
}
|
||||
|
||||
public function getRules()
|
||||
{
|
||||
$rules = $this->getForm()->getRules();
|
||||
|
||||
foreach (parent::getRules() as $field => $conditions) {
|
||||
if (! is_array($conditions)) {
|
||||
$conditions = explode('|', $conditions);
|
||||
}
|
||||
|
||||
$rules[$field] = array_merge($rules[$field] ?? [], $conditions);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function getValidationAttributes()
|
||||
{
|
||||
$attributes = $this->getForm()->getValidationAttributes();
|
||||
|
||||
foreach (parent::getValidationAttributes() as $name => $label) {
|
||||
$attributes[$name] = $label;
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
public function validate($rules = null, $messages = [], $attributes = [])
|
||||
{
|
||||
try {
|
||||
return parent::validate($rules, $messages, $attributes);
|
||||
} catch (ValidationException $exception) {
|
||||
$fieldToFocus = collect($this->getForm()->getFlatSchema())
|
||||
->first(function ($field) use ($exception) {
|
||||
return ($field instanceof Field &&
|
||||
array_key_exists($field->getName(), $exception->validator->failed())
|
||||
);
|
||||
});
|
||||
|
||||
if ($fieldToFocus) {
|
||||
$this->focusConcealedField($fieldToFocus);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
public function validateOnly($field, $rules = null, $messages = [], $attributes = [])
|
||||
{
|
||||
try {
|
||||
return parent::validateOnly($field, $rules, $messages, $attributes);
|
||||
} catch (ValidationException $exception) {
|
||||
$fieldToFocus = collect($this->getForm()->getFlatSchema())
|
||||
->first(function ($field) use ($exception) {
|
||||
return ($field instanceof Field &&
|
||||
array_key_exists($field->getName(), $exception->validator->failed())
|
||||
);
|
||||
});
|
||||
|
||||
if ($fieldToFocus) {
|
||||
$this->focusConcealedField($fieldToFocus);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-35
@@ -10,18 +10,14 @@ class Form
|
||||
|
||||
protected $columns = 1;
|
||||
|
||||
protected $context;
|
||||
protected $livewire;
|
||||
|
||||
protected $model;
|
||||
|
||||
protected $record;
|
||||
protected $parent;
|
||||
|
||||
protected $rules = [];
|
||||
|
||||
protected $schema = [];
|
||||
|
||||
protected $submitMethod = 'submit';
|
||||
|
||||
protected $validationAttributes = [];
|
||||
|
||||
public function columns($columns)
|
||||
@@ -31,11 +27,14 @@ class Form
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function context($context)
|
||||
public static function extend($form)
|
||||
{
|
||||
$this->context = $context;
|
||||
return (new static())->parent($form);
|
||||
}
|
||||
|
||||
return $this;
|
||||
public static function for($livewire)
|
||||
{
|
||||
return (new static())->livewire($livewire);
|
||||
}
|
||||
|
||||
public function getColumns()
|
||||
@@ -45,7 +44,7 @@ class Form
|
||||
|
||||
public function getContext()
|
||||
{
|
||||
return $this->context;
|
||||
return get_class($this->getLivewire());
|
||||
}
|
||||
|
||||
public function getDefaultValues()
|
||||
@@ -70,14 +69,14 @@ class Form
|
||||
return $schema;
|
||||
}
|
||||
|
||||
public function getModel()
|
||||
public function getLivewire()
|
||||
{
|
||||
return $this->model;
|
||||
return $this->livewire ?? $this->getParent()->getLivewire();
|
||||
}
|
||||
|
||||
public function getRecord()
|
||||
public function getParent()
|
||||
{
|
||||
return $this->record;
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function getRules()
|
||||
@@ -98,11 +97,6 @@ class Form
|
||||
return $this->schema;
|
||||
}
|
||||
|
||||
public function getSubmitMethod()
|
||||
{
|
||||
return $this->submitMethod;
|
||||
}
|
||||
|
||||
public function getValidationAttributes()
|
||||
{
|
||||
$attributes = $this->validationAttributes;
|
||||
@@ -114,21 +108,16 @@ class Form
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
public static function make()
|
||||
public function livewire($component)
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
|
||||
public function model($model)
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->livewire = $component;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function record($record)
|
||||
public function parent($form)
|
||||
{
|
||||
$this->record = (object) $record;
|
||||
$this->parent = $form;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -151,13 +140,6 @@ class Form
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function submitMethod($method)
|
||||
{
|
||||
$this->submitMethod = $method;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function validationAttributes($attributes)
|
||||
{
|
||||
$this->validationAttributes = $attributes;
|
||||
|
||||
+14
-257
@@ -2,271 +2,28 @@
|
||||
|
||||
namespace Filament\Forms;
|
||||
|
||||
use Filament\Forms\Components\Concerns\CanConcealFields;
|
||||
use Filament\Forms\Components\Field;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use League\Flysystem\AwsS3v3\AwsS3Adapter;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
trait HasForm
|
||||
{
|
||||
use WithFileUploads;
|
||||
use Concerns\CanHandleSelectFields;
|
||||
use Concerns\CanManipulateProperties;
|
||||
use Concerns\CanUploadFiles;
|
||||
use Concerns\CanValidateInput;
|
||||
|
||||
public $temporaryUploadedFiles = [];
|
||||
protected $form;
|
||||
|
||||
public function clearTemporaryUploadedFile($name)
|
||||
public function getForm()
|
||||
{
|
||||
$this->syncInput(
|
||||
static::getTemporaryUploadedFilePropertyName($name),
|
||||
null,
|
||||
false
|
||||
if ($this->form !== null) {
|
||||
return $this->form;
|
||||
}
|
||||
|
||||
return $this->form = $this->form(
|
||||
Form::for($this),
|
||||
);
|
||||
}
|
||||
|
||||
public function fillWithFormDefaults()
|
||||
protected function form(Form $form)
|
||||
{
|
||||
$this->fill($this->getPropertyDefaults());
|
||||
}
|
||||
|
||||
public function focusConcealedField($field)
|
||||
{
|
||||
if ($field) {
|
||||
$possiblyConcealingComponent = $field->getParent();
|
||||
|
||||
while ($possiblyConcealingComponent) {
|
||||
if (in_array(
|
||||
CanConcealFields::class,
|
||||
class_uses_recursive($possiblyConcealingComponent),
|
||||
)) {
|
||||
$this->dispatchBrowserEvent(
|
||||
'open',
|
||||
$possiblyConcealingComponent->getId(),
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$possiblyConcealingComponent = $possiblyConcealingComponent->getParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getPropertyDefaults()
|
||||
{
|
||||
return $this->getForm()->getDefaultValues();
|
||||
}
|
||||
|
||||
public function getRules()
|
||||
{
|
||||
$rules = $this->getForm()->getRules();
|
||||
|
||||
foreach (parent::getRules() as $field => $conditions) {
|
||||
if (! is_array($conditions)) {
|
||||
$conditions = explode('|', $conditions);
|
||||
}
|
||||
|
||||
$rules[$field] = array_merge($rules[$field] ?? [], $conditions);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function getSelectFieldOptionSearchResults($fieldName, $search = '')
|
||||
{
|
||||
$field = collect($this->getForm()->getFlatSchema())
|
||||
->first(fn ($field) => $field instanceof Select && $field->getName() === $fieldName);
|
||||
|
||||
if (! $field) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $field->getOptionSearchResults($search);
|
||||
}
|
||||
|
||||
public function getTemporaryUploadedFile($name)
|
||||
{
|
||||
return $this->getPropertyValue(
|
||||
static::getTemporaryUploadedFilePropertyName($name)
|
||||
);
|
||||
}
|
||||
|
||||
public static function getTemporaryUploadedFilePropertyName($fieldName)
|
||||
{
|
||||
return "temporaryUploadedFiles.{$fieldName}";
|
||||
}
|
||||
|
||||
public function getUploadedFileUrl($name, $disk)
|
||||
{
|
||||
$path = $this->getPropertyValue($name);
|
||||
|
||||
if (! $path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$storage = Storage::disk($disk);
|
||||
|
||||
if (
|
||||
$storage->getDriver()->getAdapter() instanceof AwsS3Adapter &&
|
||||
$storage->getVisibility($path) === 'private'
|
||||
) {
|
||||
return $storage->temporaryUrl(
|
||||
$path,
|
||||
now()->addMinutes(5),
|
||||
);
|
||||
}
|
||||
|
||||
return $storage->url($path);
|
||||
}
|
||||
|
||||
public function getValidationAttributes()
|
||||
{
|
||||
$attributes = $this->getForm()->getValidationAttributes();
|
||||
|
||||
foreach (parent::getValidationAttributes() as $name => $label) {
|
||||
$attributes[$name] = $label;
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
public function removeUploadedFile($name)
|
||||
{
|
||||
$this->syncInput($name, null, false);
|
||||
$this->clearTemporaryUploadedFile($name);
|
||||
}
|
||||
|
||||
public function reset(...$properties)
|
||||
{
|
||||
parent::reset(...$properties);
|
||||
|
||||
$defaults = $this->getPropertyDefaults();
|
||||
|
||||
if (count($properties) && is_array($properties[0])) {
|
||||
$properties = $properties[0];
|
||||
}
|
||||
|
||||
if (empty($properties)) {
|
||||
$properties = array_keys($defaults);
|
||||
}
|
||||
|
||||
$propertiesToFill = collect($properties)
|
||||
->filter(fn ($property) => in_array($property, $defaults))
|
||||
->mapWithKeys(fn ($property) => [$property => $defaults[$property]])
|
||||
->toArray();
|
||||
|
||||
$this->fill($propertiesToFill);
|
||||
}
|
||||
|
||||
public function resetTemporaryUploadedFiles()
|
||||
{
|
||||
$this->temporaryUploadedFiles = [];
|
||||
}
|
||||
|
||||
public function storeTemporaryUploadedFiles()
|
||||
{
|
||||
foreach ($this->getForm()->getFlatSchema() as $field) {
|
||||
if (! $field instanceof FileUpload) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$temporaryUploadedFile = $this->getTemporaryUploadedFile($field->getName());
|
||||
if (! $temporaryUploadedFile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$storeMethod = $field->getVisibility() === 'public' ? 'storePublicly' : 'store';
|
||||
$path = $temporaryUploadedFile->{$storeMethod}($field->getDirectory(), $field->getDiskName());
|
||||
$this->syncInput($field->getName(), $path, false);
|
||||
}
|
||||
|
||||
$this->resetTemporaryUploadedFiles();
|
||||
}
|
||||
|
||||
public function validate($rules = null, $messages = [], $attributes = [])
|
||||
{
|
||||
try {
|
||||
return parent::validate($rules, $messages, $attributes);
|
||||
} catch (ValidationException $exception) {
|
||||
$fieldToFocus = collect($this->getForm()->getFlatSchema())
|
||||
->first(function ($field) use ($exception) {
|
||||
return ($field instanceof Field &&
|
||||
array_key_exists($field->getName(), $exception->validator->failed())
|
||||
);
|
||||
});
|
||||
|
||||
if ($fieldToFocus) {
|
||||
$this->focusConcealedField($fieldToFocus);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
public function validateOnly($field, $rules = null, $messages = [], $attributes = [])
|
||||
{
|
||||
try {
|
||||
return parent::validateOnly($field, $rules, $messages, $attributes);
|
||||
} catch (ValidationException $exception) {
|
||||
$fieldToFocus = collect($this->getForm()->getFlatSchema())
|
||||
->first(function ($field) use ($exception) {
|
||||
return ($field instanceof Field &&
|
||||
array_key_exists($field->getName(), $exception->validator->failed())
|
||||
);
|
||||
});
|
||||
|
||||
if ($fieldToFocus) {
|
||||
$this->focusConcealedField($fieldToFocus);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
public function validateTemporaryUploadedFiles()
|
||||
{
|
||||
$rules = collect($this->getRules())
|
||||
->filter(function ($conditions, $field) {
|
||||
return Str::of($field)->startsWith('temporaryUploadedFiles.');
|
||||
})
|
||||
->toArray();
|
||||
|
||||
if (! count($rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
return parent::validate($rules);
|
||||
} catch (ValidationException $exception) {
|
||||
$fieldToFocus = collect($this->getForm()->getFlatSchema())
|
||||
->first(function ($component) use ($exception) {
|
||||
return (
|
||||
$component instanceof Field &&
|
||||
array_key_exists(
|
||||
static::getTemporaryUploadedFilePropertyName($component->getName()),
|
||||
$exception->validator->failed()
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
if ($fieldToFocus) {
|
||||
$this->focusConcealedField($fieldToFocus);
|
||||
}
|
||||
|
||||
$this->setErrorBag($exception->validator->errors());
|
||||
|
||||
foreach ($this->getErrorBag()->messages() as $field => $messages) {
|
||||
$field = (string) Str::of($field)->after('temporaryUploadedFiles.');
|
||||
|
||||
foreach ($messages as $message) {
|
||||
$this->addError($field, $message);
|
||||
}
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
@props([
|
||||
'records' => [],
|
||||
'selected' => [],
|
||||
'sortColumn' => null,
|
||||
'sortDirection' => 'asc',
|
||||
'table',
|
||||
])
|
||||
|
||||
<div {{ $attributes->merge(['class' => 'space-y-4']) }}>
|
||||
<div class="items-center justify-between space-y-4 sm:flex sm:space-y-0">
|
||||
<x-tables::delete-selected :disabled="count($selected) === 0" />
|
||||
|
||||
<x-tables::filter :table="$table" />
|
||||
</div>
|
||||
|
||||
<x-tables::table
|
||||
:records="$records"
|
||||
:selected="$selected"
|
||||
:sort-column="$sortColumn"
|
||||
:sort-direction="$sortDirection"
|
||||
:table="$table"
|
||||
/>
|
||||
|
||||
@if ($table->hasPagination())
|
||||
<x-tables::pagination.paginator :paginator="$records" />
|
||||
@endif
|
||||
</div>
|
||||
@@ -3,8 +3,8 @@
|
||||
])
|
||||
|
||||
<div class="flex shadow-sm">
|
||||
@if ($table->isFilterable())
|
||||
<select wire:model="filter" class="text-sm flex-shrink-0 sm:text-base focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50 border-gray-300 {{ $table->isSearchable() ? 'rounded-l' : 'rounded' }}">
|
||||
@if ($this->isFilterable())
|
||||
<select wire:model="filter" class="text-sm flex-shrink-0 sm:text-base focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50 border-gray-300 {{ $this->isSearchable() ? 'rounded-l' : 'rounded' }}">
|
||||
<option>{{ __('tables::table.filter.placeholder') }}</option>
|
||||
|
||||
@foreach ($table->getVisibleFilters() as $filter)
|
||||
@@ -13,13 +13,13 @@
|
||||
</select>
|
||||
@endif
|
||||
|
||||
@if ($table->isSearchable())
|
||||
@if ($this->isSearchable())
|
||||
<div class="relative flex-grow">
|
||||
<input
|
||||
type="search"
|
||||
wire:model="search"
|
||||
placeholder="{{ __('tables::table.search.placeholder') }}"
|
||||
class="text-sm sm:text-base pl-10 block w-full placeholder-gray-400 focus:placeholder-gray-500 placeholder-opacity-100 focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50 border-gray-300 {{ $table->isFilterable() ? 'rounded-r border-l-0' : 'rounded' }}"
|
||||
class="text-sm sm:text-base pl-10 block w-full placeholder-gray-400 focus:placeholder-gray-500 placeholder-opacity-100 focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50 border-gray-300 {{ $this->isFilterable() ? 'rounded-r border-l-0' : 'rounded' }}"
|
||||
/>
|
||||
|
||||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 pointer-events-none" aria-hidden="true">
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
'table',
|
||||
])
|
||||
|
||||
@if ($this->isReorderable())
|
||||
@pushonce('filament-scripts:table')
|
||||
<script src="https://cdn.jsdelivr.net/gh/livewire/sortable@v0.x.x/dist/livewire-sortable.js"></script>
|
||||
@endpushonce
|
||||
@endif
|
||||
|
||||
<div class="overflow-x-auto bg-white rounded shadow-xl">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-200">
|
||||
@@ -21,7 +27,7 @@
|
||||
|
||||
@foreach ($table->getVisibleColumns() as $column)
|
||||
<th class="px-6 py-3 text-left text-gray-600" scope="col">
|
||||
@if ($table->isSortable() && $column->isSortable())
|
||||
@if ($this->isSortable() && $column->isSortable())
|
||||
<button
|
||||
wire:click="sortBy('{{ $column->getName() }}')"
|
||||
type="button"
|
||||
@@ -50,15 +56,23 @@
|
||||
@endforeach
|
||||
|
||||
<th scope="col"></th>
|
||||
|
||||
@if ($this->isReorderable())
|
||||
<th scope="col"></th>
|
||||
@endif
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="text-sm leading-tight divide-y divide-gray-200">
|
||||
<tbody
|
||||
@if ($this->isReorderable()) wire:sortable="reorder" @endif
|
||||
class="text-sm leading-tight divide-y divide-gray-200"
|
||||
>
|
||||
@forelse ($records as $record)
|
||||
<tr
|
||||
wire:key="{{ $record->getKey() }}"
|
||||
wire:loading.class="opacity-50"
|
||||
class="{{ $loop->index % 2 ? 'bg-gray-50' : null }}"
|
||||
@if ($this->isReorderable()) wire:sortable.item="{{ $record->getKey() }}" @endif
|
||||
class="{{ $loop->index % 2 ? 'bg-gray-50' : 'bg-white' }}"
|
||||
>
|
||||
<td class="p-4 whitespace-nowrap">
|
||||
<input
|
||||
@@ -80,12 +94,18 @@
|
||||
{{ $recordAction->render($record) }}
|
||||
@endforeach
|
||||
</td>
|
||||
|
||||
@if ($this->isReorderable())
|
||||
<td wire:sortable.handle class="px-6 border-l cursor-move whitespace-nowrap">
|
||||
<x-heroicon-o-menu-alt-4 class="w-4 h-4 text-gray-500 mx-auto"/>
|
||||
</td>
|
||||
@endif
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap"
|
||||
colspan="{{ count($table->getVisibleColumns()) + 2 }}"
|
||||
colspan="{{ count($table->getVisibleColumns()) + ($this->isReorderable() ? 3 : 2) }}"
|
||||
>
|
||||
<div class="flex items-center justify-center h-16">
|
||||
<p class="font-mono text-xs text-gray-500">
|
||||
|
||||
@@ -26,6 +26,10 @@ class Column
|
||||
|
||||
protected $name;
|
||||
|
||||
protected $searchColumns;
|
||||
|
||||
protected $sortColumns;
|
||||
|
||||
protected $table;
|
||||
|
||||
protected $view;
|
||||
@@ -105,11 +109,63 @@ class Column
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function getLivewire()
|
||||
{
|
||||
return $this->getTable()->getLivewire();
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getRelationshipName()
|
||||
{
|
||||
return Str::of($this->getName())->beforeLast('.');
|
||||
}
|
||||
|
||||
public function getSearchColumns()
|
||||
{
|
||||
$columns = $this->searchColumns;
|
||||
|
||||
if ($columns !== null) {
|
||||
if (! is_array($columns)) {
|
||||
$columns = [$columns];
|
||||
}
|
||||
|
||||
if ($this->isRelationship()) {
|
||||
$columns = collect($columns)
|
||||
->map(fn ($column) => "{$this->getRelationshipName()}.{$column}")
|
||||
->toArray();
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
return [$this->getName()];
|
||||
}
|
||||
|
||||
public function getSortColumns()
|
||||
{
|
||||
$columns = $this->sortColumns;
|
||||
|
||||
if ($columns !== null) {
|
||||
if (! is_array($columns)) {
|
||||
$columns = [$columns];
|
||||
}
|
||||
|
||||
if ($this->isRelationship()) {
|
||||
$columns = collect($columns)
|
||||
->map(fn ($column) => "{$this->getRelationshipName()}.{$column}")
|
||||
->toArray();
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
return [$this->getName()];
|
||||
}
|
||||
|
||||
public function getTable()
|
||||
{
|
||||
return $this->table;
|
||||
@@ -174,6 +230,11 @@ class Column
|
||||
return $this->isPrimary;
|
||||
}
|
||||
|
||||
public function isRelationship()
|
||||
{
|
||||
return Str::of($this->getName())->contains('.');
|
||||
}
|
||||
|
||||
public function isSearchable()
|
||||
{
|
||||
return $this->isSearchable && $this->getValueUsing === null;
|
||||
@@ -253,19 +314,45 @@ class Column
|
||||
]));
|
||||
}
|
||||
|
||||
public function searchable()
|
||||
public function searchable($columns = [])
|
||||
{
|
||||
$this->configure(function () {
|
||||
$this->configure(function () use ($columns) {
|
||||
$this->isSearchable = true;
|
||||
|
||||
if ($columns) {
|
||||
$this->searchColumns($columns);
|
||||
}
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function sortable()
|
||||
public function searchColumns($columns)
|
||||
{
|
||||
$this->configure(function () {
|
||||
$this->configure(function () use ($columns) {
|
||||
$this->searchColumns = $columns;
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function sortable($columns = [])
|
||||
{
|
||||
$this->configure(function () use ($columns) {
|
||||
$this->isSortable = true;
|
||||
|
||||
if ($columns) {
|
||||
$this->sortColumns($columns);
|
||||
}
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function sortColumns($columns)
|
||||
{
|
||||
$this->configure(function () use ($columns) {
|
||||
$this->sortColumns = $columns;
|
||||
});
|
||||
|
||||
return $this;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tables\Concerns;
|
||||
|
||||
trait CanFilterRecords
|
||||
{
|
||||
public $filter;
|
||||
|
||||
public $isFilterable = true;
|
||||
|
||||
public function isFilterable()
|
||||
{
|
||||
return $this->isFilterable && count($this->getTable()->getFilters());
|
||||
}
|
||||
|
||||
public function updatedFilter()
|
||||
{
|
||||
$this->selected = [];
|
||||
|
||||
if (! $this->hasPagination()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
protected function applyFilters($query)
|
||||
{
|
||||
if (
|
||||
! $this->isFilterable() ||
|
||||
$this->filter === '' ||
|
||||
$this->filter === null
|
||||
) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
collect($this->getTable()->getFilters())
|
||||
->filter(fn ($filter) => $filter->getName() === $this->filter)
|
||||
->each(function ($filter) use (&$query) {
|
||||
$query = $filter->apply($query);
|
||||
});
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tables\Concerns;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait CanGetRecords
|
||||
{
|
||||
public function getRecords()
|
||||
{
|
||||
$query = static::getQuery();
|
||||
|
||||
$query = $this->loadRelationships($query);
|
||||
|
||||
$query = $this->applyFilters($query);
|
||||
|
||||
$query = $this->applySearch($query);
|
||||
|
||||
$query = $this->applySorting($query);
|
||||
|
||||
if ($this->hasPagination()) {
|
||||
return $query->paginate($this->recordsPerPage);
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
protected function loadRelationships($query)
|
||||
{
|
||||
$relationships = collect($this->getTable()->getColumns())
|
||||
->filter(fn ($column) => Str::of($column->getName())->contains('.'))
|
||||
->map(fn ($column) => (string) Str::of($column->getName())->beforeLast('.'))
|
||||
->toArray();
|
||||
|
||||
return $query->with($relationships);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tables\Concerns;
|
||||
|
||||
use Livewire\WithPagination;
|
||||
|
||||
trait CanPaginateRecords
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public $hasPagination = true;
|
||||
|
||||
public $recordsPerPage = 25;
|
||||
|
||||
public function hasPagination()
|
||||
{
|
||||
return $this->hasPagination;
|
||||
}
|
||||
|
||||
public function setPage($page)
|
||||
{
|
||||
$this->page = $page;
|
||||
|
||||
$this->selected = [];
|
||||
}
|
||||
|
||||
public function updatedRecordsPerPage()
|
||||
{
|
||||
$this->selected = [];
|
||||
|
||||
if (! $this->hasPagination()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->resetPage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tables\Concerns;
|
||||
|
||||
trait CanReorderRecords
|
||||
{
|
||||
public $isReorderable = true;
|
||||
|
||||
public function isReorderable()
|
||||
{
|
||||
return $this->isReorderable && $this->getTable()->isReorderable();
|
||||
}
|
||||
|
||||
public function reorder($order)
|
||||
{
|
||||
return $this->getTable()->reorder($order);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tables\Concerns;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait CanSearchRecords
|
||||
{
|
||||
public $isSearchable = true;
|
||||
|
||||
public $search = '';
|
||||
|
||||
protected $hasSearchQueriesApplied = false;
|
||||
|
||||
public function getSearch()
|
||||
{
|
||||
return Str::lower($this->search);
|
||||
}
|
||||
|
||||
public function isSearchable()
|
||||
{
|
||||
return $this->isSearchable && collect($this->getTable()->getColumns())
|
||||
->filter(fn ($column) => $column->isSearchable())
|
||||
->count();
|
||||
}
|
||||
|
||||
public function updatedSearch()
|
||||
{
|
||||
$this->selected = [];
|
||||
|
||||
if (! $this->hasPagination()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
protected function applyRelationshipSearch($query, $searchColumn)
|
||||
{
|
||||
$relationshipName = (string) Str::of($searchColumn)->beforeLast('.');
|
||||
$relatedColumnName = (string) Str::of($searchColumn)->afterLast('.');
|
||||
|
||||
return $query->{$this->hasNoSearchQueriesApplied() ? 'whereHas' : 'orWhereHas'}(
|
||||
$relationshipName,
|
||||
fn ($query) => $query->where($relatedColumnName, $this->getSearchOperator(), "%{$this->getSearch()}%"),
|
||||
);
|
||||
}
|
||||
|
||||
protected function applySearch($query)
|
||||
{
|
||||
if (
|
||||
! $this->isSearchable() ||
|
||||
$this->search === '' ||
|
||||
$this->search === null
|
||||
) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
collect($this->getTable()->getColumns())
|
||||
->filter(fn ($column) => $column->isSearchable())
|
||||
->each(function ($column) use (&$query) {
|
||||
foreach ($column->getSearchColumns() as $searchColumn) {
|
||||
if ($this->isRelationshipSearch($searchColumn)) {
|
||||
$query = $this->applyRelationshipSearch($query, $searchColumn);
|
||||
} else {
|
||||
$query = $query->{$this->hasNoSearchQueriesApplied() ? 'where' : 'orWhere'}(
|
||||
fn ($query) => $query->where($searchColumn, $this->getSearchOperator(), "%{$this->getSearch()}%"),
|
||||
);
|
||||
}
|
||||
|
||||
$this->hasSearchQueriesApplied = true;
|
||||
}
|
||||
});
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function getSearchOperator()
|
||||
{
|
||||
return [
|
||||
'pgsql' => 'ilike',
|
||||
][$this->getQuery()->getConnection()->getDriverName()] ?? 'like';
|
||||
}
|
||||
|
||||
protected function hasNoSearchQueriesApplied()
|
||||
{
|
||||
return ! $this->hasSearchQueriesApplied;
|
||||
}
|
||||
|
||||
protected function hasSearchQueriesApplied()
|
||||
{
|
||||
return $this->hasSearchQueriesApplied;
|
||||
}
|
||||
|
||||
protected function isRelationshipSearch($column)
|
||||
{
|
||||
return Str::of($column)->contains('.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tables\Concerns;
|
||||
|
||||
trait CanSelectRecords
|
||||
{
|
||||
public $selected = [];
|
||||
|
||||
public function deleteSelected()
|
||||
{
|
||||
static::getModel()::destroy($this->selected);
|
||||
|
||||
$this->selected = [];
|
||||
}
|
||||
|
||||
public function toggleSelectAll()
|
||||
{
|
||||
$records = $this->getRecords();
|
||||
|
||||
if (! $records->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$keyName = $records->first()->getKeyName();
|
||||
|
||||
if ($records->count() !== count($this->selected)) {
|
||||
$this->selected = $records->pluck($keyName)->all();
|
||||
} else {
|
||||
$this->selected = [];
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleSelected($record)
|
||||
{
|
||||
if (! in_array($record, $this->selected)) {
|
||||
$this->selected[] = $record;
|
||||
} else {
|
||||
$key = array_search($record, $this->selected);
|
||||
|
||||
unset($this->selected[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tables\Concerns;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait CanSortRecords
|
||||
{
|
||||
public $defaultSortColumn;
|
||||
|
||||
public $defaultSortDirection;
|
||||
|
||||
public $isSortable = true;
|
||||
|
||||
public $sortColumn;
|
||||
|
||||
public $sortDirection = 'asc';
|
||||
|
||||
public function getDefaultSort()
|
||||
{
|
||||
return [$this->getDefaultSortColumn(), $this->getDefaultSortDirection()];
|
||||
}
|
||||
|
||||
public function getDefaultSortColumn()
|
||||
{
|
||||
return $this->defaultSortColumn ?? $this->getTable()->getDefaultSortColumn();
|
||||
}
|
||||
|
||||
public function getDefaultSortDirection()
|
||||
{
|
||||
return $this->defaultSortDirection ?? $this->getTable()->getDefaultSortDirection();
|
||||
}
|
||||
|
||||
public function getSorts()
|
||||
{
|
||||
$sortColumn = $this->sortColumn;
|
||||
$sortDirection = $this->sortDirection;
|
||||
|
||||
if (
|
||||
! $this->isSortable() ||
|
||||
$sortColumn === '' ||
|
||||
$sortColumn === null
|
||||
) {
|
||||
if (! $this->hasDefaultSort()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
$this->getDefaultSort(),
|
||||
];
|
||||
}
|
||||
|
||||
$column = collect($this->getTable()->getColumns())
|
||||
->filter(fn ($column) => $column->getName() === $sortColumn)
|
||||
->first();
|
||||
|
||||
if ($column === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return collect($column->getSortColumns())
|
||||
->mapWithKeys(fn ($sortColumn) => [$sortColumn => $sortDirection])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function hasDefaultSort()
|
||||
{
|
||||
return $this->getDefaultSortColumn() !== null;
|
||||
}
|
||||
|
||||
public function isSortable()
|
||||
{
|
||||
return $this->isSortable && collect($this->getTable()->getColumns())
|
||||
->filter(fn ($column) => $column->isSortable())
|
||||
->count();
|
||||
}
|
||||
|
||||
public function sortBy($column)
|
||||
{
|
||||
if ($this->sortColumn === $column) {
|
||||
switch ($this->sortDirection) {
|
||||
case 'asc':
|
||||
$this->sortDirection = 'desc';
|
||||
|
||||
break;
|
||||
case 'desc':
|
||||
$this->sortColumn = null;
|
||||
$this->sortDirection = 'asc';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sortColumn = $column;
|
||||
$this->sortDirection = 'asc';
|
||||
}
|
||||
|
||||
protected function applyRelationshipSort($query, $sort)
|
||||
{
|
||||
[$sortColumn, $sortDirection] = $sort;
|
||||
|
||||
$parentModel = $query->getModel();
|
||||
$relationshipName = (string) Str::of($sortColumn)->beforeLast('.');
|
||||
$relationship = $parentModel->{$relationshipName}();
|
||||
$relatedColumnName = (string) Str::of($sortColumn)->afterLast('.');
|
||||
$relatedModel = $relationship->getModel();
|
||||
|
||||
return $query->orderBy(
|
||||
$relatedModel
|
||||
->query()
|
||||
->select($relatedColumnName)
|
||||
->whereColumn(
|
||||
"{$relatedModel->getTable()}.{$relationship->getOwnerKeyName()}",
|
||||
"{$parentModel->getTable()}.{$relationship->getForeignKeyName()}",
|
||||
),
|
||||
$sortDirection,
|
||||
);
|
||||
}
|
||||
|
||||
protected function applySorting($query)
|
||||
{
|
||||
foreach ($this->getSorts() as $sort) {
|
||||
[$column, $direction] = $sort;
|
||||
|
||||
if ($this->isRelationshipSort($column)) {
|
||||
$query = $this->applyRelationshipSort(
|
||||
$query,
|
||||
[$column, $direction],
|
||||
);
|
||||
} else {
|
||||
$query = $query->orderBy($column, $direction);
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function isRelationshipSort($column)
|
||||
{
|
||||
return Str::of($column)->contains('.');
|
||||
}
|
||||
}
|
||||
@@ -119,6 +119,11 @@ class Filter
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function getLivewire()
|
||||
{
|
||||
return $this->getTable()->getLivewire();
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
|
||||
@@ -2,187 +2,31 @@
|
||||
|
||||
namespace Filament\Tables;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
trait HasTable
|
||||
{
|
||||
use WithPagination;
|
||||
use Concerns\CanFilterRecords;
|
||||
use Concerns\CanGetRecords;
|
||||
use Concerns\CanPaginateRecords;
|
||||
use Concerns\CanReorderRecords;
|
||||
use Concerns\CanSearchRecords;
|
||||
use Concerns\CanSelectRecords;
|
||||
use Concerns\CanSortRecords;
|
||||
|
||||
public $filter = null;
|
||||
protected $table;
|
||||
|
||||
public $recordsPerPage = 25;
|
||||
|
||||
public $search = '';
|
||||
|
||||
public $selected = [];
|
||||
|
||||
public $sortColumn = null;
|
||||
|
||||
public $sortDirection = 'asc';
|
||||
|
||||
public function deleteSelected()
|
||||
public function getTable()
|
||||
{
|
||||
static::getModel()::destroy($this->selected);
|
||||
if ($this->table !== null) {
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
$this->selected = [];
|
||||
return $this->table = $this->table(
|
||||
Table::for($this),
|
||||
);
|
||||
}
|
||||
|
||||
public function getRecords()
|
||||
protected function table(Table $table)
|
||||
{
|
||||
$query = static::getQuery();
|
||||
|
||||
if ($this->getTable()->isFilterable() && $this->filter !== '' && $this->filter !== null) {
|
||||
collect($this->getTable()->getFilters())
|
||||
->filter(fn ($filter) => $filter->getName() === $this->filter)
|
||||
->each(function ($filter) use (&$query) {
|
||||
$query = $filter->apply($query);
|
||||
});
|
||||
}
|
||||
|
||||
if ($this->getTable()->isSearchable() && $this->search !== '' && $this->search !== null) {
|
||||
collect($this->getTable()->getColumns())
|
||||
->filter(fn ($column) => $column->isSearchable())
|
||||
->each(function ($column, $index) use (&$query) {
|
||||
$search = Str::lower($this->search);
|
||||
$searchOperator = [
|
||||
'pgsql' => 'ilike',
|
||||
][$query->getConnection()->getDriverName()] ?? 'like';
|
||||
|
||||
$first = $index === 0;
|
||||
|
||||
if (Str::of($column->getName())->contains('.')) {
|
||||
$relationship = (string) Str::of($column->getName())->beforeLast('.');
|
||||
|
||||
$query = $query->{$first ? 'whereHas' : 'orWhereHas'}(
|
||||
$relationship,
|
||||
function ($query) use ($column, $search, $searchOperator) {
|
||||
$columnName = (string) Str::of($column->getName())->afterLast('.');
|
||||
|
||||
return $query->where($columnName, $searchOperator, "%{$search}%");
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$query = $query->{$first ? 'where' : 'orWhere'}(
|
||||
fn ($query) => $query->where($column->getName(), $searchOperator, "%{$search}%"),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if ($this->getTable()->isSortable() && $this->sortColumn !== '' && $this->sortColumn !== null) {
|
||||
if (Str::of($this->sortColumn)->contains('.')) {
|
||||
$relationship = (string) Str::of($this->sortColumn)->beforeLast('.');
|
||||
|
||||
$query = $query->with([
|
||||
$relationship => function ($query) {
|
||||
return $query->orderBy(
|
||||
(string) Str::of($this->sortColumn)->afterLast('.'),
|
||||
$this->sortDirection,
|
||||
);
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
$query = $query->orderBy($this->sortColumn, $this->sortDirection);
|
||||
}
|
||||
}
|
||||
|
||||
if (! $this->getTable()->hasPagination()) {
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($this->recordsPerPage);
|
||||
}
|
||||
|
||||
public function setPage($page)
|
||||
{
|
||||
$this->page = $page;
|
||||
|
||||
$this->selected = [];
|
||||
}
|
||||
|
||||
public function sortBy($column)
|
||||
{
|
||||
if ($this->sortColumn === $column) {
|
||||
switch ($this->sortDirection) {
|
||||
case 'asc':
|
||||
$this->sortDirection = 'desc';
|
||||
|
||||
break;
|
||||
case 'desc':
|
||||
$this->sortColumn = null;
|
||||
$this->sortDirection = 'asc';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sortColumn = $column;
|
||||
$this->sortDirection = 'asc';
|
||||
}
|
||||
|
||||
public function toggleSelectAll()
|
||||
{
|
||||
$records = $this->getRecords();
|
||||
|
||||
if (! $records->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$keyName = $records->first()->getKeyName();
|
||||
|
||||
if ($records->count() !== count($this->selected)) {
|
||||
$this->selected = $records->pluck($keyName)->all();
|
||||
} else {
|
||||
$this->selected = [];
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleSelected($record)
|
||||
{
|
||||
if (! in_array($record, $this->selected)) {
|
||||
$this->selected[] = $record;
|
||||
} else {
|
||||
$key = array_search($record, $this->selected);
|
||||
|
||||
unset($this->selected[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedFilter()
|
||||
{
|
||||
$this->selected = [];
|
||||
|
||||
if (! $this->getTable()->hasPagination()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function updatedRecordsPerPage()
|
||||
{
|
||||
$this->selected = [];
|
||||
|
||||
if (! $this->getTable()->hasPagination()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function updatedSearch()
|
||||
{
|
||||
$this->selected = [];
|
||||
|
||||
if (! $this->getTable()->hasPagination()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->resetPage();
|
||||
return $table;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,11 @@ class Action
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLivewire()
|
||||
{
|
||||
return $this->getTable()->getLivewire();
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
|
||||
@@ -10,17 +10,13 @@ class Table
|
||||
|
||||
protected $columns = [];
|
||||
|
||||
protected $context;
|
||||
protected $defaultSortColumn;
|
||||
|
||||
protected $defaultSortDirection = 'asc';
|
||||
|
||||
protected $filters = [];
|
||||
|
||||
protected $hasPagination = true;
|
||||
|
||||
protected $isFilterable = true;
|
||||
|
||||
protected $isSearchable = true;
|
||||
|
||||
protected $isSortable = true;
|
||||
protected $livewire;
|
||||
|
||||
protected $primaryColumnAction;
|
||||
|
||||
@@ -28,6 +24,8 @@ class Table
|
||||
|
||||
protected $recordActions = [];
|
||||
|
||||
protected $reorderUsing;
|
||||
|
||||
protected $shouldPrimaryColumnUrlOpenInNewTab = false;
|
||||
|
||||
public function columns($columns)
|
||||
@@ -41,44 +39,24 @@ class Table
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function context($context)
|
||||
public function defaultSort($column, $direction = 'asc')
|
||||
{
|
||||
$this->context = $context;
|
||||
$this->defaultSortColumn($column);
|
||||
$this->defaultSortDirection($direction);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function disableFiltering()
|
||||
public function defaultSortColumn($column)
|
||||
{
|
||||
$this->isFilterable = false;
|
||||
$this->defaultSortColumn = $column;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function disablePagination()
|
||||
public function defaultSortDirection($direction)
|
||||
{
|
||||
$this->hasPagination = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function disableSearching()
|
||||
{
|
||||
$this->isSearchable = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function disableSorting()
|
||||
{
|
||||
$this->isSortable = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function filterable($filterable)
|
||||
{
|
||||
$this->isFilterable = $filterable;
|
||||
$this->defaultSortDirection = $direction;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -94,6 +72,11 @@ class Table
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function for($livewire)
|
||||
{
|
||||
return (new static())->livewire($livewire);
|
||||
}
|
||||
|
||||
public function getColumns()
|
||||
{
|
||||
return $this->columns;
|
||||
@@ -101,7 +84,22 @@ class Table
|
||||
|
||||
public function getContext()
|
||||
{
|
||||
return $this->columns;
|
||||
return get_class($this->getLivewire());
|
||||
}
|
||||
|
||||
public function getDefaultSort()
|
||||
{
|
||||
return [$this->getDefaultSortColumn(), $this->getDefaultSortDirection()];
|
||||
}
|
||||
|
||||
public function getDefaultSortColumn()
|
||||
{
|
||||
return $this->defaultSortColumn;
|
||||
}
|
||||
|
||||
public function getDefaultSortDirection()
|
||||
{
|
||||
return $this->defaultSortDirection;
|
||||
}
|
||||
|
||||
public function getFilters()
|
||||
@@ -109,6 +107,11 @@ class Table
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
public function getLivewire()
|
||||
{
|
||||
return $this->livewire;
|
||||
}
|
||||
|
||||
public function getPrimaryColumnAction($record = null)
|
||||
{
|
||||
$action = $this->primaryColumnAction;
|
||||
@@ -154,33 +157,16 @@ class Table
|
||||
return $filters;
|
||||
}
|
||||
|
||||
public function hasPagination()
|
||||
public function isReorderable()
|
||||
{
|
||||
return $this->hasPagination;
|
||||
return $this->reorderUsing !== null;
|
||||
}
|
||||
|
||||
public function isFilterable()
|
||||
public function livewire($component)
|
||||
{
|
||||
return $this->isFilterable && count($this->getFilters());
|
||||
}
|
||||
$this->livewire = $component;
|
||||
|
||||
public function isSearchable()
|
||||
{
|
||||
return $this->isSearchable && collect($this->getColumns())
|
||||
->filter(fn ($column) => $column->isSearchable())
|
||||
->count();
|
||||
}
|
||||
|
||||
public function isSortable()
|
||||
{
|
||||
return $this->isSortable && collect($this->columns)
|
||||
->filter(fn ($column) => $column->isSortable())
|
||||
->count();
|
||||
}
|
||||
|
||||
public static function make()
|
||||
{
|
||||
return new static();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function openPrimaryColumnUrlInNewTab()
|
||||
@@ -226,9 +212,38 @@ class Table
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function searchable($searchable)
|
||||
public function reorder($order)
|
||||
{
|
||||
$this->searchable = $searchable;
|
||||
$callback = $this->reorderUsing;
|
||||
|
||||
return $callback($order, $this->getLivewire());
|
||||
}
|
||||
|
||||
public function reorderOn($column)
|
||||
{
|
||||
$this->reorderUsing(function ($order) use ($column) {
|
||||
foreach ($order as $item) {
|
||||
$record = $this->getLivewire()::getQuery()->find($item['value']);
|
||||
|
||||
if (! $record) {
|
||||
return;
|
||||
}
|
||||
|
||||
$record->{$column} = $item['order'];
|
||||
$record->save();
|
||||
}
|
||||
});
|
||||
|
||||
if ($this->getDefaultSortColumn() === null) {
|
||||
$this->defaultSortColumn($column);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function reorderUsing($callback)
|
||||
{
|
||||
$this->reorderUsing = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -237,11 +252,4 @@ class Table
|
||||
{
|
||||
return $this->shouldPrimaryColumnUrlOpenInNewTab;
|
||||
}
|
||||
|
||||
public function sortable($sortable)
|
||||
{
|
||||
$this->sortable = $sortable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ module.exports = {
|
||||
plugins: [
|
||||
require("postcss-import"),
|
||||
require("postcss-nested"),
|
||||
require("tailwindcss"),
|
||||
require("@tailwindcss/jit"),
|
||||
require("autoprefixer"),
|
||||
],
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,4 @@
|
||||
// import 'alpinejs'
|
||||
|
||||
// A custom build of Alpine is being used until #1160 (https://github.com/alpinejs/alpine/pull/1160)
|
||||
// is merged in and released.
|
||||
import './alpine.js'
|
||||
import 'alpinejs'
|
||||
import 'focus-visible'
|
||||
import {createPopper} from '@popperjs/core/lib/popper-lite.js'
|
||||
import flip from '@popperjs/core/lib/modifiers/flip.js'
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
],
|
||||
|
||||
'detach' => [
|
||||
'label' => 'حذف المحدد'
|
||||
'label' => 'حذف المحدد',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -34,4 +34,4 @@ return [
|
||||
|
||||
'title' => 'Accedir al meu compte',
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -6,4 +6,4 @@ return [
|
||||
'label' => 'Tancar sessió',
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -33,4 +33,4 @@ return [
|
||||
|
||||
'title' => 'Restablir la contrasenya',
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -38,4 +38,4 @@ return [
|
||||
|
||||
'title' => 'Restablir la contrasenya',
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -38,4 +38,4 @@ return [
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -16,4 +16,4 @@ return [
|
||||
|
||||
'title' => 'Compte',
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -14,4 +14,4 @@ return [
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -10,4 +10,4 @@ return [
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -42,4 +42,4 @@ return [
|
||||
'saved' => 'Desat!',
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
],
|
||||
|
||||
'detach' => [
|
||||
'label' => 'Separar seleccionat'
|
||||
'label' => 'Separar seleccionat',
|
||||
],
|
||||
|
||||
],
|
||||
@@ -128,4 +128,4 @@ return [
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -81,4 +81,4 @@ return [
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
],
|
||||
|
||||
'detach' => [
|
||||
'label' => 'Ausgewählte trennen'
|
||||
'label' => 'Ausgewählte trennen',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -4,10 +4,22 @@ return [
|
||||
|
||||
'buttons' => [
|
||||
|
||||
'cancel' => [
|
||||
'label' => 'Cancel',
|
||||
],
|
||||
|
||||
'create' => [
|
||||
'label' => 'Create',
|
||||
],
|
||||
|
||||
'createAnother' => [
|
||||
'label' => 'Create & Create Another',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'messages' => [
|
||||
'created' => 'Created!',
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -4,6 +4,10 @@ return [
|
||||
|
||||
'buttons' => [
|
||||
|
||||
'cancel' => [
|
||||
'label' => 'Cancel',
|
||||
],
|
||||
|
||||
'delete' => [
|
||||
'label' => 'Delete',
|
||||
],
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
],
|
||||
|
||||
'detach' => [
|
||||
'label' => 'Detach selected'
|
||||
'label' => 'Detach selected',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
],
|
||||
|
||||
'detach' => [
|
||||
'label' => 'Desvincular seleccionado'
|
||||
'label' => 'Desvincular seleccionado',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
],
|
||||
|
||||
'detach' => [
|
||||
'label' => 'Détacher la sélection'
|
||||
'label' => 'Détacher la sélection',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
],
|
||||
|
||||
'detach' => [
|
||||
'label' => 'Putuskan yang terpilih'
|
||||
'label' => 'Putuskan yang terpilih',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
],
|
||||
|
||||
'detach' => [
|
||||
'label' => 'Scollega selezionati'
|
||||
'label' => 'Scollega selezionati',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
],
|
||||
|
||||
'detach' => [
|
||||
'label' => 'კავშირის გაწყვეტა'
|
||||
'label' => 'კავშირის გაწყვეტა',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
],
|
||||
|
||||
'detach' => [
|
||||
'label' => 'Ontkoppel geselecteerde'
|
||||
'label' => 'Ontkoppel geselecteerde',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
],
|
||||
|
||||
'detach' => [
|
||||
'label' => 'Desvincular selecionado'
|
||||
'label' => 'Desvincular selecionado',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -6,4 +6,4 @@ return [
|
||||
'label' => 'Выход',
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -33,4 +33,4 @@ return [
|
||||
|
||||
'title' => 'Восстановить пароль',
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -38,4 +38,4 @@ return [
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -14,4 +14,4 @@ return [
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -10,4 +10,4 @@ return [
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -42,4 +42,4 @@ return [
|
||||
'saved' => 'Сохранено!',
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -22,4 +22,4 @@ return [
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
],
|
||||
|
||||
'detach' => [
|
||||
'label' => 'Отсоединить выбранное'
|
||||
'label' => 'Отсоединить выбранное',
|
||||
],
|
||||
|
||||
],
|
||||
@@ -128,4 +128,4 @@ return [
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -81,4 +81,4 @@ return [
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<x-forms::container :form="$this->getForm()" class="space-y-6">
|
||||
<x-filament::button type="submit" color="primary" class="w-full">
|
||||
<x-filament::loader class="w-6 h-6 absolute left-0 ml-2 pointer-events-none" wire:loading />
|
||||
<form
|
||||
wire:submit.prevent="submit"
|
||||
class="space-y-6"
|
||||
>
|
||||
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
|
||||
|
||||
<x-filament::button type="submit" color="primary" class="w-full">
|
||||
{{ __('filament::auth/login.buttons.submit.label') }}
|
||||
</x-filament::button>
|
||||
</x-forms::container>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<x-forms::container :form="$this->getForm()" class="space-y-6">
|
||||
<x-filament::button type="submit" color="primary" class="w-full">
|
||||
<x-filament::loader class="w-6 h-6 absolute left-0 ml-2 pointer-events-none" wire:loading />
|
||||
<form
|
||||
wire:submit.prevent="submit"
|
||||
class="space-y-6"
|
||||
>
|
||||
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
|
||||
|
||||
<x-filament::button type="submit" color="primary" class="w-full">
|
||||
{{ __('filament::auth.register') }}
|
||||
</x-filament::button>
|
||||
</x-forms::container>
|
||||
</form>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<x-forms::container :form="$this->getForm()" class="space-y-6">
|
||||
<x-filament::button type="submit" color="primary" class="w-full">
|
||||
<x-filament::loader class="w-6 h-6 absolute left-0 ml-2 pointer-events-none" wire:loading />
|
||||
<form
|
||||
wire:submit.prevent="submit"
|
||||
class="space-y-6"
|
||||
>
|
||||
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
|
||||
|
||||
<x-filament::button type="submit" color="primary" class="w-full">
|
||||
{{ __('filament::auth/request-password.buttons.submit.label') }}
|
||||
</x-filament::button>
|
||||
</x-forms::container>
|
||||
</form>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<x-forms::container :form="$this->getForm()" class="space-y-6">
|
||||
<x-filament::button type="submit" color="primary" class="w-full">
|
||||
<x-filament::loader class="w-6 h-6 absolute left-0 ml-2 pointer-events-none" wire:loading />
|
||||
<form
|
||||
wire:submit.prevent="submit"
|
||||
class="space-y-6"
|
||||
>
|
||||
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
|
||||
|
||||
<x-filament::button type="submit" color="primary" class="w-full">
|
||||
{{ __('filament::auth/reset-password.buttons.submit.label') }}
|
||||
</x-filament::button>
|
||||
</x-forms::container>
|
||||
</form>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<main id="content" {{ $attributes->merge(['class' => 'flex-grow pb-4 md:mb-8 px-4 md:px-8']) }}>
|
||||
<main id="content" {{ $attributes->merge(['class' => 'flex-grow max-w-screen-xl mx-auto pb-4 md:mb-8 px-4 md:px-8']) }}>
|
||||
{{ $slot }}
|
||||
</main>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
'title',
|
||||
])
|
||||
|
||||
<header class="flex items-center justify-between p-4 space-x-4 md:py-6 md:px-8">
|
||||
<header class="flex items-center justify-between max-w-screen-xl mx-auto p-4 space-x-4 md:py-6 md:px-8">
|
||||
<div class="flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
@props([
|
||||
'actions' => [],
|
||||
])
|
||||
|
||||
<div class="space-x-3">
|
||||
@foreach ($actions as $button)
|
||||
<x-filament::button
|
||||
:color="$button->getColor()"
|
||||
:href="$button->getUrl()"
|
||||
:type="$button->getType()"
|
||||
:wire:click="$button->getAction() ?? false"
|
||||
>
|
||||
{{ __($button->getLabel()) }}
|
||||
</x-filament::button>
|
||||
@endforeach
|
||||
</div>
|
||||
@@ -3,14 +3,19 @@
|
||||
|
||||
<x-filament::app-content>
|
||||
<x-filament::card>
|
||||
<x-forms::container :form="$this->getForm()" class="space-y-6">
|
||||
<form
|
||||
wire:submit.prevent="save"
|
||||
class="space-y-6"
|
||||
>
|
||||
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
|
||||
|
||||
<x-filament::button
|
||||
type="submit"
|
||||
color="primary"
|
||||
>
|
||||
{{ __('filament::edit-account.buttons.save.label') }}
|
||||
</x-filament::button>
|
||||
</x-forms::container>
|
||||
</form>
|
||||
</x-filament::card>
|
||||
</x-filament::app-content>
|
||||
</div>
|
||||
|
||||
@@ -1,48 +1,20 @@
|
||||
@if (\Filament\Filament::can('viewAny', $this->getModel()))
|
||||
<x-filament::card class="space-y-4">
|
||||
<h3 class="text-lg font-medium leading-tight">
|
||||
{{ __(static::getTitle()) }}
|
||||
</h3>
|
||||
<div>
|
||||
@if (\Filament\Filament::can('viewAny', $this->getModel()))
|
||||
<x-filament::card class="space-y-4">
|
||||
<h3 class="text-lg font-medium leading-tight">
|
||||
{{ __(static::getTitle()) }}
|
||||
</h3>
|
||||
|
||||
<div class="items-center justify-between space-y-4 lg:flex lg:space-y-0 lg:space-x-4">
|
||||
<div class="space-x-3 flex-shink-0">
|
||||
@if ($this->canCreate())
|
||||
<x-filament::button wire:click="openCreate">
|
||||
{{ __(static::$createButtonLabel) }}
|
||||
</x-filament::button>
|
||||
@endif
|
||||
|
||||
@if ($this->canAttach())
|
||||
<x-filament::button wire:click="openAttach">
|
||||
{{ __(static::$attachButtonLabel) }}
|
||||
</x-filament::button>
|
||||
@endif
|
||||
|
||||
@if ($this->canDetachSelected())
|
||||
<x-filament::button
|
||||
wire:click="openDetach"
|
||||
color="danger"
|
||||
:disabled="count($selected) === 0"
|
||||
>
|
||||
{{ __(static::$detachButtonLabel) }}
|
||||
</x-filament::button>
|
||||
@endif
|
||||
|
||||
@if ($this->canDelete())
|
||||
<x-tables::delete-selected :disabled="! $this->canDeleteSelected()" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<x-tables::filter :table="$this->getTable()" />
|
||||
</div>
|
||||
|
||||
<x-tables::table
|
||||
:records="$records"
|
||||
:selected="$selected"
|
||||
:sort-column="$sortColumn"
|
||||
:sort-direction="$sortDirection"
|
||||
:table="$this->getTable()"
|
||||
/>
|
||||
@livewire(static::getComponent('list'), [
|
||||
'canAttach' => $this->canAttach(),
|
||||
'canCreate' => $this->canCreate(),
|
||||
'canDelete' => $this->canDelete(),
|
||||
'canDetach' => $this->canDetach(),
|
||||
'manager' => static::class,
|
||||
'model' => $this->getModel(),
|
||||
'owner' => $this->getOwner(),
|
||||
])
|
||||
</x-filament::card>
|
||||
|
||||
@if ($this->canCreate())
|
||||
<x-filament::modal
|
||||
@@ -52,13 +24,10 @@
|
||||
<x-filament::card class="space-y-5">
|
||||
<x-filament::card-header :title="static::$createModalHeading" />
|
||||
|
||||
@livewire(\Livewire\Livewire::getAlias(Filament\Resources\RelationManager\CreateRecord::class), [
|
||||
'cancelButtonLabel' => static::$createModalCancelButtonLabel,
|
||||
'createAnotherButtonLabel' => static::$createModalCreateAnotherButtonLabel,
|
||||
'createButtonLabel' => static::$createModalCreateButtonLabel,
|
||||
'createdMessage' => static::$createModalCreatedMessage,
|
||||
@livewire(static::getComponent('create'), [
|
||||
'manager' => static::class,
|
||||
'owner' => $this->owner,
|
||||
'model' => $this->getModel(),
|
||||
'owner' => $this->getOwner(),
|
||||
])
|
||||
</x-filament::card>
|
||||
</x-filament::modal>
|
||||
@@ -71,12 +40,10 @@
|
||||
<x-filament::card class="space-y-5">
|
||||
<x-filament::card-header :title="static::$editModalHeading" />
|
||||
|
||||
@livewire(\Livewire\Livewire::getAlias(Filament\Resources\RelationManager\EditRecord::class), [
|
||||
'cancelButtonLabel' => static::$editModalCancelButtonLabel,
|
||||
@livewire(static::getComponent('edit'), [
|
||||
'manager' => static::class,
|
||||
'owner' => $this->owner,
|
||||
'saveButtonLabel' => static::$editModalSaveButtonLabel,
|
||||
'savedMessage' => static::$editModalSavedMessage,
|
||||
'model' => $this->getModel(),
|
||||
'owner' => $this->getOwner(),
|
||||
])
|
||||
</x-filament::card>
|
||||
</x-filament::modal>
|
||||
@@ -89,44 +56,12 @@
|
||||
<x-filament::card class="w-full space-y-5">
|
||||
<x-filament::card-header :title="static::$attachModalHeading" />
|
||||
|
||||
@livewire(\Livewire\Livewire::getAlias(Filament\Resources\RelationManager\AttachRecord::class), [
|
||||
'cancelButtonLabel' => static::$attachModalCancelButtonLabel,
|
||||
'attachAnotherButtonLabel' => static::$attachModalAttachAnotherButtonLabel,
|
||||
'attachButtonLabel' => static::$attachModalAttachButtonLabel,
|
||||
'attachedMessage' => static::$attachModalAttachedMessage,
|
||||
@livewire(static::getComponent('attach'), [
|
||||
'manager' => static::class,
|
||||
'owner' => $this->owner,
|
||||
'owner' => $this->getOwner(),
|
||||
])
|
||||
</x-filament::card>
|
||||
</x-filament::modal>
|
||||
@endif
|
||||
|
||||
@if ($this->canDetachSelected())
|
||||
<x-filament::modal
|
||||
:name="static::class . 'RelationManagerDetachModal'"
|
||||
>
|
||||
<x-filament::card class="space-y-5">
|
||||
<x-filament::card-header :title="static::$detachModalHeading">
|
||||
<p class="text-sm text-gray-500">
|
||||
{{ __(static::$detachModalDescription) }}
|
||||
</p>
|
||||
</x-filament::card-header>
|
||||
|
||||
<div class="space-y-3 sm:space-y-0 sm:space-x-3 sm:flex sm:justify-end">
|
||||
<x-filament::button x-on:click="$dispatch('close', '{{ (string) Str::of(get_class($this))->replace('\\', '\\\\') }}RelationManagerDetachModal')">
|
||||
{{ __(static::$detachModalCancelButtonLabel) }}
|
||||
</x-filament::button>
|
||||
|
||||
<x-filament::button
|
||||
type="button"
|
||||
color="danger"
|
||||
wire:click="detachSelected"
|
||||
>
|
||||
{{ __(static::$detachModalDetachButtonLabel) }}
|
||||
</x-filament::button>
|
||||
</div>
|
||||
</x-filament::card>
|
||||
</x-filament::modal>
|
||||
@endif
|
||||
</x-filament::card>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -5,16 +5,23 @@
|
||||
/>
|
||||
|
||||
<x-filament::app-content>
|
||||
<x-filament::card>
|
||||
<x-forms::container :form="$this->getForm()" class="space-y-6">
|
||||
<x-filament::button
|
||||
type="submit"
|
||||
color="primary"
|
||||
>
|
||||
{{ __(static::$createButtonLabel) }}
|
||||
</x-filament::button>
|
||||
</x-forms::container>
|
||||
</x-filament::card>
|
||||
@php
|
||||
$schema = $this->getForm()->getSchema();
|
||||
@endphp
|
||||
|
||||
<form wire:submit.prevent="create" class="space-y-6">
|
||||
@if ($this->getForm()->hasWrapper())
|
||||
<x-filament::card class="space-y-6">
|
||||
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
|
||||
|
||||
<x-filament::resources.forms.actions :actions="$this->getActions()" />
|
||||
</x-filament::card>
|
||||
@else
|
||||
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
|
||||
|
||||
<x-filament::resources.forms.actions :actions="$this->getActions()" />
|
||||
@endif
|
||||
</form>
|
||||
</x-filament::app-content>
|
||||
|
||||
<div
|
||||
|
||||
@@ -43,16 +43,23 @@
|
||||
</x-filament::app-header>
|
||||
|
||||
<x-filament::app-content class="space-y-6">
|
||||
<x-filament::card>
|
||||
<x-forms::container :form="$this->getForm()" class="space-y-6">
|
||||
<x-filament::button
|
||||
type="submit"
|
||||
color="primary"
|
||||
>
|
||||
{{ __(static::$saveButtonLabel) }}
|
||||
</x-filament::button>
|
||||
</x-forms::container>
|
||||
</x-filament::card>
|
||||
@php
|
||||
$schema = $this->getForm()->getSchema();
|
||||
@endphp
|
||||
|
||||
<form wire:submit.prevent="save" class="space-y-6">
|
||||
@if ($this->getForm()->hasWrapper())
|
||||
<x-filament::card class="space-y-6">
|
||||
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
|
||||
|
||||
<x-filament::resources.forms.actions :actions="$this->getActions()" />
|
||||
</x-filament::card>
|
||||
@else
|
||||
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
|
||||
|
||||
<x-filament::resources.forms.actions :actions="$this->getActions()" />
|
||||
@endif
|
||||
</form>
|
||||
|
||||
<x-filament::resources.relations
|
||||
:owner="$record"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
:table="$this->getTable()"
|
||||
/>
|
||||
|
||||
@if ($this->getTable()->hasPagination())
|
||||
@if ($this->hasPagination())
|
||||
<x-tables::pagination.paginator :paginator="$records" />
|
||||
@endif
|
||||
</x-filament::app-content>
|
||||
|
||||
@@ -1,29 +1,10 @@
|
||||
<div>
|
||||
<x-forms::container :form="$this->getForm()" class="space-y-6">
|
||||
<div class="space-y-3 sm:space-x-3 sm:space-y-0">
|
||||
<x-filament::button
|
||||
type="submit"
|
||||
color="primary"
|
||||
class="w-full sm:w-auto"
|
||||
>
|
||||
{{ __($attachButtonLabel) }}
|
||||
</x-filament::button>
|
||||
<form
|
||||
wire:submit.prevent="attach"
|
||||
class="space-y-6"
|
||||
>
|
||||
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
|
||||
|
||||
<x-filament::button
|
||||
type="button"
|
||||
color="primary"
|
||||
wire:click="attach(true)"
|
||||
class="w-full sm:w-auto"
|
||||
>
|
||||
{{ __($attachAnotherButtonLabel) }}
|
||||
</x-filament::button>
|
||||
|
||||
<x-filament::button
|
||||
x-on:click="$dispatch('close', '{{ (string) Str::of($manager)->replace('\\', '\\\\') }}RelationManagerAttachModal')"
|
||||
class="w-full sm:w-auto"
|
||||
>
|
||||
{{ __($cancelButtonLabel) }}
|
||||
</x-filament::button>
|
||||
</div>
|
||||
</x-forms::container>
|
||||
<x-filament::resources.forms.actions :actions="$this->getActions()" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,10 @@
|
||||
<div>
|
||||
<x-forms::container :form="$this->getForm()" class="space-y-6">
|
||||
<div class="space-x-3">
|
||||
<x-filament::button
|
||||
type="submit"
|
||||
color="primary"
|
||||
>
|
||||
{{ __($createButtonLabel) }}
|
||||
</x-filament::button>
|
||||
<form
|
||||
wire:submit.prevent="create"
|
||||
class="space-y-6"
|
||||
>
|
||||
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
|
||||
|
||||
<x-filament::button
|
||||
type="button"
|
||||
color="primary"
|
||||
wire:click="create(true)"
|
||||
>
|
||||
{{ __($createAnotherButtonLabel) }}
|
||||
</x-filament::button>
|
||||
|
||||
<x-filament::button x-on:click="$dispatch('close', '{{ (string) Str::of($manager)->replace('\\', '\\\\') }}RelationManagerCreateModal')">
|
||||
{{ __($cancelButtonLabel) }}
|
||||
</x-filament::button>
|
||||
</div>
|
||||
</x-forms::container>
|
||||
<x-filament::resources.forms.actions :actions="$this->getActions()" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
<div>
|
||||
<x-forms::container :form="$this->getForm()" class="space-y-6">
|
||||
<div class="space-x-3">
|
||||
<x-filament::button
|
||||
type="submit"
|
||||
color="primary"
|
||||
>
|
||||
{{ __($saveButtonLabel) }}
|
||||
</x-filament::button>
|
||||
<form
|
||||
wire:submit.prevent="save"
|
||||
class="space-y-6"
|
||||
>
|
||||
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
|
||||
|
||||
<x-filament::button x-on:click="$dispatch('close', '{{ (string) Str::of($manager)->replace('\\', '\\\\') }}RelationManagerEditModal')">
|
||||
{{ __($cancelButtonLabel) }}
|
||||
</x-filament::button>
|
||||
</div>
|
||||
</x-forms::container>
|
||||
<x-filament::resources.forms.actions :actions="$this->getActions()" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<div class="space-y-4">
|
||||
<div class="items-center justify-between space-y-4 lg:flex lg:space-y-0 lg:space-x-4">
|
||||
<div class="space-x-3 flex-shink-0">
|
||||
@if ($this->canCreate)
|
||||
<x-filament::button wire:click="openCreate">
|
||||
{{ __($this->manager::$createButtonLabel) }}
|
||||
</x-filament::button>
|
||||
@endif
|
||||
|
||||
@if ($this->canAttach)
|
||||
<x-filament::button wire:click="openAttach">
|
||||
{{ __($this->manager::$attachButtonLabel) }}
|
||||
</x-filament::button>
|
||||
@endif
|
||||
|
||||
@if ($this->canDetach)
|
||||
<x-filament::button
|
||||
wire:click="openDetach"
|
||||
color="danger"
|
||||
:disabled="count($selected) === 0"
|
||||
>
|
||||
{{ __($this->manager::$detachButtonLabel) }}
|
||||
</x-filament::button>
|
||||
@endif
|
||||
|
||||
@if ($this->canDelete)
|
||||
<x-tables::delete-selected :disabled="! $this->canDeleteSelected()" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<x-tables::filter :table="$this->getTable()" />
|
||||
</div>
|
||||
|
||||
<x-tables::table
|
||||
:records="$records"
|
||||
:selected="$selected"
|
||||
:sort-column="$sortColumn"
|
||||
:sort-direction="$sortDirection"
|
||||
:table="$this->getTable()"
|
||||
/>
|
||||
|
||||
@if ($this->canDetach)
|
||||
<x-filament::modal
|
||||
:name="$this->manager . 'RelationManagerDetachModal'"
|
||||
>
|
||||
<x-filament::card class="space-y-5">
|
||||
<x-filament::card-header :title="$this->manager::$detachModalHeading">
|
||||
<p class="text-sm text-gray-500">
|
||||
{{ __($this->manager::$detachModalDescription) }}
|
||||
</p>
|
||||
</x-filament::card-header>
|
||||
|
||||
<div class="space-y-3 sm:space-y-0 sm:space-x-3 sm:flex sm:justify-end">
|
||||
<x-filament::button x-on:click="$dispatch('close', '{{ (string) Str::of($this->manager)->replace('\\', '\\\\') }}RelationManagerDetachModal')">
|
||||
{{ __($this->manager::$detachModalCancelButtonLabel) }}
|
||||
</x-filament::button>
|
||||
|
||||
<x-filament::button
|
||||
type="button"
|
||||
color="danger"
|
||||
wire:click="detachSelected"
|
||||
>
|
||||
{{ __($this->manager::$detachModalDetachButtonLabel) }}
|
||||
</x-filament::button>
|
||||
</div>
|
||||
</x-filament::card>
|
||||
</x-filament::modal>
|
||||
@endif
|
||||
</div>
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Filament\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UpgradeCommand extends Command
|
||||
{
|
||||
|
||||
@@ -21,28 +21,6 @@ class Login extends Component
|
||||
|
||||
public $remember = false;
|
||||
|
||||
public function getForm()
|
||||
{
|
||||
return Form::make()
|
||||
->context(static::class)
|
||||
->schema([
|
||||
Components\TextInput::make('email')
|
||||
->label('filament::auth/login.form.email.label')
|
||||
->email()
|
||||
->autofocus()
|
||||
->autocomplete('email')
|
||||
->required(),
|
||||
Components\TextInput::make('password')
|
||||
->label('filament::auth/login.form.password.label')
|
||||
->hint('[' . __('filament::auth/login.form.password.hint') . '](' . route('filament.auth.password.request') . ')')
|
||||
->password()
|
||||
->autocomplete('current-password')
|
||||
->required(),
|
||||
Components\Checkbox::make('remember')
|
||||
->label('filament::auth/login.form.remember.label'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('filament::auth.login')
|
||||
@@ -74,4 +52,25 @@ class Login extends Component
|
||||
|
||||
return redirect()->intended(route('filament.dashboard'));
|
||||
}
|
||||
|
||||
protected function form(Form $form)
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Components\TextInput::make('email')
|
||||
->label('filament::auth/login.form.email.label')
|
||||
->email()
|
||||
->autofocus()
|
||||
->autocomplete('email')
|
||||
->required(),
|
||||
Components\TextInput::make('password')
|
||||
->label('filament::auth/login.form.password.label')
|
||||
->hint('[' . __('filament::auth/login.form.password.hint') . '](' . route('filament.auth.password.request') . ')')
|
||||
->password()
|
||||
->autocomplete('current-password')
|
||||
->required(),
|
||||
Components\Checkbox::make('remember')
|
||||
->label('filament::auth/login.form.remember.label'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user