調整User 新版 20250513
This commit is contained in:
parent
a8caec12e6
commit
34d4748cd2
20
app/Enums/Traits/HasLabels.php
Normal file
20
app/Enums/Traits/HasLabels.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums\Traits;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
trait HasLabels
|
||||||
|
{
|
||||||
|
public static function options(): Collection
|
||||||
|
{
|
||||||
|
return collect(self::cases())->mapWithKeys(function (self $case) {
|
||||||
|
return [$case->value => $case->labels()];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function labelPowergridFilter(): string
|
||||||
|
{
|
||||||
|
return $this->labels();
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Enums;
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use App\Enums\Traits\HasLabels;
|
||||||
|
|
||||||
enum UserGender: string
|
enum UserGender: string
|
||||||
{
|
{
|
||||||
|
use HasLabels;
|
||||||
|
|
||||||
case Male = 'male';
|
case Male = 'male';
|
||||||
case Female = 'female';
|
case Female = 'female';
|
||||||
case Other = 'other';
|
case Other = 'other';
|
||||||
@ -19,9 +23,4 @@ enum UserGender: string
|
|||||||
self::Unset => __('enums.user.gender.Unset'),
|
self::Unset => __('enums.user.gender.Unset'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public function labelPowergridFilter(): String
|
|
||||||
{
|
|
||||||
return $this -> labels();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Enums;
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use App\Enums\Traits\HasLabels;
|
||||||
|
|
||||||
enum UserStatus: int
|
enum UserStatus: int
|
||||||
{
|
{
|
||||||
|
use HasLabels;
|
||||||
|
|
||||||
case Active = 0; // 正常
|
case Active = 0; // 正常
|
||||||
case Suspended = 1; // 停權
|
case Suspended = 1; // 停權
|
||||||
case Deleting = 2; // 刪除中
|
case Deleting = 2; // 刪除中
|
||||||
@ -17,9 +21,4 @@ enum UserStatus: int
|
|||||||
self::Deleting => __('enums.user.status.Deleting'),
|
self::Deleting => __('enums.user.status.Deleting'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public function labelPowergridFilter(): String
|
|
||||||
{
|
|
||||||
return $this -> labels();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
45
app/Imports/DataImport.php
Normal file
45
app/Imports/DataImport.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Imports;
|
||||||
|
|
||||||
|
use Maatwebsite\Excel\Concerns\ToCollection;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithHeadingRow;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithChunkReading;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Maatwebsite\Excel\Imports\HeadingRowFormatter;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use App\Jobs\ImportUserChunkJob;
|
||||||
|
|
||||||
|
class DataImport implements ToCollection, WithHeadingRow, WithChunkReading
|
||||||
|
{
|
||||||
|
protected int $con=0;
|
||||||
|
protected string $modelName;
|
||||||
|
public function __construct(string $modelName)
|
||||||
|
{
|
||||||
|
HeadingRowFormatter::default('none');
|
||||||
|
$this->modelName= $modelName;
|
||||||
|
}
|
||||||
|
public function collection(Collection $rows)
|
||||||
|
{
|
||||||
|
|
||||||
|
Log::warning('匯入啟動', [
|
||||||
|
'model' => $this->modelName,
|
||||||
|
'rows_id' =>++$this->con,
|
||||||
|
'rows_count' => $rows->count()
|
||||||
|
]);
|
||||||
|
if($this->modelName=='User'){
|
||||||
|
ImportUserChunkJob::dispatch($rows,$this->con);
|
||||||
|
}else{
|
||||||
|
Log::warning('未知的 modelName', ['model' => $this->modelName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function chunkSize(): int
|
||||||
|
{
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function headingRow(): int
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
59
app/Jobs/ImportJob.php
Normal file
59
app/Jobs/ImportJob.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use App\Imports\DataImport;
|
||||||
|
|
||||||
|
class ImportJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected string $modelName;
|
||||||
|
protected string $filePath;
|
||||||
|
public $timeout = 3600;
|
||||||
|
public $tries = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct(string $filePath,string $modelName)
|
||||||
|
{
|
||||||
|
$this->filePath = $filePath;
|
||||||
|
$this->modelName= $modelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
ini_set('memory_limit', '512M'); // ✅ 增加記憶體限制
|
||||||
|
Log::info('[ImportJob] 開始處理檔案:' . $this->filePath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!Storage::exists($this->filePath)) {
|
||||||
|
Log::warning('[ImportJob] 檔案不存在:' . $this->filePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Excel::import(new DataImport($this->modelName), $this->filePath);
|
||||||
|
Log::info('[ImportJob] 已提交所有 chunk 匯入任務。');
|
||||||
|
|
||||||
|
Storage::delete($this->filePath);
|
||||||
|
Log::info('[ImportJob] 已刪除檔案:' . $this->filePath);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error("[ImportJob] 匯入失敗:{$e->getMessage()}", [
|
||||||
|
'file' => $this->filePath,
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
app/Jobs/ImportUserChunkJob.php
Normal file
67
app/Jobs/ImportUserChunkJob.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class ImportUserChunkJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected Collection $rows;
|
||||||
|
protected String $id;
|
||||||
|
|
||||||
|
public function __construct(Collection $rows,String $id)
|
||||||
|
{
|
||||||
|
$this->rows = $rows;
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
Log::warning('匯入啟動', [
|
||||||
|
'model' => "ImportUserChunkJob",
|
||||||
|
'rows_id' =>$this->id,
|
||||||
|
]);
|
||||||
|
$now = now();
|
||||||
|
foreach ($this->rows as $index => $row) {
|
||||||
|
try {
|
||||||
|
$name = $this->normalizeName($row['歌手姓名'] ?? '');
|
||||||
|
|
||||||
|
if (empty($name) || User::where('name', $name)->exists()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 準備 song 資料
|
||||||
|
$toInsert[] = [
|
||||||
|
'name' => $name,
|
||||||
|
'category' => ArtistCategory::tryFrom(trim($row['歌手分類'] ?? '未定義')) ?? ArtistCategory::Unset,
|
||||||
|
'simplified' => $simplified,
|
||||||
|
'phonetic_abbr' => $phoneticAbbr,
|
||||||
|
'pinyin_abbr' => $pinyinAbbr,
|
||||||
|
'strokes_abbr' => $strokesAbbr,
|
||||||
|
'enable' =>trim($row['狀態'] ?? 1),
|
||||||
|
'created_at' => $now,
|
||||||
|
'updated_at' => $now,
|
||||||
|
];
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\Log::error("Row {$index} failed: {$e->getMessage()}", [
|
||||||
|
'row' => $row,
|
||||||
|
'trace' => $e->getTraceAsString()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
User::insert($toInsert);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalizeName(?string $str): string
|
||||||
|
{
|
||||||
|
return strtoupper(mb_convert_kana(trim($str ?? ''), 'as'));
|
||||||
|
}
|
||||||
|
}
|
118
app/Livewire/Admin/ActivityLogTable.php
Normal file
118
app/Livewire/Admin/ActivityLogTable.php
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Admin;
|
||||||
|
|
||||||
|
use App\Models\ActivityLog;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use PowerComponents\LivewirePowerGrid\Column;
|
||||||
|
use PowerComponents\LivewirePowerGrid\Facades\Filter;
|
||||||
|
use PowerComponents\LivewirePowerGrid\Facades\PowerGrid;
|
||||||
|
use PowerComponents\LivewirePowerGrid\PowerGridFields;
|
||||||
|
use PowerComponents\LivewirePowerGrid\PowerGridComponent;
|
||||||
|
use PowerComponents\LivewirePowerGrid\Traits\WithExport;
|
||||||
|
use PowerComponents\LivewirePowerGrid\Components\SetUp\Exportable;
|
||||||
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
|
|
||||||
|
final class ActivityLogTable extends PowerGridComponent
|
||||||
|
{
|
||||||
|
use WithExport;
|
||||||
|
public string $tableName = 'activity-log-table';
|
||||||
|
public bool $canDownload;
|
||||||
|
|
||||||
|
public bool $showFilters = false;
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
config(['livewire-powergrid.filter' => 'outside']);
|
||||||
|
//權限設定
|
||||||
|
$this->canDownload=true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUp(): array
|
||||||
|
{
|
||||||
|
if($this->canDownload ){
|
||||||
|
$this->showCheckBox();
|
||||||
|
}
|
||||||
|
$actions = [];
|
||||||
|
if($this->canDownload){
|
||||||
|
$actions[]=PowerGrid::exportable(fileName: $this->tableName.'-file')
|
||||||
|
->type(Exportable::TYPE_XLS, Exportable::TYPE_CSV);
|
||||||
|
}
|
||||||
|
$header = PowerGrid::header()
|
||||||
|
->withoutLoading()
|
||||||
|
->showToggleColumns();
|
||||||
|
//->showSoftDeletes()
|
||||||
|
//->showSearchInput()
|
||||||
|
$actions[]=$header;
|
||||||
|
$actions[]=PowerGrid::footer()->showPerPage()->showRecordCount();
|
||||||
|
return $actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function datasource(): Builder
|
||||||
|
{
|
||||||
|
return Activity::with(['causer'])->latest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationSearch(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fields(): PowerGridFields
|
||||||
|
{
|
||||||
|
return PowerGrid::fields()
|
||||||
|
->add('id')
|
||||||
|
->add('created_at_formatted', fn (Activity $model) => Carbon::parse($model->created_at)->format('Y-m-d H:i:s'))
|
||||||
|
->add('causer_name', fn (Activity $model) => optional($model->causer)->name)
|
||||||
|
->add('subject_type_label', fn (Activity $model) => class_basename($model->subject_type))
|
||||||
|
->add('subject_type')
|
||||||
|
->add('subject_id')
|
||||||
|
->add('description')
|
||||||
|
->add('changes_str', function (Activity $model) {
|
||||||
|
$old = $model->properties['old'] ?? [];
|
||||||
|
$new = $model->properties['attributes'] ?? [];
|
||||||
|
|
||||||
|
$changes = [];
|
||||||
|
|
||||||
|
foreach ($new as $key => $newValue) {
|
||||||
|
if (in_array($key, ['updated_at', 'created_at'])) continue;
|
||||||
|
$oldValue = $old[$key] ?? '(空)';
|
||||||
|
if ($newValue != $oldValue) {
|
||||||
|
$changes[] = "<strong>{$key}</strong>: {$oldValue} → {$newValue}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//dd(implode('<br>', $changes));
|
||||||
|
return implode('<br>', $changes);
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function columns(): array
|
||||||
|
{
|
||||||
|
$column=[];
|
||||||
|
$column[]=Column::make('時間', 'created_at_formatted', 'created_at')->sortable()->searchable();
|
||||||
|
$column[]=Column::make('操作者', 'causer_name')->sortable()->searchable()->bodyAttribute('whitespace-nowrap');
|
||||||
|
$column[]=Column::make('模型', 'subject_type_label')->sortable()->searchable();
|
||||||
|
$column[]=Column::make('模型 ID', 'subject_id')->sortable()->searchable();
|
||||||
|
$column[]=Column::make('動作', 'description')->sortable()->searchable();
|
||||||
|
$column[]=Column::make('變更內容', 'changes_str')->sortable(false)->searchable(false)->bodyAttribute('whitespace-normal text-sm text-gray-700');
|
||||||
|
return $column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filters(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Filter::datetimepicker('created_at'),
|
||||||
|
Filter::inputText('causer_name')->placeholder('操作者'),
|
||||||
|
Filter::inputText('subject_type_label')->placeholder('模型'),
|
||||||
|
Filter::number('subject_id'),
|
||||||
|
Filter::inputText('description')->placeholder('動作'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -2,15 +2,25 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Admin;
|
namespace App\Livewire\Admin;
|
||||||
|
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use WireUi\Traits\WireUiActions;
|
||||||
|
|
||||||
use Spatie\Permission\Models\Role;
|
use Spatie\Permission\Models\Role;
|
||||||
use Spatie\Permission\Models\Permission;
|
use Spatie\Permission\Models\Permission;
|
||||||
|
|
||||||
class RoleForm extends Component
|
class RoleForm extends Component
|
||||||
{
|
{
|
||||||
|
use WireUiActions;
|
||||||
|
|
||||||
protected $listeners = ['openCreateRoleModal','openEditRoleModal', 'deleteRole'];
|
protected $listeners = ['openCreateRoleModal','openEditRoleModal', 'deleteRole'];
|
||||||
|
|
||||||
|
public bool $canCreate;
|
||||||
|
public bool $canEdit;
|
||||||
|
public bool $canDelect;
|
||||||
|
|
||||||
public $showCreateModal=false;
|
public $showCreateModal=false;
|
||||||
public ?int $roleId = null;
|
public ?int $roleId = null;
|
||||||
public $name = '';
|
public $name = '';
|
||||||
@ -22,6 +32,9 @@ class RoleForm extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->permissions = Permission::all();
|
$this->permissions = Permission::all();
|
||||||
|
$this->canCreate = Auth::user()?->can('role-edit') ?? false;
|
||||||
|
$this->canEdit = Auth::user()?->can('role-edit') ?? false;
|
||||||
|
$this->canDelect = Auth::user()?->can('role-delete') ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function openCreateRoleModal()
|
public function openCreateRoleModal()
|
||||||
@ -47,24 +60,44 @@ class RoleForm extends Component
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if ($this->roleId) {
|
if ($this->roleId) {
|
||||||
$role = Role::findOrFail($this->roleId);
|
if ($this->canEdit) {
|
||||||
$role->update(['name' => $this->name]);
|
$role = Role::findOrFail($this->roleId);
|
||||||
$role->syncPermissions($this->selectedPermissions);
|
$role->update(['name' => $this->name]);
|
||||||
session()->flash('message', '角色已更新');
|
$role->syncPermissions($this->selectedPermissions);
|
||||||
|
$this->notification()->send([
|
||||||
|
'icon' => 'success',
|
||||||
|
'title' => '成功',
|
||||||
|
'description' => '角色已更新',
|
||||||
|
]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$role = Role::create(['name' => $this->name]);
|
if ($this->canCreate) {
|
||||||
$role->syncPermissions($this->selectedPermissions);
|
$role = Role::create(['name' => $this->name]);
|
||||||
session()->flash('message', '角色已新增');
|
$role->syncPermissions($this->selectedPermissions);
|
||||||
|
$this->notification()->send([
|
||||||
|
'icon' => 'success',
|
||||||
|
'title' => '成功',
|
||||||
|
'description' => '角色已新增',
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->resetFields();
|
$this->resetFields();
|
||||||
$this->showCreateModal = false;
|
$this->showCreateModal = false;
|
||||||
|
$this->dispatch('pg:eventRefresh-role-table');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteRole($id)
|
public function deleteRole($id)
|
||||||
{
|
{
|
||||||
Role::findOrFail($id)->delete();
|
if ($this->canDelect) {
|
||||||
session()->flash('message', '角色已刪除');
|
Role::findOrFail($id)->delete();
|
||||||
|
$this->notification()->send([
|
||||||
|
'icon' => 'success',
|
||||||
|
'title' => '成功',
|
||||||
|
'description' => '角色已刪除',
|
||||||
|
]);
|
||||||
|
$this->dispatch('pg:eventRefresh-role-table');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resetFields()
|
public function resetFields()
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
namespace App\Livewire\Admin;
|
namespace App\Livewire\Admin;
|
||||||
|
|
||||||
use Spatie\Permission\Models\Role;
|
use Spatie\Permission\Models\Role;
|
||||||
|
use Spatie\Permission\Models\Permission;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use PowerComponents\LivewirePowerGrid\Button;
|
use PowerComponents\LivewirePowerGrid\Button;
|
||||||
use PowerComponents\LivewirePowerGrid\Column;
|
use PowerComponents\LivewirePowerGrid\Column;
|
||||||
@ -11,28 +13,55 @@ use PowerComponents\LivewirePowerGrid\Facades\Filter;
|
|||||||
use PowerComponents\LivewirePowerGrid\Facades\PowerGrid;
|
use PowerComponents\LivewirePowerGrid\Facades\PowerGrid;
|
||||||
use PowerComponents\LivewirePowerGrid\PowerGridFields;
|
use PowerComponents\LivewirePowerGrid\PowerGridFields;
|
||||||
use PowerComponents\LivewirePowerGrid\PowerGridComponent;
|
use PowerComponents\LivewirePowerGrid\PowerGridComponent;
|
||||||
|
use Livewire\Attributes\On;
|
||||||
|
use WireUi\Traits\WireUiActions;
|
||||||
|
|
||||||
final class RoleTable extends PowerGridComponent
|
final class RoleTable extends PowerGridComponent
|
||||||
{
|
{
|
||||||
|
use WireUiActions;
|
||||||
|
|
||||||
public string $tableName = 'role-table';
|
public string $tableName = 'role-table';
|
||||||
|
public bool $canCreate;
|
||||||
|
public bool $canEdit;
|
||||||
|
public bool $canDelect;
|
||||||
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
config(['livewire-powergrid.filter' => 'outside']);
|
config(['livewire-powergrid.filter' => 'outside']);
|
||||||
|
//權限設定
|
||||||
|
$this->canCreate = Auth::user()?->can('role-edit') ?? false;
|
||||||
|
$this->canEdit = Auth::user()?->can('role-edit') ?? false;
|
||||||
|
$this->canDelect = Auth::user()?->can('role-delete') ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUp(): array
|
public function setUp(): array
|
||||||
{
|
{
|
||||||
//$this->showCheckBox();
|
if($this->canDelect){
|
||||||
|
$this->showCheckBox();
|
||||||
return [
|
}
|
||||||
//PowerGrid::header()
|
$actions = [];
|
||||||
// ->showSearchInput(),
|
$header =PowerGrid::header();
|
||||||
//PowerGrid::footer()
|
if($this->canCreate){
|
||||||
// ->showPerPage()
|
$header->includeViewOnTop('livewire.admin.role-header');
|
||||||
// ->showRecordCount(),
|
}
|
||||||
];
|
$actions[]=$header;
|
||||||
|
$actions[]=PowerGrid::footer()
|
||||||
|
->showPerPage()
|
||||||
|
->showRecordCount();
|
||||||
|
return $actions;
|
||||||
}
|
}
|
||||||
|
public function header(): array
|
||||||
|
{
|
||||||
|
$actions = [];
|
||||||
|
if ($this->canDelect) {
|
||||||
|
$actions[]=Button::add('bulk-delete')
|
||||||
|
->slot('Bulk delete (<span x-text="window.pgBulkActions.count(\'' . $this->tableName . '\')"></span>)')
|
||||||
|
->icon('solid-trash',['id' => 'my-custom-icon-id', 'class' => 'font-bold'])
|
||||||
|
->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
|
||||||
|
->dispatch('bulkDelete.' . $this->tableName, []);
|
||||||
|
}
|
||||||
|
return $actions;
|
||||||
|
}
|
||||||
|
|
||||||
public function datasource(): Builder
|
public function datasource(): Builder
|
||||||
{
|
{
|
||||||
@ -47,27 +76,72 @@ final class RoleTable extends PowerGridComponent
|
|||||||
|
|
||||||
public function fields(): PowerGridFields
|
public function fields(): PowerGridFields
|
||||||
{
|
{
|
||||||
|
$allPermissions = Permission::pluck('name')->sort()->values();
|
||||||
return PowerGrid::fields()
|
return PowerGrid::fields()
|
||||||
->add('id')
|
->add('id')
|
||||||
->add('name')
|
->add('name')
|
||||||
->add('permissions_list' ,fn(Role $model)=> $model->permissions->pluck('name')->implode(', '))
|
->add('permissions_list', function (Role $model) use ($allPermissions) {
|
||||||
->add('created_at_formatted', fn (Role $model) => Carbon::parse($model->created_at)->format('d/m/Y H:i:s'));
|
$rolePermissions = $model->permissions->pluck('name')->sort()->values();
|
||||||
|
|
||||||
|
if ($rolePermissions->count() === $allPermissions->count() && $rolePermissions->values()->all() === $allPermissions->values()->all()) {
|
||||||
|
return 'all';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rolePermissions->implode(', ');
|
||||||
|
})
|
||||||
|
->add('created_at_formatted', fn (Role $model) => Carbon::parse($model->created_at)->format('Y-m-d H:i:s'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function columns(): array
|
public function columns(): array
|
||||||
{
|
{
|
||||||
return [
|
$column=[];
|
||||||
Column::make(__('roles.no'), 'id')->sortable()->searchable(),
|
$column[]=Column::make(__('roles.no'), 'id')->sortable()->searchable();
|
||||||
Column::make(__('roles.name'), 'name')->sortable()->searchable(),
|
$column[]=Column::make(__('roles.name'), 'name')->sortable()->searchable()
|
||||||
Column::make(__('roles.permissions'), 'permissions_list'),
|
->editOnClick(
|
||||||
Column::make('Created at', 'created_at_formatted', 'created_at')->sortable(),
|
hasPermission: $this->canEdit,
|
||||||
Column::action('Action')
|
dataField: 'name',
|
||||||
];
|
fallback: 'N/A',
|
||||||
|
saveOnMouseOut: true
|
||||||
|
);
|
||||||
|
$column[]=Column::make(__('roles.permissions'), 'permissions_list');
|
||||||
|
$column[]=Column::make('Created at', 'created_at_formatted', 'created_at')->sortable();
|
||||||
|
$column[]=Column::action('Action');
|
||||||
|
return $column;
|
||||||
|
}
|
||||||
|
#[On('bulkDelete.{tableName}')]
|
||||||
|
public function bulkDelete(): void
|
||||||
|
{
|
||||||
|
if ($this->canDelect) {
|
||||||
|
$this->js('alert(window.pgBulkActions.get(\'' . $this->tableName . '\'))');
|
||||||
|
if($this->checkboxValues){
|
||||||
|
Role::destroy($this->checkboxValues);
|
||||||
|
$this->js('window.pgBulkActions.clearAll()'); // clear the count on the interface.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[On('onUpdatedEditable')]
|
||||||
|
public function onUpdatedEditable($id, $field, $value): void
|
||||||
|
{
|
||||||
|
if ($field === 'name' && $this->canEdit) {
|
||||||
|
$this->noUpdated($id,$field,$value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function noUpdated($id,$field,$value){
|
||||||
|
$role = Role::find($id);
|
||||||
|
if ($role) {
|
||||||
|
$role->{$field} = $value;
|
||||||
|
$role->save(); // 明確觸發 saving
|
||||||
|
}
|
||||||
|
$this->notification()->send([
|
||||||
|
'icon' => 'success',
|
||||||
|
'title' => $id.'.'.__('roles.'.$field).':'.$value,
|
||||||
|
'description' => '已經寫入',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function filters(): array
|
public function filters(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
Filter::inputText('name')->placeholder(__('roles.name')),
|
||||||
Filter::datetimepicker('created_at'),
|
Filter::datetimepicker('created_at'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -75,18 +149,22 @@ final class RoleTable extends PowerGridComponent
|
|||||||
|
|
||||||
public function actions(Role $row): array
|
public function actions(Role $row): array
|
||||||
{
|
{
|
||||||
return [
|
$actions = [];
|
||||||
Button::add('edit')
|
if ($this->canEdit) {
|
||||||
|
$actions[] =Button::add('edit')
|
||||||
->slot(__('roles.edit'))
|
->slot(__('roles.edit'))
|
||||||
->icon('solid-pencil-square')
|
->icon('solid-pencil-square')
|
||||||
->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
|
->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
|
||||||
->dispatchTo('admin.role-form', 'openEditRoleModal', ['id' => $row->id]),
|
->dispatchTo('admin.role-form', 'openEditRoleModal', ['id' => $row->id]);
|
||||||
Button::add('delete')
|
}
|
||||||
->slot(__('delete'))
|
if($this->canDelect){
|
||||||
|
$actions[] =Button::add('delete')
|
||||||
|
->slot(__('roles.delete'))
|
||||||
->icon('solid-trash')
|
->icon('solid-trash')
|
||||||
->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
|
->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
|
||||||
->dispatchTo('admin.role-form', 'deleteRole', ['id' => $row->id]),
|
->dispatchTo('admin.role-form', 'deleteRole', ['id' => $row->id]);
|
||||||
];
|
}
|
||||||
|
return $actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Admin;
|
namespace App\Livewire\Admin;
|
||||||
|
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use WireUi\Traits\WireUiActions;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Enums\UserGender;
|
use App\Enums\UserGender;
|
||||||
@ -11,17 +15,21 @@ use Spatie\Permission\Models\Role;
|
|||||||
|
|
||||||
class UserForm extends Component
|
class UserForm extends Component
|
||||||
{
|
{
|
||||||
protected $listeners = ['openCreateUserModal','openEditUserModal', 'deleteUser','bulkDeleteUser'];
|
use WireUiActions;
|
||||||
|
|
||||||
|
protected $listeners = ['openModal','closeModal', 'deleteUser'];
|
||||||
|
|
||||||
|
public bool $canCreate;
|
||||||
|
public bool $canEdit;
|
||||||
|
public bool $canDelect;
|
||||||
|
|
||||||
|
public bool $showModal = false;
|
||||||
|
|
||||||
public bool $showCreateModal = false;
|
|
||||||
|
|
||||||
public array $genderOptions =[];
|
public array $genderOptions =[];
|
||||||
public array $statusOptions =[];
|
public array $statusOptions =[];
|
||||||
public $rolesOptions = []; // 所有角色清單
|
public $rolesOptions = []; // 所有角色清單
|
||||||
public $selectedRoles = []; // 表單中選到的權限
|
public $selectedRoles = []; // 表單中選到的權限
|
||||||
|
|
||||||
public ?int $userId = null;
|
public ?int $userId = null;
|
||||||
|
|
||||||
public array $fields = [
|
public array $fields = [
|
||||||
'name' =>'',
|
'name' =>'',
|
||||||
'email' => '',
|
'email' => '',
|
||||||
@ -30,6 +38,7 @@ class UserForm extends Component
|
|||||||
'gender' => 'unset',
|
'gender' => 'unset',
|
||||||
'status' => 0,
|
'status' => 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'fields.name' => 'required|string|max:255',
|
'fields.name' => 'required|string|max:255',
|
||||||
@ -52,48 +61,72 @@ class UserForm extends Component
|
|||||||
'value' => $status->value,
|
'value' => $status->value,
|
||||||
])->toArray();
|
])->toArray();
|
||||||
$this->rolesOptions = Role::all();
|
$this->rolesOptions = Role::all();
|
||||||
|
$this->canCreate = Auth::user()?->can('user-edit') ?? false;
|
||||||
|
$this->canEdit = Auth::user()?->can('user-edit') ?? false;
|
||||||
|
$this->canDelect = Auth::user()?->can('user-delete') ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function openCreateUserModal()
|
public function openModal($id = null)
|
||||||
{
|
{
|
||||||
$this->resetFields();
|
$this->resetFields();
|
||||||
$this->showCreateModal = true;
|
if($id){
|
||||||
|
$obj = User::findOrFail($id);
|
||||||
|
$this->userId = $obj->id;
|
||||||
|
$this->fields = $obj->only(array_keys($this->fields));
|
||||||
|
$this->selectedRoles = $obj->roles()->pluck('id')->toArray();
|
||||||
|
}
|
||||||
|
$this->showModal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function openEditUserModal($id)
|
public function closeModal()
|
||||||
{
|
{
|
||||||
$user = User::findOrFail($id);
|
$this->resetFields();
|
||||||
$this->userId = $user->id;
|
$this->showModal = false;
|
||||||
$this->fields = $user->only(array_keys($this->fields));
|
|
||||||
|
|
||||||
$this->selectedRoles = $user->roles()->pluck('id')->toArray();
|
|
||||||
$this->showCreateModal = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save()
|
public function save()
|
||||||
{
|
{
|
||||||
$this->validate();
|
//$this->validate();
|
||||||
|
|
||||||
if ($this->userId) {
|
if ($this->userId) {
|
||||||
$user = User::findOrFail($this->userId);
|
if ($this->canEdit) {
|
||||||
$user->update($this->fields);
|
$obj = User::findOrFail($this->userId);
|
||||||
$user->syncRoles($this->selectedRoles);
|
$obj->update($this->fields);
|
||||||
session()->flash('message', '使用者已更新');
|
$obj->syncRoles($this->selectedRoles);
|
||||||
|
$this->notification()->send([
|
||||||
|
'icon' => 'success',
|
||||||
|
'title' => '成功',
|
||||||
|
'description' => '使用者已更新',
|
||||||
|
]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$user = User::create($this->fields);
|
if ($this->canCreate) {
|
||||||
$user->syncRoles($this->selectedRoles);
|
$obj = User::create($this->fields);
|
||||||
session()->flash('message', '使用者已新增');
|
$obj->syncRoles($this->selectedRoles);
|
||||||
|
$this->notification()->send([
|
||||||
|
'icon' => 'success',
|
||||||
|
'title' => '成功',
|
||||||
|
'description' => '使用者已新增',
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->resetFields();
|
$this->resetFields();
|
||||||
$this->showCreateModal = false;
|
$this->showModal = false;
|
||||||
$this->dispatch('pg:eventRefresh-user-table');
|
$this->dispatch('pg:eventRefresh-user-table');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteUser($id)
|
public function deleteUser($id)
|
||||||
{
|
{
|
||||||
User::findOrFail($id)->delete();
|
if ($this->canDelect) {
|
||||||
session()->flash('message', '使用者已刪除');
|
User::findOrFail($id)->delete();
|
||||||
|
$this->notification()->send([
|
||||||
|
'icon' => 'success',
|
||||||
|
'title' => '成功',
|
||||||
|
'description' => '使用者已刪除',
|
||||||
|
]);
|
||||||
|
$this->dispatch('pg:eventRefresh-user-table');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resetFields()
|
public function resetFields()
|
||||||
|
114
app/Livewire/Admin/UserImportData.php
Normal file
114
app/Livewire/Admin/UserImportData.php
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Admin;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use WireUi\Traits\WireUiActions;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithFileUploads;
|
||||||
|
use App\Jobs\ImportJob;
|
||||||
|
|
||||||
|
|
||||||
|
class UserImportData extends Component
|
||||||
|
{
|
||||||
|
use WithFileUploads, WireUiActions;
|
||||||
|
|
||||||
|
protected $listeners = ['openModal','closeModal'];
|
||||||
|
|
||||||
|
public bool $canCreate;
|
||||||
|
|
||||||
|
public bool $showModal = false;
|
||||||
|
|
||||||
|
public $file;
|
||||||
|
public string $maxUploadSize;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->canCreate = Auth::user()?->can('user-edit') ?? false;
|
||||||
|
$this->maxUploadSize = $this->getMaxUploadSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function openModal()
|
||||||
|
{
|
||||||
|
$this->showModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function closeModal()
|
||||||
|
{
|
||||||
|
$this->deleteTmpFile(); // 關閉 modal 時刪除暫存檔案
|
||||||
|
$this->reset(['file']);
|
||||||
|
$this->showModal = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function import()
|
||||||
|
{
|
||||||
|
// 檢查檔案是否有上傳
|
||||||
|
$this->validate([
|
||||||
|
'file' => 'required|file|mimes:csv,xlsx,xls'
|
||||||
|
]);
|
||||||
|
if ($this->canCreate) {
|
||||||
|
// 儲存檔案至 storage
|
||||||
|
$path = $this->file->storeAs('imports', uniqid() . '_' . $this->file->getClientOriginalName());
|
||||||
|
|
||||||
|
// 丟到 queue 執行
|
||||||
|
ImportJob::dispatch($path,'User');
|
||||||
|
|
||||||
|
$this->notification()->send([
|
||||||
|
'icon' => 'info',
|
||||||
|
'title' => $this->file->getClientOriginalName(),
|
||||||
|
'description' => '已排入背景匯入作業,請稍候查看結果',
|
||||||
|
]);
|
||||||
|
$this->deleteTmpFile(); // 匯入後也順便刪除 tmp 檔
|
||||||
|
$this->reset(['file']);
|
||||||
|
$this->showModal = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected function deleteTmpFile()
|
||||||
|
{
|
||||||
|
if($this->file!=null){
|
||||||
|
$Path = $this->file->getRealPath();
|
||||||
|
if ($Path && File::exists($Path)) {
|
||||||
|
File::delete($Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMaxUploadSize(): string
|
||||||
|
{
|
||||||
|
$uploadMax = $this->convertPHPSizeToBytes(ini_get('upload_max_filesize'));
|
||||||
|
$postMax = $this->convertPHPSizeToBytes(ini_get('post_max_size'));
|
||||||
|
$max = min($uploadMax, $postMax);
|
||||||
|
return $this->humanFileSize($max);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function convertPHPSizeToBytes(string $s): int
|
||||||
|
{
|
||||||
|
$s = trim($s);
|
||||||
|
$unit = strtolower($s[strlen($s) - 1]);
|
||||||
|
$bytes = (int) $s;
|
||||||
|
switch ($unit) {
|
||||||
|
case 'g':
|
||||||
|
$bytes *= 1024;
|
||||||
|
case 'm':
|
||||||
|
$bytes *= 1024;
|
||||||
|
case 'k':
|
||||||
|
$bytes *= 1024;
|
||||||
|
}
|
||||||
|
return $bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function humanFileSize(int $bytes, int $decimals = 2): string
|
||||||
|
{
|
||||||
|
$sizes = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
$factor = floor((strlen((string) $bytes) - 1) / 3);
|
||||||
|
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . $sizes[$factor];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.admin.user-import-data');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,6 +6,8 @@ use App\Models\User;
|
|||||||
use App\Enums\UserGender;
|
use App\Enums\UserGender;
|
||||||
use App\Enums\UserStatus;
|
use App\Enums\UserStatus;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Blade;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use PowerComponents\LivewirePowerGrid\Button;
|
use PowerComponents\LivewirePowerGrid\Button;
|
||||||
use PowerComponents\LivewirePowerGrid\Column;
|
use PowerComponents\LivewirePowerGrid\Column;
|
||||||
@ -13,48 +15,63 @@ use PowerComponents\LivewirePowerGrid\Facades\Filter;
|
|||||||
use PowerComponents\LivewirePowerGrid\Facades\PowerGrid;
|
use PowerComponents\LivewirePowerGrid\Facades\PowerGrid;
|
||||||
use PowerComponents\LivewirePowerGrid\PowerGridFields;
|
use PowerComponents\LivewirePowerGrid\PowerGridFields;
|
||||||
use PowerComponents\LivewirePowerGrid\PowerGridComponent;
|
use PowerComponents\LivewirePowerGrid\PowerGridComponent;
|
||||||
//use PowerComponents\LivewirePowerGrid\Traits\WithExport;
|
use PowerComponents\LivewirePowerGrid\Traits\WithExport;
|
||||||
use PowerComponents\LivewirePowerGrid\Components\SetUp\Exportable;
|
use PowerComponents\LivewirePowerGrid\Components\SetUp\Exportable;
|
||||||
use PowerComponents\LivewirePowerGrid\Facades\Rule;
|
use PowerComponents\LivewirePowerGrid\Facades\Rule;
|
||||||
use Livewire\Attributes\On;
|
use Livewire\Attributes\On;
|
||||||
|
use WireUi\Traits\WireUiActions;
|
||||||
|
|
||||||
final class UserTable extends PowerGridComponent
|
final class UserTable extends PowerGridComponent
|
||||||
{
|
{
|
||||||
//use WithExport ;
|
use WithExport, WireUiActions;
|
||||||
|
|
||||||
public string $tableName = 'user-table';
|
public string $tableName = 'user-table';
|
||||||
|
|
||||||
public bool $showFilters = false;
|
public bool $showFilters = false;
|
||||||
|
public bool $canCreate;
|
||||||
|
public bool $canEdit;
|
||||||
|
public bool $canDownload;
|
||||||
|
public bool $canDelect;
|
||||||
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
config(['livewire-powergrid.filter' => 'outside']);
|
config(['livewire-powergrid.filter' => 'outside']);
|
||||||
|
//權限設定
|
||||||
|
$this->canCreate = Auth::user()?->can('user-edit') ?? false;
|
||||||
|
$this->canEdit = Auth::user()?->can('user-edit') ?? false;
|
||||||
|
$this->canDownload=Auth::user()?->can('user-delete') ?? false;
|
||||||
|
$this->canDelect = Auth::user()?->can('user-delete') ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function setUp(): array
|
public function setUp(): array
|
||||||
{
|
{
|
||||||
$this->showCheckBox();
|
if($this->canDownload || $this->canDelect){
|
||||||
|
$this->showCheckBox();
|
||||||
return [
|
}
|
||||||
PowerGrid::exportable(fileName: 'my-export-file')
|
$actions = [];
|
||||||
->type(Exportable::TYPE_XLS, Exportable::TYPE_CSV),
|
$actions[] =PowerGrid::exportable(fileName: $this->tableName.'-file')
|
||||||
PowerGrid::header()
|
->type(Exportable::TYPE_XLS, Exportable::TYPE_CSV);
|
||||||
//->showSoftDeletes()
|
$header = PowerGrid::header()
|
||||||
->showToggleColumns()
|
->showToggleColumns();
|
||||||
->showSearchInput(),
|
if($this->canCreate){
|
||||||
PowerGrid::footer()->showPerPage()->showRecordCount(),
|
$header->includeViewOnTop('livewire.admin.user-header');
|
||||||
];
|
}
|
||||||
|
$actions[]=$header;
|
||||||
|
$actions[]=PowerGrid::footer()->showPerPage()->showRecordCount();
|
||||||
|
return $actions;
|
||||||
}
|
}
|
||||||
public function header(): array
|
public function header(): array
|
||||||
{
|
{
|
||||||
return [
|
$actions = [];
|
||||||
Button::add('bulk-delete')
|
if ($this->canDelect) {
|
||||||
|
$actions[]=Button::add('bulk-delete')
|
||||||
->slot('Bulk delete (<span x-text="window.pgBulkActions.count(\'' . $this->tableName . '\')"></span>)')
|
->slot('Bulk delete (<span x-text="window.pgBulkActions.count(\'' . $this->tableName . '\')"></span>)')
|
||||||
->icon('solid-trash',['id' => 'my-custom-icon-id', 'class' => 'font-bold'])
|
->icon('solid-trash',['id' => 'my-custom-icon-id', 'class' => 'font-bold'])
|
||||||
->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
|
->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
|
||||||
->dispatch('bulkDelete.' . $this->tableName, []),
|
->dispatch('bulkDelete.' . $this->tableName, []);
|
||||||
];
|
}
|
||||||
|
return $actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function datasource(): Builder
|
public function datasource(): Builder
|
||||||
@ -75,8 +92,38 @@ final class UserTable extends PowerGridComponent
|
|||||||
->add('email')
|
->add('email')
|
||||||
->add('phone')
|
->add('phone')
|
||||||
->add('birthday_formatted',fn (User $model) => Carbon::parse($model->birthday)->format('Y-m-d'))
|
->add('birthday_formatted',fn (User $model) => Carbon::parse($model->birthday)->format('Y-m-d'))
|
||||||
->add('gender', fn (User $model) => UserGender::from($model->gender)->labels())
|
->add('gender_str', function (User $model) {
|
||||||
->add('status', fn (User $model) => UserStatus::from($model->status)->labels())
|
if ($this->canEdit) {
|
||||||
|
return Blade::render(
|
||||||
|
'<x-select-category type="occurrence" :options=$options :modelId=$modelId :fieldName=$fieldName :selected=$selected/>',
|
||||||
|
[
|
||||||
|
'options' => UserGender::options(),
|
||||||
|
'modelId' => intval($model->id),
|
||||||
|
'fieldName'=>'gender',
|
||||||
|
'selected' => $model->gender->value
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 沒有權限就顯示對應的文字
|
||||||
|
|
||||||
|
return $model->gender->labelPowergridFilter(); // 假設 label() 會回傳顯示文字
|
||||||
|
} )
|
||||||
|
->add('status_str', function (User $model) {
|
||||||
|
if ($this->canEdit) {
|
||||||
|
return Blade::render(
|
||||||
|
'<x-select-category type="occurrence" :options=$options :modelId=$modelId :fieldName=$fieldName :selected=$selected/>',
|
||||||
|
[
|
||||||
|
'options' => UserStatus::options(),
|
||||||
|
'modelId' => intval($model->id),
|
||||||
|
'fieldName'=>'status',
|
||||||
|
'selected' => $model->status->value
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 沒有權限就顯示對應的文字
|
||||||
|
|
||||||
|
return $model->status->labelPowergridFilter(); // 假設 label() 會回傳顯示文字
|
||||||
|
} )
|
||||||
->add('roles' ,fn(User $model)=> $model->roles->pluck('name')->implode(', '))
|
->add('roles' ,fn(User $model)=> $model->roles->pluck('name')->implode(', '))
|
||||||
->add('created_at_formatted', fn (User $model) => Carbon::parse($model->created_at)->format('Y-m-d H:i:s'));
|
->add('created_at_formatted', fn (User $model) => Carbon::parse($model->created_at)->format('Y-m-d H:i:s'));
|
||||||
}
|
}
|
||||||
@ -89,7 +136,7 @@ final class UserTable extends PowerGridComponent
|
|||||||
->sortable()
|
->sortable()
|
||||||
->searchable()
|
->searchable()
|
||||||
->editOnClick(
|
->editOnClick(
|
||||||
hasPermission: true,
|
hasPermission: $this->canEdit,
|
||||||
dataField: 'name',
|
dataField: 'name',
|
||||||
fallback: 'N/A',
|
fallback: 'N/A',
|
||||||
saveOnMouseOut: true
|
saveOnMouseOut: true
|
||||||
@ -98,7 +145,7 @@ final class UserTable extends PowerGridComponent
|
|||||||
->sortable()
|
->sortable()
|
||||||
->searchable()
|
->searchable()
|
||||||
->editOnClick(
|
->editOnClick(
|
||||||
hasPermission: true,
|
hasPermission: $this->canEdit,
|
||||||
dataField: 'email',
|
dataField: 'email',
|
||||||
fallback: 'N/A',
|
fallback: 'N/A',
|
||||||
saveOnMouseOut: true
|
saveOnMouseOut: true
|
||||||
@ -107,20 +154,65 @@ final class UserTable extends PowerGridComponent
|
|||||||
->sortable()
|
->sortable()
|
||||||
->searchable()
|
->searchable()
|
||||||
->editOnClick(
|
->editOnClick(
|
||||||
hasPermission: true,
|
hasPermission: $this->canEdit,
|
||||||
dataField: 'phone',
|
dataField: 'phone',
|
||||||
fallback: 'N/A',
|
fallback: 'N/A',
|
||||||
saveOnMouseOut: true
|
saveOnMouseOut: true
|
||||||
),
|
),
|
||||||
|
|
||||||
Column::make(__('users.gender'), 'gender','users.gender'),
|
Column::make(__('users.gender'), 'gender_str','users.gender'),
|
||||||
Column::make(__('users.birthday'), 'birthday_formatted')->sortable()->searchable(),
|
Column::make(__('users.birthday'), 'birthday_formatted')->sortable()->searchable(),
|
||||||
Column::make(__('users.status'), 'status','users.status'),
|
Column::make(__('users.status'), 'status_str','users.status'),
|
||||||
Column::make(__('users.role'), 'roles'),
|
Column::make(__('users.role'), 'roles'),
|
||||||
Column::make('建立時間', 'created_at_formatted', 'created_at')->sortable(),
|
Column::make('建立時間', 'created_at_formatted', 'created_at')->sortable(),
|
||||||
Column::action('操作')
|
Column::action('操作')
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
#[On('bulkDelete.{tableName}')]
|
||||||
|
public function bulkDelete(): void
|
||||||
|
{
|
||||||
|
if ($this->canDelect) {
|
||||||
|
$this->js('alert(window.pgBulkActions.get(\'' . $this->tableName . '\'))');
|
||||||
|
if($this->checkboxValues){
|
||||||
|
User::destroy($this->checkboxValues);
|
||||||
|
$this->js('window.pgBulkActions.clearAll()'); // clear the count on the interface.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[On('categoryChanged')]
|
||||||
|
public function categoryChanged($value,$fieldName, $modelId): void
|
||||||
|
{
|
||||||
|
// dd($value,$fieldName, $modelId);
|
||||||
|
if (in_array($fieldName,['gender','status']) && $this->canEdit) {
|
||||||
|
$this->noUpdated($modelId,$fieldName,$value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[On('onUpdatedEditable')]
|
||||||
|
public function onUpdatedEditable($id, $field, $value): void
|
||||||
|
{
|
||||||
|
if (in_array($field,['name','email','phone']) && $this->canEdit) {
|
||||||
|
$this->noUpdated($id,$field,$value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[On('onUpdatedToggleable')]
|
||||||
|
public function onUpdatedToggleable($id, $field, $value): void
|
||||||
|
{
|
||||||
|
if (in_array($field,[]) && $this->canEdit) {
|
||||||
|
$this->noUpdated($id,$field,$value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function noUpdated($id,$field,$value){
|
||||||
|
$user = User::find($id);
|
||||||
|
if ($user) {
|
||||||
|
$user->{$field} = $value;
|
||||||
|
$user->save(); // 明確觸發 saving
|
||||||
|
}
|
||||||
|
$this->notification()->send([
|
||||||
|
'icon' => 'success',
|
||||||
|
'title' => $id.'.'.__('users.'.$field).':'.$value,
|
||||||
|
'description' => '已經寫入',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function filters(): array
|
public function filters(): array
|
||||||
{
|
{
|
||||||
@ -128,11 +220,11 @@ final class UserTable extends PowerGridComponent
|
|||||||
Filter::inputText('name')->placeholder(__('users.name')),
|
Filter::inputText('name')->placeholder(__('users.name')),
|
||||||
Filter::inputText('email')->placeholder('Email'),
|
Filter::inputText('email')->placeholder('Email'),
|
||||||
Filter::inputText('phone')->placeholder(__('users.phone')),
|
Filter::inputText('phone')->placeholder(__('users.phone')),
|
||||||
Filter::enumSelect('gender','users.gender')
|
Filter::enumSelect('gender_str','users.gender')
|
||||||
->datasource(UserGender::cases())
|
->datasource(UserGender::cases())
|
||||||
->optionLabel('users.gender'),
|
->optionLabel('users.gender'),
|
||||||
Filter::datepicker('birthday'),
|
Filter::datepicker('birthday'),
|
||||||
Filter::enumSelect('status', 'users.status')
|
Filter::enumSelect('status_str', 'users.status')
|
||||||
->datasource(UserStatus::cases())
|
->datasource(UserStatus::cases())
|
||||||
->optionLabel('users.status'),
|
->optionLabel('users.status'),
|
||||||
Filter::datetimepicker('created_at'),
|
Filter::datetimepicker('created_at'),
|
||||||
@ -141,47 +233,22 @@ final class UserTable extends PowerGridComponent
|
|||||||
|
|
||||||
public function actions(User $row): array
|
public function actions(User $row): array
|
||||||
{
|
{
|
||||||
return [
|
$actions = [];
|
||||||
|
if ($this->canEdit) {
|
||||||
Button::add('edit')
|
$actions[]=Button::add('edit')
|
||||||
->slot(__('users.edit'))
|
->slot(__('users.edit'))
|
||||||
->icon('solid-pencil-square')
|
->icon('solid-pencil-square')
|
||||||
->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
|
->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
|
||||||
->dispatchTo('admin.user-form', 'openEditUserModal', ['id' => $row->id]),
|
->dispatchTo('admin.user-form', 'openModal', ['id' => $row->id]);
|
||||||
Button::add('delete')
|
}
|
||||||
|
if($this->canDelect){
|
||||||
|
$actions[]=Button::add('delete')
|
||||||
->slot(__('users.delete'))
|
->slot(__('users.delete'))
|
||||||
->icon('solid-trash')
|
->icon('solid-trash')
|
||||||
->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
|
->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
|
||||||
->dispatchTo('admin.user-form', 'deleteUser', ['id' => $row->id]),
|
->dispatchTo('admin.user-form', 'deleteUser', ['id' => $row->id]);
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onUpdatedEditable($id, $field, $value): void
|
|
||||||
{
|
|
||||||
$updated = User::query()->where('id', $id)->update([
|
|
||||||
$field => $value,
|
|
||||||
]);
|
|
||||||
if ($updated) {
|
|
||||||
$this->fillData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public function onUpdatedToggleable($id, $field, $value): void
|
|
||||||
{
|
|
||||||
$updated = User::query()->where('id', $id)->update([
|
|
||||||
$field => $value,
|
|
||||||
]);
|
|
||||||
if ($updated) {
|
|
||||||
$this->fillData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[On('bulkDelete.{tableName}')]
|
|
||||||
public function bulkDelete(): void
|
|
||||||
{
|
|
||||||
$this->js('alert(window.pgBulkActions.get(\'' . $this->tableName . '\'))');
|
|
||||||
if($this->checkboxValues){
|
|
||||||
User::destroy($this->checkboxValues);
|
|
||||||
$this->js('window.pgBulkActions.clearAll()'); // clear the count on the interface.
|
|
||||||
}
|
}
|
||||||
|
return $actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,11 +7,13 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Spatie\Permission\Traits\HasRoles;
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
|
use App\Traits\LogsModelActivity;
|
||||||
|
use Spatie\Activitylog\Traits\CausesActivity;
|
||||||
|
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||||
use HasFactory, Notifiable, HasRoles;
|
use HasFactory, Notifiable, HasRoles, LogsModelActivity,CausesActivity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
@ -48,7 +50,9 @@ class User extends Authenticatable
|
|||||||
return [
|
return [
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
'birthday' => 'date'
|
'birthday' => 'date',
|
||||||
|
'gender' => \App\Enums\UserGender::class,
|
||||||
|
'status' => \App\Enums\UserStatus::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
app/Traits/LogsModelActivity.php
Normal file
23
app/Traits/LogsModelActivity.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use Spatie\Activitylog\LogOptions;
|
||||||
|
use Spatie\Activitylog\Traits\LogsActivity;
|
||||||
|
|
||||||
|
trait LogsModelActivity
|
||||||
|
{
|
||||||
|
use LogsActivity;
|
||||||
|
|
||||||
|
public function getActivitylogOptions(): LogOptions
|
||||||
|
{
|
||||||
|
return LogOptions::defaults()
|
||||||
|
->useLogName(strtolower(class_basename(static::class)))
|
||||||
|
->logOnly($this->getFillable())
|
||||||
|
->logOnlyDirty()
|
||||||
|
->dontSubmitEmptyLogs()
|
||||||
|
->setDescriptionForEvent(function (string $eventName) {
|
||||||
|
return class_basename(static::class) . " 已 {$eventName}";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
27
app/View/Components/SelectCategory.php
Normal file
27
app/View/Components/SelectCategory.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class SelectCategory extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new component instance.
|
||||||
|
*/
|
||||||
|
public function __construct(public Collection $options, public int $modelId,public string $fieldName, public string $selected)
|
||||||
|
{
|
||||||
|
//dd($options,$modelId,$fieldName,$selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represent the component.
|
||||||
|
*/
|
||||||
|
public function render(): View|Closure|string
|
||||||
|
{
|
||||||
|
return view('components.select-category');
|
||||||
|
}
|
||||||
|
}
|
@ -11,8 +11,10 @@
|
|||||||
"laravel/tinker": "^2.10.1",
|
"laravel/tinker": "^2.10.1",
|
||||||
"livewire/livewire": "^3.4",
|
"livewire/livewire": "^3.4",
|
||||||
"livewire/volt": "^1.7.0",
|
"livewire/volt": "^1.7.0",
|
||||||
|
"maatwebsite/excel": "^3.1",
|
||||||
"openspout/openspout": "^4.0",
|
"openspout/openspout": "^4.0",
|
||||||
"power-components/livewire-powergrid": "^6.3",
|
"power-components/livewire-powergrid": "^6.3",
|
||||||
|
"spatie/laravel-activitylog": "^4.10",
|
||||||
"spatie/laravel-permission": "^6.17",
|
"spatie/laravel-permission": "^6.17",
|
||||||
"wire-elements/modal": "^2.0",
|
"wire-elements/modal": "^2.0",
|
||||||
"wireui/wireui": "^2.4"
|
"wireui/wireui": "^2.4"
|
||||||
|
686
composer.lock
generated
686
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "357c14558fec81a259ca41d9f582db48",
|
"content-hash": "6ee6a3628fc42da4568e5a2564abd9f0",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
@ -135,6 +135,166 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-02-09T16:56:22+00:00"
|
"time": "2024-02-09T16:56:22+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "composer/pcre",
|
||||||
|
"version": "3.3.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/composer/pcre.git",
|
||||||
|
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
||||||
|
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.4 || ^8.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"phpstan/phpstan": "<1.11.10"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^1.12 || ^2",
|
||||||
|
"phpstan/phpstan-strict-rules": "^1 || ^2",
|
||||||
|
"phpunit/phpunit": "^8 || ^9"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"phpstan": {
|
||||||
|
"includes": [
|
||||||
|
"extension.neon"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "3.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Composer\\Pcre\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jordi Boggiano",
|
||||||
|
"email": "j.boggiano@seld.be",
|
||||||
|
"homepage": "http://seld.be"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
|
||||||
|
"keywords": [
|
||||||
|
"PCRE",
|
||||||
|
"preg",
|
||||||
|
"regex",
|
||||||
|
"regular expression"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/composer/pcre/issues",
|
||||||
|
"source": "https://github.com/composer/pcre/tree/3.3.2"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://packagist.com",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/composer",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-11-12T16:29:46+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "composer/semver",
|
||||||
|
"version": "3.4.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/composer/semver.git",
|
||||||
|
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
|
||||||
|
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^5.3.2 || ^7.0 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^1.11",
|
||||||
|
"symfony/phpunit-bridge": "^3 || ^7"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "3.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Composer\\Semver\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nils Adermann",
|
||||||
|
"email": "naderman@naderman.de",
|
||||||
|
"homepage": "http://www.naderman.de"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jordi Boggiano",
|
||||||
|
"email": "j.boggiano@seld.be",
|
||||||
|
"homepage": "http://seld.be"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rob Bast",
|
||||||
|
"email": "rob.bast@gmail.com",
|
||||||
|
"homepage": "http://robbast.nl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Semver library that offers utilities, version constraint parsing and validation.",
|
||||||
|
"keywords": [
|
||||||
|
"semantic",
|
||||||
|
"semver",
|
||||||
|
"validation",
|
||||||
|
"versioning"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"irc": "ircs://irc.libera.chat:6697/composer",
|
||||||
|
"issues": "https://github.com/composer/semver/issues",
|
||||||
|
"source": "https://github.com/composer/semver/tree/3.4.3"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://packagist.com",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/composer",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-09-19T14:15:21+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "dflydev/dot-access-data",
|
"name": "dflydev/dot-access-data",
|
||||||
"version": "v3.0.3",
|
"version": "v3.0.3",
|
||||||
@ -510,6 +670,67 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-03-06T22:45:56+00:00"
|
"time": "2025-03-06T22:45:56+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ezyang/htmlpurifier",
|
||||||
|
"version": "v4.18.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ezyang/htmlpurifier.git",
|
||||||
|
"reference": "cb56001e54359df7ae76dc522d08845dc741621b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/cb56001e54359df7ae76dc522d08845dc741621b",
|
||||||
|
"reference": "cb56001e54359df7ae76dc522d08845dc741621b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"cerdic/css-tidy": "^1.7 || ^2.0",
|
||||||
|
"simpletest/simpletest": "dev-master"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
|
||||||
|
"ext-bcmath": "Used for unit conversion and imagecrash protection",
|
||||||
|
"ext-iconv": "Converts text to and from non-UTF-8 encodings",
|
||||||
|
"ext-tidy": "Used for pretty-printing HTML"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"library/HTMLPurifier.composer.php"
|
||||||
|
],
|
||||||
|
"psr-0": {
|
||||||
|
"HTMLPurifier": "library/"
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/library/HTMLPurifier/Language/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"LGPL-2.1-or-later"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Edward Z. Yang",
|
||||||
|
"email": "admin@htmlpurifier.org",
|
||||||
|
"homepage": "http://ezyang.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Standards compliant HTML filter written in PHP",
|
||||||
|
"homepage": "http://htmlpurifier.org/",
|
||||||
|
"keywords": [
|
||||||
|
"html"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/ezyang/htmlpurifier/issues",
|
||||||
|
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.18.0"
|
||||||
|
},
|
||||||
|
"time": "2024-11-01T03:51:45+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "fruitcake/php-cors",
|
"name": "fruitcake/php-cors",
|
||||||
"version": "v1.3.0",
|
"version": "v1.3.0",
|
||||||
@ -2154,6 +2375,272 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-04-08T15:13:36+00:00"
|
"time": "2025-04-08T15:13:36+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "maatwebsite/excel",
|
||||||
|
"version": "3.1.64",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/SpartnerNL/Laravel-Excel.git",
|
||||||
|
"reference": "e25d44a2d91da9179cd2d7fec952313548597a79"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/e25d44a2d91da9179cd2d7fec952313548597a79",
|
||||||
|
"reference": "e25d44a2d91da9179cd2d7fec952313548597a79",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"composer/semver": "^3.3",
|
||||||
|
"ext-json": "*",
|
||||||
|
"illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0",
|
||||||
|
"php": "^7.0||^8.0",
|
||||||
|
"phpoffice/phpspreadsheet": "^1.29.9",
|
||||||
|
"psr/simple-cache": "^1.0||^2.0||^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"laravel/scout": "^7.0||^8.0||^9.0||^10.0",
|
||||||
|
"orchestra/testbench": "^6.0||^7.0||^8.0||^9.0||^10.0",
|
||||||
|
"predis/predis": "^1.1"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"aliases": {
|
||||||
|
"Excel": "Maatwebsite\\Excel\\Facades\\Excel"
|
||||||
|
},
|
||||||
|
"providers": [
|
||||||
|
"Maatwebsite\\Excel\\ExcelServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Maatwebsite\\Excel\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Patrick Brouwers",
|
||||||
|
"email": "patrick@spartner.nl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Supercharged Excel exports and imports in Laravel",
|
||||||
|
"keywords": [
|
||||||
|
"PHPExcel",
|
||||||
|
"batch",
|
||||||
|
"csv",
|
||||||
|
"excel",
|
||||||
|
"export",
|
||||||
|
"import",
|
||||||
|
"laravel",
|
||||||
|
"php",
|
||||||
|
"phpspreadsheet"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/SpartnerNL/Laravel-Excel/issues",
|
||||||
|
"source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.64"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://laravel-excel.com/commercial-support",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/patrickbrouwers",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-02-24T11:12:50+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maennchen/zipstream-php",
|
||||||
|
"version": "3.1.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/maennchen/ZipStream-PHP.git",
|
||||||
|
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f",
|
||||||
|
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"ext-zlib": "*",
|
||||||
|
"php-64bit": "^8.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"brianium/paratest": "^7.7",
|
||||||
|
"ext-zip": "*",
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.16",
|
||||||
|
"guzzlehttp/guzzle": "^7.5",
|
||||||
|
"mikey179/vfsstream": "^1.6",
|
||||||
|
"php-coveralls/php-coveralls": "^2.5",
|
||||||
|
"phpunit/phpunit": "^11.0",
|
||||||
|
"vimeo/psalm": "^6.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"guzzlehttp/psr7": "^2.4",
|
||||||
|
"psr/http-message": "^2.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"ZipStream\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Paul Duncan",
|
||||||
|
"email": "pabs@pablotron.org"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jonatan Männchen",
|
||||||
|
"email": "jonatan@maennchen.ch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jesse Donat",
|
||||||
|
"email": "donatj@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "András Kolesár",
|
||||||
|
"email": "kolesar@kolesar.hu"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
|
||||||
|
"keywords": [
|
||||||
|
"stream",
|
||||||
|
"zip"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
|
||||||
|
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/maennchen",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-01-27T12:07:53+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "markbaker/complex",
|
||||||
|
"version": "3.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/MarkBaker/PHPComplex.git",
|
||||||
|
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
|
||||||
|
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
|
||||||
|
"phpcompatibility/php-compatibility": "^9.3",
|
||||||
|
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
|
||||||
|
"squizlabs/php_codesniffer": "^3.7"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Complex\\": "classes/src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Mark Baker",
|
||||||
|
"email": "mark@lange.demon.co.uk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP Class for working with complex numbers",
|
||||||
|
"homepage": "https://github.com/MarkBaker/PHPComplex",
|
||||||
|
"keywords": [
|
||||||
|
"complex",
|
||||||
|
"mathematics"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
|
||||||
|
"source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
|
||||||
|
},
|
||||||
|
"time": "2022-12-06T16:21:08+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "markbaker/matrix",
|
||||||
|
"version": "3.0.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/MarkBaker/PHPMatrix.git",
|
||||||
|
"reference": "728434227fe21be27ff6d86621a1b13107a2562c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
|
||||||
|
"reference": "728434227fe21be27ff6d86621a1b13107a2562c",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.1 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
|
||||||
|
"phpcompatibility/php-compatibility": "^9.3",
|
||||||
|
"phpdocumentor/phpdocumentor": "2.*",
|
||||||
|
"phploc/phploc": "^4.0",
|
||||||
|
"phpmd/phpmd": "2.*",
|
||||||
|
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
|
||||||
|
"sebastian/phpcpd": "^4.0",
|
||||||
|
"squizlabs/php_codesniffer": "^3.7"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Matrix\\": "classes/src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Mark Baker",
|
||||||
|
"email": "mark@demon-angel.eu"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP Class for working with matrices",
|
||||||
|
"homepage": "https://github.com/MarkBaker/PHPMatrix",
|
||||||
|
"keywords": [
|
||||||
|
"mathematics",
|
||||||
|
"matrix",
|
||||||
|
"vector"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
|
||||||
|
"source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
|
||||||
|
},
|
||||||
|
"time": "2022-12-02T22:17:43+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "monolog/monolog",
|
"name": "monolog/monolog",
|
||||||
"version": "3.9.0",
|
"version": "3.9.0",
|
||||||
@ -2749,6 +3236,112 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-03-11T14:40:46+00:00"
|
"time": "2025-03-11T14:40:46+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "phpoffice/phpspreadsheet",
|
||||||
|
"version": "1.29.10",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
|
||||||
|
"reference": "c80041b1628c4f18030407134fe88303661d4e4e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/c80041b1628c4f18030407134fe88303661d4e4e",
|
||||||
|
"reference": "c80041b1628c4f18030407134fe88303661d4e4e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"composer/pcre": "^1||^2||^3",
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-dom": "*",
|
||||||
|
"ext-fileinfo": "*",
|
||||||
|
"ext-gd": "*",
|
||||||
|
"ext-iconv": "*",
|
||||||
|
"ext-libxml": "*",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"ext-simplexml": "*",
|
||||||
|
"ext-xml": "*",
|
||||||
|
"ext-xmlreader": "*",
|
||||||
|
"ext-xmlwriter": "*",
|
||||||
|
"ext-zip": "*",
|
||||||
|
"ext-zlib": "*",
|
||||||
|
"ezyang/htmlpurifier": "^4.15",
|
||||||
|
"maennchen/zipstream-php": "^2.1 || ^3.0",
|
||||||
|
"markbaker/complex": "^3.0",
|
||||||
|
"markbaker/matrix": "^3.0",
|
||||||
|
"php": "^7.4 || ^8.0",
|
||||||
|
"psr/http-client": "^1.0",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
||||||
|
"dompdf/dompdf": "^1.0 || ^2.0 || ^3.0",
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.2",
|
||||||
|
"mitoteam/jpgraph": "^10.3",
|
||||||
|
"mpdf/mpdf": "^8.1.1",
|
||||||
|
"phpcompatibility/php-compatibility": "^9.3",
|
||||||
|
"phpstan/phpstan": "^1.1",
|
||||||
|
"phpstan/phpstan-phpunit": "^1.0",
|
||||||
|
"phpunit/phpunit": "^8.5 || ^9.0",
|
||||||
|
"squizlabs/php_codesniffer": "^3.7",
|
||||||
|
"tecnickcom/tcpdf": "^6.5"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
|
||||||
|
"ext-intl": "PHP Internationalization Functions",
|
||||||
|
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
|
||||||
|
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
|
||||||
|
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Maarten Balliauw",
|
||||||
|
"homepage": "https://blog.maartenballiauw.be"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mark Baker",
|
||||||
|
"homepage": "https://markbakeruk.net"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Franck Lefevre",
|
||||||
|
"homepage": "https://rootslabs.net"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Erik Tilt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Adrien Crivelli"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
|
||||||
|
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
|
||||||
|
"keywords": [
|
||||||
|
"OpenXML",
|
||||||
|
"excel",
|
||||||
|
"gnumeric",
|
||||||
|
"ods",
|
||||||
|
"php",
|
||||||
|
"spreadsheet",
|
||||||
|
"xls",
|
||||||
|
"xlsx"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
|
||||||
|
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.10"
|
||||||
|
},
|
||||||
|
"time": "2025-02-08T02:56:14+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoption/phpoption",
|
"name": "phpoption/phpoption",
|
||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
@ -3605,6 +4198,97 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-04-27T21:32:50+00:00"
|
"time": "2024-04-27T21:32:50+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spatie/laravel-activitylog",
|
||||||
|
"version": "4.10.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/spatie/laravel-activitylog.git",
|
||||||
|
"reference": "466f30f7245fe3a6e328ad5e6812bd43b4bddea5"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/466f30f7245fe3a6e328ad5e6812bd43b4bddea5",
|
||||||
|
"reference": "466f30f7245fe3a6e328ad5e6812bd43b4bddea5",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/config": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
|
||||||
|
"illuminate/database": "^8.69 || ^9.27 || ^10.0 || ^11.0 || ^12.0",
|
||||||
|
"illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
|
||||||
|
"php": "^8.1",
|
||||||
|
"spatie/laravel-package-tools": "^1.6.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"orchestra/testbench": "^6.23 || ^7.0 || ^8.0 || ^9.0 || ^10.0",
|
||||||
|
"pestphp/pest": "^1.20 || ^2.0 || ^3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Spatie\\Activitylog\\ActivitylogServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/helpers.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Spatie\\Activitylog\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Freek Van der Herten",
|
||||||
|
"email": "freek@spatie.be",
|
||||||
|
"homepage": "https://spatie.be",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sebastian De Deyne",
|
||||||
|
"email": "sebastian@spatie.be",
|
||||||
|
"homepage": "https://spatie.be",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tom Witkowski",
|
||||||
|
"email": "dev.gummibeer@gmail.com",
|
||||||
|
"homepage": "https://gummibeer.de",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A very simple activity logger to monitor the users of your website or application",
|
||||||
|
"homepage": "https://github.com/spatie/activitylog",
|
||||||
|
"keywords": [
|
||||||
|
"activity",
|
||||||
|
"laravel",
|
||||||
|
"log",
|
||||||
|
"spatie",
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/spatie/laravel-activitylog/issues",
|
||||||
|
"source": "https://github.com/spatie/laravel-activitylog/tree/4.10.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://spatie.be/open-source/support-us",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/spatie",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-02-10T15:38:25+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spatie/laravel-package-tools",
|
"name": "spatie/laravel-package-tools",
|
||||||
"version": "1.92.4",
|
"version": "1.92.4",
|
||||||
|
52
config/activitylog.php
Normal file
52
config/activitylog.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If set to false, no activities will be saved to the database.
|
||||||
|
*/
|
||||||
|
'enabled' => env('ACTIVITY_LOGGER_ENABLED', true),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When the clean-command is executed, all recording activities older than
|
||||||
|
* the number of days specified here will be deleted.
|
||||||
|
*/
|
||||||
|
'delete_records_older_than_days' => 365,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If no log name is passed to the activity() helper
|
||||||
|
* we use this default log name.
|
||||||
|
*/
|
||||||
|
'default_log_name' => 'default',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* You can specify an auth driver here that gets user models.
|
||||||
|
* If this is null we'll use the current Laravel auth driver.
|
||||||
|
*/
|
||||||
|
'default_auth_driver' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If set to true, the subject returns soft deleted models.
|
||||||
|
*/
|
||||||
|
'subject_returns_soft_deleted_models' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This model will be used to log activity.
|
||||||
|
* It should implement the Spatie\Activitylog\Contracts\Activity interface
|
||||||
|
* and extend Illuminate\Database\Eloquent\Model.
|
||||||
|
*/
|
||||||
|
'activity_model' => \Spatie\Activitylog\Models\Activity::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the name of the table that will be created by the migration and
|
||||||
|
* used by the Activity model shipped with this package.
|
||||||
|
*/
|
||||||
|
'table_name' => env('ACTIVITY_LOGGER_TABLE_NAME', 'activity_log'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the database connection that will be used by the migration and
|
||||||
|
* the Activity model shipped with this package. In case it's not set
|
||||||
|
* Laravel's database.default will be used instead.
|
||||||
|
*/
|
||||||
|
'database_connection' => env('ACTIVITY_LOGGER_DB_CONNECTION'),
|
||||||
|
];
|
@ -65,7 +65,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'timezone' => 'UTC',
|
'timezone' => env('APP_TIMEZONE', 'UTC'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
380
config/excel.php
Normal file
380
config/excel.php
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Maatwebsite\Excel\Excel;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Reader\Csv;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'exports' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Chunk size
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When using FromQuery, the query is automatically chunked.
|
||||||
|
| Here you can specify how big the chunk should be.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'chunk_size' => 1000,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Pre-calculate formulas during export
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
'pre_calculate_formulas' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Enable strict null comparison
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When enabling strict null comparison empty cells ('') will
|
||||||
|
| be added to the sheet.
|
||||||
|
*/
|
||||||
|
'strict_null_comparison' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| CSV Settings
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure e.g. delimiter, enclosure and line ending for CSV exports.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'csv' => [
|
||||||
|
'delimiter' => ',',
|
||||||
|
'enclosure' => '"',
|
||||||
|
'line_ending' => PHP_EOL,
|
||||||
|
'use_bom' => false,
|
||||||
|
'include_separator_line' => false,
|
||||||
|
'excel_compatibility' => false,
|
||||||
|
'output_encoding' => '',
|
||||||
|
'test_auto_detect' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Worksheet properties
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure e.g. default title, creator, subject,...
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'properties' => [
|
||||||
|
'creator' => '',
|
||||||
|
'lastModifiedBy' => '',
|
||||||
|
'title' => '',
|
||||||
|
'description' => '',
|
||||||
|
'subject' => '',
|
||||||
|
'keywords' => '',
|
||||||
|
'category' => '',
|
||||||
|
'manager' => '',
|
||||||
|
'company' => '',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'imports' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Read Only
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When dealing with imports, you might only be interested in the
|
||||||
|
| data that the sheet exists. By default we ignore all styles,
|
||||||
|
| however if you want to do some logic based on style data
|
||||||
|
| you can enable it by setting read_only to false.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'read_only' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Ignore Empty
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When dealing with imports, you might be interested in ignoring
|
||||||
|
| rows that have null values or empty strings. By default rows
|
||||||
|
| containing empty strings or empty values are not ignored but can be
|
||||||
|
| ignored by enabling the setting ignore_empty to true.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'ignore_empty' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Heading Row Formatter
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure the heading row formatter.
|
||||||
|
| Available options: none|slug|custom
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'heading_row' => [
|
||||||
|
'formatter' => 'slug',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| CSV Settings
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure e.g. delimiter, enclosure and line ending for CSV imports.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'csv' => [
|
||||||
|
'delimiter' => null,
|
||||||
|
'enclosure' => '"',
|
||||||
|
'escape_character' => '\\',
|
||||||
|
'contiguous' => false,
|
||||||
|
'input_encoding' => Csv::GUESS_ENCODING,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Worksheet properties
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure e.g. default title, creator, subject,...
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'properties' => [
|
||||||
|
'creator' => '',
|
||||||
|
'lastModifiedBy' => '',
|
||||||
|
'title' => '',
|
||||||
|
'description' => '',
|
||||||
|
'subject' => '',
|
||||||
|
'keywords' => '',
|
||||||
|
'category' => '',
|
||||||
|
'manager' => '',
|
||||||
|
'company' => '',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cell Middleware
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure middleware that is executed on getting a cell value
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'cells' => [
|
||||||
|
'middleware' => [
|
||||||
|
//\Maatwebsite\Excel\Middleware\TrimCellValue::class,
|
||||||
|
//\Maatwebsite\Excel\Middleware\ConvertEmptyCellValuesToNull::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Extension detector
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure here which writer/reader type should be used when the package
|
||||||
|
| needs to guess the correct type based on the extension alone.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'extension_detector' => [
|
||||||
|
'xlsx' => Excel::XLSX,
|
||||||
|
'xlsm' => Excel::XLSX,
|
||||||
|
'xltx' => Excel::XLSX,
|
||||||
|
'xltm' => Excel::XLSX,
|
||||||
|
'xls' => Excel::XLS,
|
||||||
|
'xlt' => Excel::XLS,
|
||||||
|
'ods' => Excel::ODS,
|
||||||
|
'ots' => Excel::ODS,
|
||||||
|
'slk' => Excel::SLK,
|
||||||
|
'xml' => Excel::XML,
|
||||||
|
'gnumeric' => Excel::GNUMERIC,
|
||||||
|
'htm' => Excel::HTML,
|
||||||
|
'html' => Excel::HTML,
|
||||||
|
'csv' => Excel::CSV,
|
||||||
|
'tsv' => Excel::TSV,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| PDF Extension
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure here which Pdf driver should be used by default.
|
||||||
|
| Available options: Excel::MPDF | Excel::TCPDF | Excel::DOMPDF
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'pdf' => Excel::DOMPDF,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Value Binder
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| PhpSpreadsheet offers a way to hook into the process of a value being
|
||||||
|
| written to a cell. In there some assumptions are made on how the
|
||||||
|
| value should be formatted. If you want to change those defaults,
|
||||||
|
| you can implement your own default value binder.
|
||||||
|
|
|
||||||
|
| Possible value binders:
|
||||||
|
|
|
||||||
|
| [x] Maatwebsite\Excel\DefaultValueBinder::class
|
||||||
|
| [x] PhpOffice\PhpSpreadsheet\Cell\StringValueBinder::class
|
||||||
|
| [x] PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'value_binder' => [
|
||||||
|
'default' => Maatwebsite\Excel\DefaultValueBinder::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
'cache' => [
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default cell caching driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| By default PhpSpreadsheet keeps all cell values in memory, however when
|
||||||
|
| dealing with large files, this might result into memory issues. If you
|
||||||
|
| want to mitigate that, you can configure a cell caching driver here.
|
||||||
|
| When using the illuminate driver, it will store each value in the
|
||||||
|
| cache store. This can slow down the process, because it needs to
|
||||||
|
| store each value. You can use the "batch" store if you want to
|
||||||
|
| only persist to the store when the memory limit is reached.
|
||||||
|
|
|
||||||
|
| Drivers: memory|illuminate|batch
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'driver' => 'memory',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Batch memory caching
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When dealing with the "batch" caching driver, it will only
|
||||||
|
| persist to the store when the memory limit is reached.
|
||||||
|
| Here you can tweak the memory limit to your liking.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'batch' => [
|
||||||
|
'memory_limit' => 60000,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Illuminate cache
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When using the "illuminate" caching driver, it will automatically use
|
||||||
|
| your default cache store. However if you prefer to have the cell
|
||||||
|
| cache on a separate store, you can configure the store name here.
|
||||||
|
| You can use any store defined in your cache config. When leaving
|
||||||
|
| at "null" it will use the default store.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'illuminate' => [
|
||||||
|
'store' => null,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cache Time-to-live (TTL)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The TTL of items written to cache. If you want to keep the items cached
|
||||||
|
| indefinitely, set this to null. Otherwise, set a number of seconds,
|
||||||
|
| a \DateInterval, or a callable.
|
||||||
|
|
|
||||||
|
| Allowable types: callable|\DateInterval|int|null
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'default_ttl' => 10800,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Transaction Handler
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| By default the import is wrapped in a transaction. This is useful
|
||||||
|
| for when an import may fail and you want to retry it. With the
|
||||||
|
| transactions, the previous import gets rolled-back.
|
||||||
|
|
|
||||||
|
| You can disable the transaction handler by setting this to null.
|
||||||
|
| Or you can choose a custom made transaction handler here.
|
||||||
|
|
|
||||||
|
| Supported handlers: null|db
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'transactions' => [
|
||||||
|
'handler' => 'db',
|
||||||
|
'db' => [
|
||||||
|
'connection' => null,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'temporary_files' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Local Temporary Path
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When exporting and importing files, we use a temporary file, before
|
||||||
|
| storing reading or downloading. Here you can customize that path.
|
||||||
|
| permissions is an array with the permission flags for the directory (dir)
|
||||||
|
| and the create file (file).
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'local_path' => storage_path('framework/cache/laravel-excel'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Local Temporary Path Permissions
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Permissions is an array with the permission flags for the directory (dir)
|
||||||
|
| and the create file (file).
|
||||||
|
| If omitted the default permissions of the filesystem will be used.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'local_permissions' => [
|
||||||
|
// 'dir' => 0755,
|
||||||
|
// 'file' => 0644,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Remote Temporary Disk
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When dealing with a multi server setup with queues in which you
|
||||||
|
| cannot rely on having a shared local temporary path, you might
|
||||||
|
| want to store the temporary file on a shared disk. During the
|
||||||
|
| queue executing, we'll retrieve the temporary file from that
|
||||||
|
| location instead. When left to null, it will always use
|
||||||
|
| the local path. This setting only has effect when using
|
||||||
|
| in conjunction with queued imports and exports.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'remote_disk' => null,
|
||||||
|
'remote_prefix' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Force Resync
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When dealing with a multi server setup as above, it's possible
|
||||||
|
| for the clean up that occurs after entire queue has been run to only
|
||||||
|
| cleanup the server that the last AfterImportJob runs on. The rest of the server
|
||||||
|
| would still have the local temporary file stored on it. In this case your
|
||||||
|
| local storage limits can be exceeded and future imports won't be processed.
|
||||||
|
| To mitigate this you can set this config value to be true, so that after every
|
||||||
|
| queued chunk is processed the local temporary file is deleted on the server that
|
||||||
|
| processed it.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'force_resync_remote' => null,
|
||||||
|
],
|
||||||
|
];
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class CreateActivityLogTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::connection(config('activitylog.database_connection'))->create(config('activitylog.table_name'), function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->string('log_name')->nullable();
|
||||||
|
$table->text('description');
|
||||||
|
$table->nullableMorphs('subject', 'subject');
|
||||||
|
$table->nullableMorphs('causer', 'causer');
|
||||||
|
$table->json('properties')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->index('log_name');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::connection(config('activitylog.database_connection'))->dropIfExists(config('activitylog.table_name'));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class AddEventColumnToActivityLogTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||||
|
$table->string('event')->nullable()->after('subject_type');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||||
|
$table->dropColumn('event');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class AddBatchUuidColumnToActivityLogTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||||
|
$table->uuid('batch_uuid')->nullable()->after('properties');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||||
|
$table->dropColumn('batch_uuid');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -2,13 +2,17 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'list' => '角色列表',
|
'list' => '角色列表',
|
||||||
'CreateNewRole' => '新增角色',
|
'CreateNew' => '新增角色',
|
||||||
|
'EditRole' => '編輯角色',
|
||||||
'edit' => '編輯',
|
'edit' => '編輯',
|
||||||
'delete' => '刪除',
|
'delete' => '刪除',
|
||||||
|
|
||||||
'no' => '編號',
|
'no' => '編號',
|
||||||
'name' => '名稱',
|
'name' => '名稱',
|
||||||
'permissions' => '權限',
|
'permissions' => '權限',
|
||||||
|
|
||||||
|
'role_name' =>'角色名稱',
|
||||||
|
'select_permissions'=>'選擇權限',
|
||||||
|
|
||||||
'create' => '新增',
|
'create' => '新增',
|
||||||
'action' => '操作',
|
'action' => '操作',
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'list' => '使用者列表',
|
'list' => '使用者列表',
|
||||||
'CreateNewRole' => '新增使用者',
|
'CreateNew' => '新增使用者',
|
||||||
|
'EditUser' => '編輯使用者',
|
||||||
|
'ImportData' => '滙入使用者',
|
||||||
'edit' => '編輯',
|
'edit' => '編輯',
|
||||||
'delete' => '刪除',
|
'delete' => '刪除',
|
||||||
|
|
||||||
@ -13,6 +15,10 @@ return [
|
|||||||
'birthday' => '生日',
|
'birthday' => '生日',
|
||||||
'status' => '狀態',
|
'status' => '狀態',
|
||||||
'role' =>'角色',
|
'role' =>'角色',
|
||||||
|
|
||||||
|
'select_gender'=>'選擇性別',
|
||||||
|
'select_status'=>'選擇狀態',
|
||||||
|
'select_role'=>'選擇角色',
|
||||||
|
|
||||||
'create' => '新增',
|
'create' => '新增',
|
||||||
'action' => '操作',
|
'action' => '操作',
|
||||||
|
16
resources/views/components/select-category.blade.php
Normal file
16
resources/views/components/select-category.blade.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
@props(['selected','fieldName', 'modelId'])
|
||||||
|
<div>
|
||||||
|
<select wire:change="categoryChanged($event.target.value,'{{ $fieldName}}', {{ $modelId }})">
|
||||||
|
@foreach ($options as $id => $name)
|
||||||
|
<option
|
||||||
|
value="{{ $id }}"
|
||||||
|
@if ($id == $selected)
|
||||||
|
selected="selected"
|
||||||
|
@endif
|
||||||
|
>
|
||||||
|
{{ $name }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
</select>
|
||||||
|
</div>
|
3
resources/views/livewire/admin/activity-log.blade.php
Normal file
3
resources/views/livewire/admin/activity-log.blade.php
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<x-layouts.admin>
|
||||||
|
<livewire:admin.activity-log-table />
|
||||||
|
</x-layouts.admin>
|
@ -1,25 +1,21 @@
|
|||||||
<div>
|
<x-wireui:modal-card title="{{ $roleId ? __('roles.EditRole') : __('roles.CreateNew') }}" blur wire:model.defer="showCreateModal">
|
||||||
@if ($showCreateModal)
|
<div class="space-y-4">
|
||||||
<x-wireui:modal-card title="{{ $roleId ? '編輯角色' : '新增角色' }}" blur wire:model.defer="showCreateModal">
|
<x-wireui:input label="{{__('roles.role_name')}}" wire:model.defer="name" />
|
||||||
<div class="space-y-4">
|
<x-wireui:select
|
||||||
<x-wireui:input label="角色名稱" wire:model.defer="name" />
|
label="{{__('roles.permissions')}}"
|
||||||
<x-wireui:select
|
wire:model.defer="selectedPermissions"
|
||||||
label="權限"
|
placeholder="{{__('roles.select_permissions')}}"
|
||||||
wire:model.defer="selectedPermissions"
|
multiselect
|
||||||
placeholder="選擇權限"
|
option-label="label"
|
||||||
multiselect
|
option-value="value"
|
||||||
option-label="label"
|
:options="$permissions->map(fn($p) => ['value' => $p->id, 'label' => $p->name])->toArray()"
|
||||||
option-value="value"
|
/>
|
||||||
:options="$permissions->map(fn($p) => ['value' => $p->id, 'label' => $p->name])->toArray()"
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<x-slot name="footer">
|
<x-slot name="footer">
|
||||||
<div class="flex justify-center gap-2">
|
<div class="flex justify-between w-full">
|
||||||
<x-wireui:button primary label="{{__('roles.cancel')}}" x-on:click="$dispatch('close')" />
|
<x-wireui:button flat label="{{__('roles.cancel')}}" @click="$wire.showCreateModal = false" />
|
||||||
<x-wireui:button primary label="{{__('roles.submit')}}" wire:click="save" />
|
<x-wireui:button primary label="{{__('roles.submit')}}" wire:click="save" />
|
||||||
</div>
|
</div>
|
||||||
</x-slot>
|
</x-slot>
|
||||||
</x-wireui:modal-card>
|
</x-wireui:modal-card>
|
||||||
@endif
|
|
||||||
</div>
|
|
8
resources/views/livewire/admin/role-header.blade.php
Normal file
8
resources/views/livewire/admin/role-header.blade.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<div class="flex justify-end mb-2">
|
||||||
|
<x-wireui:button
|
||||||
|
wire:click="$dispatchTo('admin.role-form', 'openCreateRoleModal')"
|
||||||
|
icon="plus"
|
||||||
|
label="{{ __('roles.CreateNew') }}"
|
||||||
|
class="bg-blue-600 text-white"
|
||||||
|
/>
|
||||||
|
</div>
|
@ -1,21 +1,5 @@
|
|||||||
|
|
||||||
<x-layouts.admin>
|
<x-layouts.admin>
|
||||||
<x-slot name="header">
|
<x-wireui:notifications/>
|
||||||
角色管理
|
|
||||||
</x-slot>
|
|
||||||
|
|
||||||
@if (session()->has('message'))
|
|
||||||
<x-wireui:notifications />
|
|
||||||
<script>
|
|
||||||
window.$wireui.notify({
|
|
||||||
title: '提示',
|
|
||||||
description: '{{ session('message') }}',
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
{{-- 單一 Livewire 元件,內含資料表與 Modal --}}
|
|
||||||
<livewire:admin.role-table />
|
<livewire:admin.role-table />
|
||||||
<livewire:admin.role-form />
|
<livewire:admin.role-form />
|
||||||
</x-layouts.admin>
|
</x-layouts.admin>
|
@ -1,29 +1,29 @@
|
|||||||
<x-wireui:modal-card title="{{ $userId ? '編輯使用者' : '新增使用者' }}" blur wire:model.defer="showCreateModal">
|
<x-wireui:modal-card title="{{ $userId ? __('users.EditUser') : __('users.CreateNew') }}" blur wire:model.defer="showModal">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<x-wireui:input label="名稱" wire:model.defer="fields.name" required />
|
<x-wireui:input label="{{__('users.name')}}" wire:model.defer="fields.name" required />
|
||||||
<x-wireui:input label="Email" wire:model.defer="fields.email" required />
|
<x-wireui:input label="Email" wire:model.defer="fields.email" required />
|
||||||
<x-wireui:input label="Phone" wire:model.defer="fields.phone" />
|
<x-wireui:input label="Phone" wire:model.defer="fields.phone" />
|
||||||
<x-wireui:select
|
<x-wireui:select
|
||||||
label="性別"
|
label="{{__('users.gender')}}"
|
||||||
wire:model.defer="fields.gender"
|
wire:model.defer="fields.gender"
|
||||||
placeholder="選擇性別"
|
placeholder="{{__('users.select_gender')}}"
|
||||||
:options="$genderOptions"
|
:options="$genderOptions"
|
||||||
option-label="name"
|
option-label="name"
|
||||||
option-value="value"
|
option-value="value"
|
||||||
/>
|
/>
|
||||||
<x-wireui:select
|
<x-wireui:select
|
||||||
label="狀態"
|
label="{{__('users.status')}}"
|
||||||
wire:model.defer="fields.status"
|
wire:model.defer="fields.status"
|
||||||
placeholder="選擇狀態"
|
placeholder="{{__('users.select_status')}}"
|
||||||
:options="$statusOptions"
|
:options="$statusOptions"
|
||||||
option-label="name"
|
option-label="name"
|
||||||
option-value="value"
|
option-value="value"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<x-wireui:select
|
<x-wireui:select
|
||||||
label="角色"
|
label="{{__('users.role')}}"
|
||||||
wire:model.defer="selectedRoles"
|
wire:model.defer="selectedRoles"
|
||||||
placeholder="選擇角色"
|
placeholder="{{__('users.select_role')}}"
|
||||||
multiselect
|
multiselect
|
||||||
option-label="label"
|
option-label="label"
|
||||||
option-value="value"
|
option-value="value"
|
||||||
@ -32,8 +32,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<x-slot name="footer">
|
<x-slot name="footer">
|
||||||
<div class="flex justify-center gap-2">
|
<div class="flex justify-between w-full">
|
||||||
<x-wireui:button primary label="{{__('users.cancel')}}" x-on:click="$dispatch('close')" />
|
<x-wireui:button flat label="{{__('users.cancel')}}" wire:click="closeModal" />
|
||||||
<x-wireui:button primary label="{{__('users.submit')}}" wire:click="save" />
|
<x-wireui:button primary label="{{__('users.submit')}}" wire:click="save" />
|
||||||
</div>
|
</div>
|
||||||
</x-slot>
|
</x-slot>
|
||||||
|
16
resources/views/livewire/admin/user-header.blade.php
Normal file
16
resources/views/livewire/admin/user-header.blade.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<div class="flex justify-end mb-2 mr-2 mt-2 sm:mt-0 gap-3">
|
||||||
|
<x-wireui:button
|
||||||
|
wire:click="$dispatchTo('admin.user-form', 'openModal')"
|
||||||
|
icon="plus"
|
||||||
|
label="{{ __('users.CreateNew') }}"
|
||||||
|
class="bg-blue-600 text-white"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<x-wireui:button
|
||||||
|
wire:click="$dispatchTo('admin.user-import-data','openModal')"
|
||||||
|
icon="document-plus"
|
||||||
|
label="{{ __('users.ImportData') }}"
|
||||||
|
class="bg-green-600 text-white"
|
||||||
|
/>
|
||||||
|
</div>
|
63
resources/views/livewire/admin/user-import-data.blade.php
Normal file
63
resources/views/livewire/admin/user-import-data.blade.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<x-wireui:modal-card title="{{ __('users.ImportData') }}" blur wire:model.defer="showModal" hide-close>
|
||||||
|
|
||||||
|
{{-- 說明區塊 --}}
|
||||||
|
<div class="mb-4 p-4 bg-gray-100 border border-gray-300 rounded text-sm text-gray-700">
|
||||||
|
<p class="font-semibold mb-2">匯入格式說明</p>
|
||||||
|
<p class="mb-2">請依下列表格格式準備 Excel 或 CSV 檔案:</p>
|
||||||
|
|
||||||
|
<div class="overflow-x-auto mb-2">
|
||||||
|
<table class="min-w-full text-sm text-left border border-collapse border-gray-300">
|
||||||
|
<thead class="bg-gray-200">
|
||||||
|
<tr>
|
||||||
|
<th class="border border-gray-300 px-3 py-1">欄位名稱</th>
|
||||||
|
<th class="border border-gray-300 px-3 py-1">說明</th>
|
||||||
|
<th class="border border-gray-300 px-3 py-1">範例</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="border border-gray-300 px-3 py-1">???</td>
|
||||||
|
<td class="border border-gray-300 px-3 py-1">???</td>
|
||||||
|
<td class="border border-gray-300 px-3 py-1">???</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{{-- 檔案上傳 --}}
|
||||||
|
<div x-data="{
|
||||||
|
fileName: '',
|
||||||
|
updateFileInfo(event) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (file) this.fileName = file.name+ '('+(file.size / 1024 / 1024).toFixed(2) + ' MB'+')';
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div x-show="$wire.file === null" >
|
||||||
|
<input type="file" wire:model="file" accept=".csv, .xls, .xlsx" class="mb-2 w-full" @change="updateFileInfo" />
|
||||||
|
<p class="text-xs text-gray-500 mb-2" >
|
||||||
|
系統限制:最大上傳 {{ $maxUploadSize }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- 檔案資訊顯示 -->
|
||||||
|
<div wire:loading.remove wire:target="file" class="text-sm text-green-600 flex items-center space-x-1" x-show="$wire.file != null">
|
||||||
|
<x-wireui:icon name="check-circle" class="w-5 h-5 text-green-500" />
|
||||||
|
<strong x-text="fileName"></strong>
|
||||||
|
</div>
|
||||||
|
<!-- 上傳中提示 -->
|
||||||
|
<div wire:loading wire:target="file" class="text-sm text-blue-500">
|
||||||
|
檔案上傳中,請稍候...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<x-slot name="footer">
|
||||||
|
<div class="flex justify-between w-full">
|
||||||
|
<x-wireui:button flat label="{{ __('users.cancel') }}" wire:click="closeModal" />
|
||||||
|
<x-wireui:button primary label="{{ __('users.submit') }}" wire:click="import" />
|
||||||
|
</div>
|
||||||
|
</x-slot>
|
||||||
|
</x-wireui:modal-card>
|
@ -1,21 +1,6 @@
|
|||||||
|
|
||||||
<x-layouts.admin>
|
<x-layouts.admin>
|
||||||
<x-slot name="header">
|
<x-wireui:notifications/>
|
||||||
使用者管理
|
|
||||||
</x-slot>
|
|
||||||
|
|
||||||
@if (session()->has('message'))
|
|
||||||
<x-wireui:notifications />
|
|
||||||
<script>
|
|
||||||
window.$wireui.notify({
|
|
||||||
title: '提示',
|
|
||||||
description: '{{ session('message') }}',
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
{{-- 單一 Livewire 元件,內含資料表與 Modal --}}
|
|
||||||
<livewire:admin.user-table />
|
<livewire:admin.user-table />
|
||||||
<livewire:admin.user-form />
|
<livewire:admin.user-form />
|
||||||
|
<livewire:admin.user-import-data />
|
||||||
</x-layouts.admin>
|
</x-layouts.admin>
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
$menus = [
|
$menus = [
|
||||||
['label' => 'Dashboard', 'route' => 'admin.dashboard', 'icon' => 'home', 'permission' => null],
|
['label' => 'Dashboard', 'route' => 'admin.dashboard', 'icon' => 'home', 'permission' => null],
|
||||||
|
['label' => 'ActivityLog', 'route' => 'admin.activity-log', 'icon' => 'clock', 'permission' => null],
|
||||||
['label' => 'Role', 'route' => 'admin.roles', 'icon' => 'user-circle', 'permission' => 'role-list'],
|
['label' => 'Role', 'route' => 'admin.roles', 'icon' => 'user-circle', 'permission' => 'role-list'],
|
||||||
['label' => 'User', 'route' => 'admin.users', 'icon' => 'user-circle', 'permission' => 'user-list'],
|
['label' => 'User', 'route' => 'admin.users', 'icon' => 'user-circle', 'permission' => 'user-list'],
|
||||||
];
|
];
|
||||||
|
@ -20,15 +20,8 @@ require __DIR__.'/auth.php';
|
|||||||
|
|
||||||
Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () {
|
Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () {
|
||||||
Route::get('/dashboard', AdminDashboard::class)->name('dashboard');
|
Route::get('/dashboard', AdminDashboard::class)->name('dashboard');
|
||||||
|
Route::get('/activity-log', function () {return view('livewire.admin.activity-log');})->name('activity-log');
|
||||||
Route::get('/roles', function () {
|
Route::get('/roles', function () {return view('livewire.admin.roles');})->name('roles');
|
||||||
return view('livewire.admin.roles');
|
Route::get('/users', function () {return view('livewire.admin.users');})->name('users');
|
||||||
})->name('roles');
|
|
||||||
Route::get('/roles-table', RoleTable::class)->name('roles-table');
|
|
||||||
|
|
||||||
Route::get('/users', function () {
|
|
||||||
return view('livewire.admin.users');
|
|
||||||
})->name('users');
|
|
||||||
Route::get('/users-table', UserTable::class)->name('users-table');
|
|
||||||
|
|
||||||
});
|
});
|
12
開發手冊.ini
12
開發手冊.ini
@ -60,13 +60,21 @@ composer require wireui/wireui
|
|||||||
php artisan vendor:publish --tag="wireui.config"
|
php artisan vendor:publish --tag="wireui.config"
|
||||||
|
|
||||||
php artisan make:livewire Admin/Roles/Index
|
php artisan make:livewire Admin/Roles/Index
|
||||||
php artisan make:livewire Admin/Roles/CreateRole
|
|
||||||
php artisan make:livewire Admin/Roles/EditRole
|
|
||||||
|
|
||||||
php artisan make:livewire Admin/Users
|
php artisan make:livewire Admin/Users
|
||||||
|
|
||||||
php artisan make:component Table
|
php artisan make:component Table
|
||||||
|
|
||||||
|
操作記錄
|
||||||
|
composer require spatie/laravel-activitylog
|
||||||
|
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
|
||||||
|
php artisan migrate
|
||||||
|
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-config"
|
||||||
|
php artisan make:model ActivityLog
|
||||||
|
|
||||||
|
Laravel Excel
|
||||||
|
composer require maatwebsite/excel
|
||||||
|
|
||||||
composer require power-components/livewire-powergrid
|
composer require power-components/livewire-powergrid
|
||||||
php artisan vendor:publish --tag=livewire-powergrid-config
|
php artisan vendor:publish --tag=livewire-powergrid-config
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user