├── .nvmrc ├── public ├── favicon.ico ├── robots.txt ├── fonts │ └── filament │ │ └── filament │ │ └── inter │ │ ├── inter-greek-wght-normal-AXVTPQD5.woff2 │ │ ├── inter-greek-wght-normal-IRE366VL.woff2 │ │ ├── inter-greek-wght-normal-N43DBLU2.woff2 │ │ ├── inter-latin-wght-normal-NRMW37G5.woff2 │ │ ├── inter-latin-wght-normal-O25CN4JL.woff2 │ │ ├── inter-latin-wght-normal-OPIJAQLS.woff2 │ │ ├── inter-cyrillic-wght-normal-EWLSKVKN.woff2 │ │ ├── inter-cyrillic-wght-normal-JEOLYBOO.woff2 │ │ ├── inter-cyrillic-wght-normal-R5CMSONN.woff2 │ │ ├── inter-greek-ext-wght-normal-7GGTF7EK.woff2 │ │ ├── inter-greek-ext-wght-normal-EOVOK2B5.woff2 │ │ ├── inter-greek-ext-wght-normal-ZEVLMORV.woff2 │ │ ├── inter-latin-ext-wght-normal-5SRY4DMZ.woff2 │ │ ├── inter-latin-ext-wght-normal-GZCIV3NH.woff2 │ │ ├── inter-latin-ext-wght-normal-HA22NDSG.woff2 │ │ ├── inter-vietnamese-wght-normal-CE5GGD3W.woff2 │ │ ├── inter-vietnamese-wght-normal-TWG5UU7E.woff2 │ │ ├── inter-cyrillic-ext-wght-normal-ASVAGXXE.woff2 │ │ ├── inter-cyrillic-ext-wght-normal-IYF56FF6.woff2 │ │ ├── inter-cyrillic-ext-wght-normal-XKHXBTUO.woff2 │ │ └── index.css ├── index.php ├── js │ └── filament │ │ ├── forms │ │ └── components │ │ │ ├── textarea.js │ │ │ ├── tags-input.js │ │ │ ├── key-value.js │ │ │ ├── checkbox-list.js │ │ │ └── color-picker.js │ │ ├── schemas │ │ ├── components │ │ │ ├── tabs.js │ │ │ ├── actions.js │ │ │ └── wizard.js │ │ └── schemas.js │ │ ├── tables │ │ ├── components │ │ │ ├── columns │ │ │ │ ├── checkbox.js │ │ │ │ ├── toggle.js │ │ │ │ └── text-input.js │ │ │ └── table.js │ │ └── tables.js │ │ ├── actions │ │ └── actions.js │ │ ├── support │ │ └── async-alpine.js │ │ ├── notifications │ │ └── notifications.js │ │ └── filament │ │ └── app.js ├── .htaccess └── css │ └── filament │ └── support │ └── support.css ├── database ├── .gitignore ├── seeders │ └── DatabaseSeeder.php ├── migrations │ ├── 2025_02_07_113149_user_timezone.php │ ├── 2022_12_14_083707_create_settings_table.php │ ├── 0001_01_01_000001_create_cache_table.php │ ├── 0001_01_01_000000_create_users_table.php │ ├── 0001_01_01_000002_create_jobs_table.php │ └── 2025_07_24_073400_create_permission_tables.php └── factories │ └── UserFactory.php ├── bootstrap ├── cache │ └── .gitignore ├── providers.php └── app.php ├── resources ├── js │ ├── app.js │ └── bootstrap.js └── css │ └── app.css ├── storage ├── logs │ └── .gitignore ├── app │ ├── private │ │ └── .gitignore │ ├── public │ │ └── .gitignore │ └── .gitignore ├── debugbar │ └── .gitignore └── framework │ ├── testing │ └── .gitignore │ ├── views │ └── .gitignore │ ├── cache │ ├── data │ │ └── .gitignore │ └── .gitignore │ ├── sessions │ └── .gitignore │ └── .gitignore ├── app ├── Http │ └── Controllers │ │ └── Controller.php ├── Settings │ └── AppSetting.php ├── Filament │ ├── Resources │ │ └── Users │ │ │ ├── Pages │ │ │ ├── CreateUser.php │ │ │ ├── ListUsers.php │ │ │ └── EditUser.php │ │ │ └── UserResource.php │ ├── Pages │ │ └── AppSetting.php │ └── User │ │ └── Pages │ │ └── EditProfile.php ├── Notifications │ └── VerifyEmail.php ├── Providers │ ├── AppServiceProvider.php │ └── Filament │ │ ├── AdminPanelProvider.php │ │ └── UserPanelProvider.php ├── helpers.php └── Models │ └── User.php ├── routes ├── web.php └── console.php ├── tests ├── TestCase.php ├── Unit │ └── ExampleTest.php └── Feature │ └── ExampleTest.php ├── .gitattributes ├── .vscode ├── settings.json ├── extensions.json └── launch.json ├── .editorconfig ├── mago.toml ├── .gitignore ├── phpstan.neon ├── package.json ├── artisan ├── vite.config.js ├── README.md ├── config ├── services.php ├── filesystems.php ├── settings.php ├── mail.php ├── cache.php ├── auth.php ├── queue.php ├── app.php ├── logging.php ├── essentials.php ├── database.php ├── permission.php ├── session.php └── ide-helper.php ├── .env.example ├── phpunit.xml ├── rector.php ├── pint.json └── composer.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import './bootstrap'; 2 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /storage/app/private/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/debugbar/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !private/ 3 | !public/ 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 8 | })->purpose('Display an inspiring quote'); 9 | -------------------------------------------------------------------------------- /app/Settings/AppSetting.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [compose.yaml] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /app/Notifications/VerifyEmail.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $testResponse->assertStatus(200); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - vendor/larastan/larastan/extension.neon 3 | 4 | parameters: 5 | 6 | paths: 7 | - app/ 8 | 9 | # Level 9 is the highest level 10 | level: 9 11 | 12 | ignoreErrors: 13 | # - '#Unsafe usage of new static#' 14 | 15 | excludePaths: 16 | - ./*/*/FileToBeExcluded.php 17 | - **/node_modules/** 18 | - **/vendor/** 19 | - **/storage/** 20 | 21 | editorUrl: 'vscode://file/%%file%%:%%line%%' 22 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | handleCommand(new ArgvInput); 17 | 18 | exit($status); 19 | -------------------------------------------------------------------------------- /app/helpers.php: -------------------------------------------------------------------------------- 1 | as(User::class); 12 | } 13 | } 14 | 15 | if (! function_exists('diskPublic')) { 16 | function diskPublic(): FilesystemAdapter 17 | { 18 | return Storage::disk('public'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import laravel from 'laravel-vite-plugin'; 3 | import tailwindcss from '@tailwindcss/vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | laravel({ 8 | input: ['resources/css/app.css', 'resources/js/app.js'], 9 | refresh: true, 10 | }), 11 | tailwindcss(), 12 | ], 13 | server: { 14 | watch: { 15 | ignored: ['**/storage/framework/views/**'], 16 | }, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /app/Filament/Resources/Users/Pages/ListUsers.php: -------------------------------------------------------------------------------- 1 | create(); 17 | 18 | User::factory()->create([ 19 | 'name' => 'Test User', 20 | 'email' => 'test@example.com', 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | withRouting( 9 | web: __DIR__.'/../routes/web.php', 10 | commands: __DIR__.'/../routes/console.php', 11 | health: '/up', 12 | ) 13 | ->withMiddleware(function (Middleware $middleware): void { 14 | // 15 | }) 16 | ->withExceptions(function (Exceptions $exceptions): void { 17 | // 18 | })->create(); 19 | -------------------------------------------------------------------------------- /app/Filament/Pages/AppSetting.php: -------------------------------------------------------------------------------- 1 | components([ 19 | // ... 20 | ]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 21 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/textarea.js: -------------------------------------------------------------------------------- 1 | function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /database/migrations/2025_02_07_113149_user_timezone.php: -------------------------------------------------------------------------------- 1 | string('timezone')->nullable()->comment('+7 or Asia/Ho_Chi_Minh')->after('remember_token'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | // 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /app/Filament/Resources/Users/Pages/EditUser.php: -------------------------------------------------------------------------------- 1 | id(); 13 | 14 | $blueprint->string('group'); 15 | $blueprint->string('name'); 16 | $blueprint->boolean('locked')->default(false); 17 | $blueprint->json('payload'); 18 | 19 | $blueprint->timestamps(); 20 | 21 | $blueprint->unique(['group', 'name']); 22 | }); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/tags-input.js: -------------------------------------------------------------------------------- 1 | function s({state:n,splitKeys:a}){return{newTag:"",state:n,createTag(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag(t){this.state=this.state.filter(e=>e!==t)},reorderTags(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...a].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(a.length===0){this.createTag();return}let t=a.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{s as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/schemas/components/tabs.js: -------------------------------------------------------------------------------- 1 | function u({activeTab:a,isTabPersistedInQueryString:e,livewireId:h,tab:o,tabQueryStringKey:s}){return{tab:o,init(){let t=this.getTabs(),i=new URLSearchParams(window.location.search);e&&i.has(s)&&t.includes(i.get(s))&&(this.tab=i.get(s)),this.$watch("tab",()=>this.updateQueryString()),(!this.tab||!t.includes(this.tab))&&(this.tab=t[a-1]),Livewire.hook("commit",({component:r,commit:f,succeed:c,fail:l,respond:b})=>{c(({snapshot:d,effect:m})=>{this.$nextTick(()=>{if(r.id!==h)return;let n=this.getTabs();n.includes(this.tab)||(this.tab=n[a-1]??this.tab)})})})},getTabs(){return this.$refs.tabsData?JSON.parse(this.$refs.tabsData.value):[]},updateQueryString(){if(!e)return;let t=new URL(window.location.href);t.searchParams.set(s,this.tab),history.replaceState(null,document.title,t.toString())}}}export{u as default}; 2 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Handle X-XSRF-Token Header 13 | RewriteCond %{HTTP:x-xsrf-token} . 14 | RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}] 15 | 16 | # Redirect Trailing Slashes If Not A Folder... 17 | RewriteCond %{REQUEST_FILENAME} !-d 18 | RewriteCond %{REQUEST_URI} (.+)/$ 19 | RewriteRule ^ %1 [L,R=301] 20 | 21 | # Send Requests To Front Controller... 22 | RewriteCond %{REQUEST_FILENAME} !-d 23 | RewriteCond %{REQUEST_FILENAME} !-f 24 | RewriteRule ^ index.php [L] 25 | 26 | -------------------------------------------------------------------------------- /public/js/filament/tables/components/columns/checkbox.js: -------------------------------------------------------------------------------- 1 | function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:h,respond:u})=>{n(({snapshot:f,effect:d})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{o as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/tables/components/columns/toggle.js: -------------------------------------------------------------------------------- 1 | function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:h,respond:u})=>{n(({snapshot:f,effect:d})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{o as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/schemas/components/actions.js: -------------------------------------------------------------------------------- 1 | var i=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let e=this.$el.parentElement;e&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(e),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let e=this.$el.parentElement;if(!e)return;let t=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=e.offsetWidth+parseInt(t.marginInlineStart,10)*-1+parseInt(t.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});export{i as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/key-value.js: -------------------------------------------------------------------------------- 1 | function h({state:r}){return{state:r,rows:[],init(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(e,t)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(e)===0&&s(t)===0||this.updateRows()})},addRow(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow(e){this.rows.splice(e,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows(e){let t=Alpine.raw(this.rows);this.rows=[];let s=t.splice(e.oldIndex,1)[0];t.splice(e.newIndex,0,s),this.$nextTick(()=>{this.rows=t,this.updateState()})},updateRows(){let t=Alpine.raw(this.state).map(({key:s,value:i})=>({key:s,value:i}));this.rows.forEach(s=>{(s.key===""||s.key===null)&&t.push({key:"",value:s.value})}),this.rows=t},updateState(){let e=[];this.rows.forEach(t=>{t.key===""||t.key===null||e.push({key:t.key,value:t.value})}),JSON.stringify(this.state)!==JSON.stringify(e)&&(this.state=e)}}}export{h as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/actions/actions.js: -------------------------------------------------------------------------------- 1 | (()=>{var n=({livewireId:e})=>({actionNestingIndex:null,init(){window.addEventListener("sync-action-modals",t=>{t.detail.id===e&&this.syncActionModals(t.detail.newActionNestingIndex)})},syncActionModals(t){if(this.actionNestingIndex===t){this.actionNestingIndex!==null&&this.$nextTick(()=>this.openModal());return}if(this.actionNestingIndex!==null&&this.closeModal(),this.actionNestingIndex=t,this.actionNestingIndex!==null){if(!this.$el.querySelector(`#${this.generateModalId(t)}`)){this.$nextTick(()=>this.openModal());return}this.openModal()}},generateModalId(t){return`fi-${e}-action-`+t},openModal(){let t=this.generateModalId(this.actionNestingIndex);document.dispatchEvent(new CustomEvent("open-modal",{bubbles:!0,composed:!0,detail:{id:t}}))},closeModal(){let t=this.generateModalId(this.actionNestingIndex);document.dispatchEvent(new CustomEvent("close-modal-quietly",{bubbles:!0,composed:!0,detail:{id:t}}))}});document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentActionModals",n)});})(); 2 | -------------------------------------------------------------------------------- /public/js/filament/tables/components/columns/text-input.js: -------------------------------------------------------------------------------- 1 | function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:d,respond:u})=>{n(({snapshot:f,effect:h})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||this.getNormalizedState()===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||this.getNormalizedState()===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.getNormalizedState()),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[null,void 0].includes(this.$refs.serverState.value)?"":this.$refs.serverState.value.replaceAll('\\"','"')},getNormalizedState(){let e=Alpine.raw(this.state);return[null,void 0].includes(e)?"":e}}}export{o as default}; 2 | -------------------------------------------------------------------------------- /app/Filament/User/Pages/EditProfile.php: -------------------------------------------------------------------------------- 1 | components([ 20 | FileUpload::make('avatar') 21 | ->hiddenLabel() 22 | ->disk('public') 23 | ->directory('profile-photos') 24 | ->avatar() 25 | ->alignCenter(), 26 | $this->getNameFormComponent(), 27 | $this->getEmailFormComponent(), 28 | $this->getPasswordFormComponent(), 29 | $this->getPasswordConfirmationFormComponent(), 30 | $this->getCurrentPasswordFormComponent(), 31 | ]); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000001_create_cache_table.php: -------------------------------------------------------------------------------- 1 | string('key')->primary(); 16 | $blueprint->mediumText('value'); 17 | $blueprint->integer('expiration'); 18 | }); 19 | 20 | Schema::create('cache_locks', function (Blueprint $blueprint): void { 21 | $blueprint->string('key')->primary(); 22 | $blueprint->string('owner'); 23 | $blueprint->integer('expiration'); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('cache'); 33 | Schema::dropIfExists('cache_locks'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cài đặt 2 | 3 | Hãy bấm nút `Use this template` > `Create a new repository` 4 | 5 | ```bash 6 | composer install 7 | cp .env.example .env 8 | php artisan key:generate --force 9 | php artisan storage:link 10 | php artisan migrate 11 | # tạo tài khoản admin, nhập admin@example.com và password 12 | php artisan make:filament-user 13 | ``` 14 | 15 | ## Đồng bộ code từ template này 16 | 17 | ```bash 18 | git remote add template https://github.com/flashteamdev/laravel-skeleton.git 19 | git fetch --all 20 | git merge template/main --allow-unrelated-histories 21 | # nếu conflict xảy ra, chạy lệnh này 22 | git diff --name-only --diff-filter=U | xargs git checkout --ours -- 23 | ``` 24 | 25 | ## VS Code Extension 26 | 27 | Mở VS Code Extension gõ @recommended và cài toàn bộ `WORKSPACE RECOMMENDATIONS` extension. 28 | 29 | ## Format Code 30 | 31 | Vui lòng chạy lệnh dưới đây trước khi gửi Pull Request! 32 | 33 | ```bash 34 | composer ide 35 | php artisan test 36 | ``` 37 | 38 | ## Test Coverage 39 | 40 | ```bash 41 | php artisan test --coverage 42 | ``` 43 | 44 | Mở trình duyệt http://127.0.0.1:8000/coverage/index.html để xem kết quả 45 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'key' => env('POSTMARK_API_KEY'), 19 | ], 20 | 21 | 'resend' => [ 22 | 'key' => env('RESEND_API_KEY'), 23 | ], 24 | 25 | 'ses' => [ 26 | 'key' => env('AWS_ACCESS_KEY_ID'), 27 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 28 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 29 | ], 30 | 31 | 'slack' => [ 32 | 'notifications' => [ 33 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), 34 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), 35 | ], 36 | ], 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class UserFactory extends Factory 13 | { 14 | /** 15 | * The current password being used by the factory. 16 | */ 17 | protected static ?string $password = null; 18 | 19 | /** 20 | * Define the model's default state. 21 | * 22 | * @return array 23 | */ 24 | public function definition(): array 25 | { 26 | return [ 27 | 'name' => fake()->name(), 28 | 'email' => fake()->unique()->safeEmail(), 29 | 'email_verified_at' => now(), 30 | 'password' => static::$password ??= Hash::make('password'), 31 | 'remember_token' => Str::random(10), 32 | ]; 33 | } 34 | 35 | /** 36 | * Indicate that the model's email address should be unverified. 37 | */ 38 | public function unverified(): static 39 | { 40 | return $this->state(fn (array $attributes): array => [ 41 | 'email_verified_at' => null, 42 | ]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/js/filament/schemas/components/wizard.js: -------------------------------------------------------------------------------- 1 | function o({isSkippable:s,isStepPersistedInQueryString:i,key:r,startStep:h,stepQueryStringKey:n}){return{step:null,init(){this.$watch("step",()=>this.updateQueryString()),this.step=this.getSteps().at(h-1),this.autofocusFields()},async requestNextStep(){await this.$wire.callSchemaComponentMethod(r,"nextStep",{currentStepIndex:this.getStepIndex(this.step)})},goToNextStep(){let t=this.getStepIndex(this.step)+1;t>=this.getSteps().length||(this.step=this.getSteps()[t],this.autofocusFields(),this.scroll())},goToPreviousStep(){let t=this.getStepIndex(this.step)-1;t<0||(this.step=this.getSteps()[t],this.autofocusFields(),this.scroll())},scroll(){this.$nextTick(()=>{this.$refs.header?.children[this.getStepIndex(this.step)].scrollIntoView({behavior:"smooth",block:"start"})})},autofocusFields(){this.$nextTick(()=>this.$refs[`step-${this.step}`].querySelector("[autofocus]")?.focus())},getStepIndex(t){let e=this.getSteps().findIndex(p=>p===t);return e===-1?0:e},getSteps(){return JSON.parse(this.$refs.stepsData.value)},isFirstStep(){return this.getStepIndex(this.step)<=0},isLastStep(){return this.getStepIndex(this.step)+1>=this.getSteps().length},isStepAccessible(t){return s||this.getStepIndex(this.step)>this.getStepIndex(t)},updateQueryString(){if(!i)return;let t=new URL(window.location.href);t.searchParams.set(n,this.step),history.replaceState(null,document.title,t.toString())}}}export{o as default}; 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_TIMEZONE=UTC 6 | APP_URL=http://localhost 7 | 8 | APP_LOCALE=en 9 | APP_FALLBACK_LOCALE=en 10 | APP_FAKER_LOCALE=en_US 11 | 12 | APP_MAINTENANCE_DRIVER=file 13 | # APP_MAINTENANCE_STORE=database 14 | 15 | # PHP_CLI_SERVER_WORKERS=4 16 | 17 | BCRYPT_ROUNDS=12 18 | 19 | LOG_CHANNEL=stack 20 | LOG_STACK=single 21 | LOG_DEPRECATIONS_CHANNEL=null 22 | LOG_LEVEL=debug 23 | 24 | DB_CONNECTION=sqlite 25 | # DB_HOST=127.0.0.1 26 | # DB_PORT=3306 27 | # DB_DATABASE=laravel 28 | # DB_USERNAME=root 29 | # DB_PASSWORD= 30 | 31 | SESSION_DRIVER=database 32 | SESSION_LIFETIME=120 33 | SESSION_ENCRYPT=false 34 | SESSION_PATH=/ 35 | SESSION_DOMAIN=null 36 | 37 | BROADCAST_CONNECTION=log 38 | FILESYSTEM_DISK=local 39 | QUEUE_CONNECTION=database 40 | 41 | CACHE_STORE=database 42 | # CACHE_PREFIX= 43 | 44 | MEMCACHED_HOST=127.0.0.1 45 | 46 | REDIS_CLIENT=phpredis 47 | REDIS_HOST=127.0.0.1 48 | REDIS_PASSWORD=null 49 | REDIS_PORT=6379 50 | 51 | MAIL_MAILER=log 52 | MAIL_SCHEME=null 53 | MAIL_HOST=127.0.0.1 54 | MAIL_PORT=2525 55 | MAIL_USERNAME=null 56 | MAIL_PASSWORD=null 57 | MAIL_FROM_ADDRESS="hello@example.com" 58 | MAIL_FROM_NAME="${APP_NAME}" 59 | 60 | AWS_ACCESS_KEY_ID= 61 | AWS_SECRET_ACCESS_KEY= 62 | AWS_DEFAULT_REGION=us-east-1 63 | AWS_BUCKET= 64 | AWS_USE_PATH_STYLE_ENDPOINT=false 65 | 66 | VITE_APP_NAME="${APP_NAME}" 67 | 68 | ADMIN_EMAIL=admin@example.com 69 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/checkbox-list.js: -------------------------------------------------------------------------------- 1 | function c({livewireId:s}){return{areAllCheckboxesChecked:!1,checkboxListOptions:[],search:"",visibleCheckboxListOptions:[],init(){this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.$nextTick(()=>{this.checkIfAllCheckboxesAreChecked()}),Livewire.hook("commit",({component:e,commit:t,succeed:i,fail:o,respond:h})=>{i(({snapshot:r,effect:l})=>{this.$nextTick(()=>{e.id===s&&(this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked())})})}),this.$watch("search",()=>{this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked()})},checkIfAllCheckboxesAreChecked(){this.areAllCheckboxesChecked=this.visibleCheckboxListOptions.length===this.visibleCheckboxListOptions.filter(e=>e.querySelector("input[type=checkbox]:checked, input[type=checkbox]:disabled")).length},toggleAllCheckboxes(){this.checkIfAllCheckboxesAreChecked();let e=!this.areAllCheckboxesChecked;this.visibleCheckboxListOptions.forEach(t=>{let i=t.querySelector("input[type=checkbox]");i.disabled||i.checked!==e&&(i.checked=e,i.dispatchEvent(new Event("change")))}),this.areAllCheckboxesChecked=e},updateVisibleCheckboxListOptions(){this.visibleCheckboxListOptions=this.checkboxListOptions.filter(e=>["",null,void 0].includes(this.search)||e.querySelector(".fi-fo-checkbox-list-option-label")?.innerText.toLowerCase().includes(this.search.toLowerCase())?!0:e.querySelector(".fi-fo-checkbox-list-option-description")?.innerText.toLowerCase().includes(this.search.toLowerCase()))}}}export{c as default}; 2 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests/Unit 10 | 11 | 12 | tests/Feature 13 | 14 | 15 | 16 | 17 | app 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Listen for Xdebug", 9 | "type": "php", 10 | "request": "launch", 11 | "port": 9003 12 | }, 13 | { 14 | "name": "Launch currently open script", 15 | "type": "php", 16 | "request": "launch", 17 | "program": "${file}", 18 | "cwd": "${fileDirname}", 19 | "port": 0, 20 | "runtimeArgs": [ 21 | "-dxdebug.start_with_request=yes" 22 | ], 23 | "env": { 24 | "XDEBUG_MODE": "debug,develop", 25 | "XDEBUG_CONFIG": "client_port=${port}" 26 | } 27 | }, 28 | { 29 | "name": "Launch Built-in web server", 30 | "type": "php", 31 | "request": "launch", 32 | "runtimeArgs": [ 33 | "-dxdebug.mode=debug", 34 | "-dxdebug.start_with_request=yes", 35 | "-S", 36 | "localhost:0" 37 | ], 38 | "program": "", 39 | "cwd": "${workspaceRoot}", 40 | "port": 9003, 41 | "serverReadyAction": { 42 | "pattern": "Development Server \\(http://localhost:([0-9]+)\\) started", 43 | "uriFormat": "http://localhost:%s", 44 | "action": "openExternally" 45 | } 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $blueprint->string('name'); 17 | $blueprint->string('email')->unique(); 18 | $blueprint->string('avatar')->nullable(); 19 | $blueprint->timestamp('email_verified_at')->nullable(); 20 | $blueprint->string('password'); 21 | $blueprint->rememberToken(); 22 | $blueprint->timestamps(); 23 | }); 24 | 25 | Schema::create('password_reset_tokens', function (Blueprint $blueprint): void { 26 | $blueprint->string('email')->primary(); 27 | $blueprint->string('token'); 28 | $blueprint->timestamp('created_at')->nullable(); 29 | }); 30 | 31 | Schema::create('sessions', function (Blueprint $blueprint): void { 32 | $blueprint->string('id')->primary(); 33 | $blueprint->foreignId('user_id')->nullable()->index(); 34 | $blueprint->string('ip_address', 45)->nullable(); 35 | $blueprint->text('user_agent')->nullable(); 36 | $blueprint->longText('payload'); 37 | $blueprint->integer('last_activity')->index(); 38 | }); 39 | } 40 | 41 | /** 42 | * Reverse the migrations. 43 | */ 44 | public function down(): void 45 | { 46 | Schema::dropIfExists('users'); 47 | Schema::dropIfExists('password_reset_tokens'); 48 | Schema::dropIfExists('sessions'); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/index.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-ext-wght-normal-IYF56FF6.woff2") format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-wght-normal-JEOLYBOO.woff2") format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-ext-wght-normal-EOVOK2B5.woff2") format("woff2-variations");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-wght-normal-IRE366VL.woff2") format("woff2-variations");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-vietnamese-wght-normal-CE5GGD3W.woff2") format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-ext-wght-normal-HA22NDSG.woff2") format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-wght-normal-NRMW37G5.woff2") format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD} 2 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withPaths([ 15 | __DIR__.'/app', 16 | __DIR__.'/bootstrap/app.php', 17 | // __DIR__.'/config', 18 | __DIR__.'/database', 19 | __DIR__.'/public', 20 | // __DIR__.'/tests', 21 | ]) 22 | ->withSkipPath(__DIR__.'/app/Filament') 23 | // ->withSkipPath(__DIR__.'/app/Livewire') 24 | ->withSkip([ 25 | StaticArrowFunctionRector::class, 26 | StaticClosureRector::class, 27 | AddOverrideAttributeToOverriddenMethodsRector::class, 28 | EncapsedStringsToSprintfRector::class, 29 | ]) 30 | // here we can define, what prepared sets of rules will be applied 31 | ->withPreparedSets( 32 | deadCode: true, 33 | codeQuality: true, 34 | codingStyle: true, 35 | typeDeclarations: true, 36 | privatization: true, 37 | naming: true, 38 | instanceOf: true, 39 | earlyReturn: true, 40 | strictBooleans: true, 41 | ) 42 | ->withPhpSets(php84: true) 43 | ->withSets([ 44 | LaravelLevelSetList::UP_TO_LARAVEL_110, 45 | LaravelSetList::LARAVEL_ARRAY_STR_FUNCTION_TO_STATIC_CALL, 46 | LaravelSetList::LARAVEL_CODE_QUALITY, 47 | LaravelSetList::LARAVEL_ELOQUENT_MAGIC_METHOD_TO_QUERY_BUILDER, 48 | LaravelSetList::LARAVEL_FACADE_ALIASES_TO_FULL_NAMES, 49 | LaravelSetList::LARAVEL_LEGACY_FACTORIES_TO_CLASSES, 50 | LaravelSetList::LARAVEL_IF_HELPERS, 51 | LaravelSetList::LARAVEL_CONTAINER_STRING_TO_FULLY_QUALIFIED_NAME, 52 | LaravelSetList::LARAVEL_COLLECTION, 53 | ]); 54 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "notPath": [ 4 | "tests/TestCase.php" 5 | ], 6 | "rules": { 7 | "array_push": true, 8 | "backtick_to_shell_exec": true, 9 | "date_time_immutable": true, 10 | "declare_strict_types": true, 11 | "lowercase_keywords": true, 12 | "lowercase_static_reference": true, 13 | "final_class": true, 14 | "final_internal_class": true, 15 | "final_public_method_for_abstract_class": true, 16 | "fully_qualified_strict_types": true, 17 | "global_namespace_import": { 18 | "import_classes": true, 19 | "import_constants": true, 20 | "import_functions": true 21 | }, 22 | "mb_str_functions": true, 23 | "modernize_types_casting": true, 24 | "new_with_parentheses": false, 25 | "no_superfluous_elseif": true, 26 | "no_useless_else": true, 27 | "no_multiple_statements_per_line": true, 28 | "ordered_class_elements": { 29 | "order": [ 30 | "use_trait", 31 | "case", 32 | "constant", 33 | "constant_public", 34 | "constant_protected", 35 | "constant_private", 36 | "property_public", 37 | "property_protected", 38 | "property_private", 39 | "construct", 40 | "destruct", 41 | "magic", 42 | "phpunit", 43 | "method_abstract", 44 | "method_public_static", 45 | "method_public", 46 | "method_protected_static", 47 | "method_protected", 48 | "method_private_static", 49 | "method_private" 50 | ], 51 | "sort_algorithm": "none" 52 | }, 53 | "ordered_interfaces": true, 54 | "ordered_traits": true, 55 | "protected_to_private": true, 56 | "self_accessor": true, 57 | "self_static_accessor": true, 58 | "strict_comparison": true, 59 | "visibility_required": true 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000002_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $blueprint->string('queue')->index(); 17 | $blueprint->longText('payload'); 18 | $blueprint->unsignedTinyInteger('attempts'); 19 | $blueprint->unsignedInteger('reserved_at')->nullable(); 20 | $blueprint->unsignedInteger('available_at'); 21 | $blueprint->unsignedInteger('created_at'); 22 | }); 23 | 24 | Schema::create('job_batches', function (Blueprint $blueprint): void { 25 | $blueprint->string('id')->primary(); 26 | $blueprint->string('name'); 27 | $blueprint->integer('total_jobs'); 28 | $blueprint->integer('pending_jobs'); 29 | $blueprint->integer('failed_jobs'); 30 | $blueprint->longText('failed_job_ids'); 31 | $blueprint->mediumText('options')->nullable(); 32 | $blueprint->integer('cancelled_at')->nullable(); 33 | $blueprint->integer('created_at'); 34 | $blueprint->integer('finished_at')->nullable(); 35 | }); 36 | 37 | Schema::create('failed_jobs', function (Blueprint $blueprint): void { 38 | $blueprint->id(); 39 | $blueprint->string('uuid')->unique(); 40 | $blueprint->text('connection'); 41 | $blueprint->text('queue'); 42 | $blueprint->longText('payload'); 43 | $blueprint->longText('exception'); 44 | $blueprint->timestamp('failed_at')->useCurrent(); 45 | }); 46 | } 47 | 48 | /** 49 | * Reverse the migrations. 50 | */ 51 | public function down(): void 52 | { 53 | Schema::dropIfExists('jobs'); 54 | Schema::dropIfExists('job_batches'); 55 | Schema::dropIfExists('failed_jobs'); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /public/js/filament/schemas/schemas.js: -------------------------------------------------------------------------------- 1 | (()=>{var d=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let t=this.$el.parentElement;t&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(t),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let t=this.$el.parentElement;if(!t)return;let e=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=t.offsetWidth+parseInt(e.marginInlineStart,10)*-1+parseInt(e.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});var u=function(t,e,n){let i=t;if(e.startsWith("/")&&(n=!0,e=e.slice(1)),n)return e;for(;e.startsWith("../");)i=i.includes(".")?i.slice(0,i.lastIndexOf(".")):null,e=e.slice(3);return["",null,void 0].includes(i)?e:["",null,void 0].includes(e)?i:`${i}.${e}`},c=t=>{let e=Alpine.findClosest(t,n=>n.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire};document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:t})=>({handleFormValidationError(e){e.detail.livewireId===t&&this.$nextTick(()=>{let n=this.$el.querySelector("[data-validation-error]");if(!n)return;let i=n;for(;i;)i.dispatchEvent(new CustomEvent("expand")),i=i.parentNode;setTimeout(()=>n.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})},isStateChanged(e,n){if(e===void 0)return!1;try{return JSON.stringify(e)!==JSON.stringify(n)}catch{return e!==n}}})),window.Alpine.data("filamentSchemaComponent",({path:t,containerPath:e,$wire:n})=>({$statePath:t,$get:(i,s)=>n.$get(u(e,i,s)),$set:(i,s,a,o=!1)=>n.$set(u(e,i,a),s,o),get $state(){return n.$get(t)}})),window.Alpine.data("filamentActionsSchemaComponent",d),Livewire.hook("commit",({component:t,commit:e,respond:n,succeed:i,fail:s})=>{i(({snapshot:a,effects:o})=>{o.dispatches?.forEach(r=>{if(!r.params?.awaitSchemaComponent)return;let l=Array.from(t.el.querySelectorAll(`[wire\\:partial="schema-component::${r.params.awaitSchemaComponent}"]`)).filter(h=>c(h)===t);if(l.length!==1){if(l.length>1)throw`Multiple schema components found with key [${r.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${t.id}-${r.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(r.name,{detail:r.params}))},{once:!0})}})})})});})(); 2 | -------------------------------------------------------------------------------- /app/Providers/Filament/AdminPanelProvider.php: -------------------------------------------------------------------------------- 1 | default() 29 | ->id('admin') 30 | ->path('admin') 31 | ->login() 32 | ->colors([ 33 | 'primary' => Color::Amber, 34 | ]) 35 | ->discoverResources( 36 | in: app_path('Filament/Resources'), 37 | for: 'App\\Filament\\Resources', 38 | ) 39 | ->discoverPages( 40 | in: app_path('Filament/Pages'), 41 | for: 'App\\Filament\\Pages', 42 | ) 43 | ->pages([ 44 | Dashboard::class, 45 | ]) 46 | ->discoverWidgets( 47 | in: app_path('Filament/Widgets'), 48 | for: 'App\\Filament\\Widgets', 49 | ) 50 | ->widgets([ 51 | AccountWidget::class, 52 | FilamentInfoWidget::class, 53 | ]) 54 | ->plugin(FilamentSpatieRolesPermissionsPlugin::make()) 55 | ->middleware([ 56 | EncryptCookies::class, 57 | AddQueuedCookiesToResponse::class, 58 | StartSession::class, 59 | AuthenticateSession::class, 60 | ShareErrorsFromSession::class, 61 | VerifyCsrfToken::class, 62 | SubstituteBindings::class, 63 | DisableBladeIconComponents::class, 64 | DispatchServingFilamentEvent::class, 65 | ]) 66 | ->authMiddleware([ 67 | Authenticate::class, 68 | ]); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DISK', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Filesystem Disks 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Below you may configure as many filesystem disks as necessary, and you 24 | | may even configure multiple disks for the same driver. Examples for 25 | | most supported storage drivers are configured here for reference. 26 | | 27 | | Supported drivers: "local", "ftp", "sftp", "s3" 28 | | 29 | */ 30 | 31 | 'disks' => [ 32 | 33 | 'local' => [ 34 | 'driver' => 'local', 35 | 'root' => storage_path('app/private'), 36 | 'serve' => true, 37 | 'throw' => false, 38 | 'report' => false, 39 | ], 40 | 41 | 'public' => [ 42 | 'driver' => 'local', 43 | 'root' => storage_path('app/public'), 44 | 'url' => env('APP_URL').'/storage', 45 | 'visibility' => 'public', 46 | 'throw' => false, 47 | 'report' => false, 48 | ], 49 | 50 | 's3' => [ 51 | 'driver' => 's3', 52 | 'key' => env('AWS_ACCESS_KEY_ID'), 53 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 54 | 'region' => env('AWS_DEFAULT_REGION'), 55 | 'bucket' => env('AWS_BUCKET'), 56 | 'url' => env('AWS_URL'), 57 | 'endpoint' => env('AWS_ENDPOINT'), 58 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 59 | 'throw' => false, 60 | 'report' => false, 61 | ], 62 | 63 | ], 64 | 65 | /* 66 | |-------------------------------------------------------------------------- 67 | | Symbolic Links 68 | |-------------------------------------------------------------------------- 69 | | 70 | | Here you may configure the symbolic links that will be created when the 71 | | `storage:link` Artisan command is executed. The array keys should be 72 | | the locations of the links and the values should be their targets. 73 | | 74 | */ 75 | 76 | 'links' => [ 77 | public_path('storage') => storage_path('app/public'), 78 | ], 79 | 80 | ]; 81 | -------------------------------------------------------------------------------- /app/Providers/Filament/UserPanelProvider.php: -------------------------------------------------------------------------------- 1 | id('user') 31 | ->path('user') 32 | ->login() 33 | ->registration() 34 | ->passwordReset() 35 | ->emailVerification() 36 | ->emailChangeVerification() 37 | ->profile(EditProfile::class, isSimple: false) 38 | ->navigationItems([ 39 | NavigationItem::make(__('Profile')) 40 | ->url('/user/profile') 41 | ->icon('heroicon-o-user-circle') 42 | ->sort(1), 43 | ]) 44 | ->colors([ 45 | 'primary' => Color::Amber, 46 | ]) 47 | ->discoverResources(in: app_path('Filament/User/Resources'), for: 'App\Filament\User\Resources') 48 | ->discoverPages(in: app_path('Filament/User/Pages'), for: 'App\Filament\User\Pages') 49 | ->pages([ 50 | Dashboard::class, 51 | ]) 52 | ->discoverWidgets(in: app_path('Filament/User/Widgets'), for: 'App\Filament\User\Widgets') 53 | ->widgets([ 54 | AccountWidget::class, 55 | ]) 56 | ->middleware([ 57 | EncryptCookies::class, 58 | AddQueuedCookiesToResponse::class, 59 | StartSession::class, 60 | AuthenticateSession::class, 61 | ShareErrorsFromSession::class, 62 | VerifyCsrfToken::class, 63 | SubstituteBindings::class, 64 | DisableBladeIconComponents::class, 65 | DispatchServingFilamentEvent::class, 66 | ]) 67 | ->authMiddleware([ 68 | Authenticate::class, 69 | ]); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /public/css/filament/support/support.css: -------------------------------------------------------------------------------- 1 | .fi-pagination-items,.fi-pagination-overview,.fi-pagination-records-per-page-select:not(.fi-compact){display:none}@supports (container-type:inline-size){.fi-pagination{container-type:inline-size}@container (min-width: 28rem){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@container (min-width: 56rem){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:640px){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@media (min-width:768px){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{background-color:#333;border-radius:4px;color:#fff;font-size:14px;line-height:1.4;outline:0;position:relative;transition-property:transform,visibility,opacity;white-space:normal}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-top-color:initial;border-width:8px 8px 0;bottom:-7px;left:0;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:initial;border-width:0 8px 8px;left:0;top:-7px;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-left-color:initial;border-width:8px 0 8px 8px;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{border-right-color:initial;border-width:8px 8px 8px 0;left:-7px;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{color:#333;height:16px;width:16px}.tippy-arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.tippy-content{padding:5px 9px;position:relative;z-index:1}.tippy-box[data-theme~=light]{background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;color:#26323d}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.fi-sortable-ghost{opacity:.3} -------------------------------------------------------------------------------- /public/js/filament/tables/components/table.js: -------------------------------------------------------------------------------- 1 | function d(){return{checkboxClickController:null,collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,init:function(){this.livewireId=this.$root.closest("[wire\\:id]").attributes["wire:id"].value,this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1}),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction:function(e,t=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,t)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){this.isLoading=!0;let t=await this.$wire.getGroupedSelectableTableRecordKeys(e);this.areRecordsSelected(this.getRecordsInGroupOnPage(e))?this.deselectRecords(t):this.selectRecords(t),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let t=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])s.dataset.group===e&&t.push(s.value);return t},getRecordsOnPage:function(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords:function(e){for(let t of e)this.isRecordSelected(t)||this.selectedRecords.push(t)},deselectRecords:function(e){for(let t of e){let s=this.selectedRecords.indexOf(t);s!==-1&&this.selectedRecords.splice(s,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(t=>this.isRecordSelected(t))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]},watchForCheckboxClicks:function(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:e}=this.checkboxClickController;this.$root?.addEventListener("click",t=>t.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(t,t.target),{signal:e})},handleCheckboxClick:function(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let s=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!s.includes(this.lastChecked)){this.lastChecked=t;return}let l=s.indexOf(this.lastChecked),r=s.indexOf(t),o=[l,r].sort((c,n)=>c-n),i=[];for(let c=o[0];c<=o[1];c++)s[c].checked=t.checked,i.push(s[c].value);t.checked?this.selectRecords(i):this.deselectRecords(i)}this.lastChecked=t}}}export{d as default}; 2 | -------------------------------------------------------------------------------- /config/settings.php: -------------------------------------------------------------------------------- 1 | [ 10 | 11 | ], 12 | 13 | /* 14 | * The path where the settings classes will be created. 15 | */ 16 | 'setting_class_path' => app_path('Settings'), 17 | 18 | /* 19 | * In these directories settings migrations will be stored and ran when migrating. A settings 20 | * migration created via the make:settings-migration command will be stored in the first path or 21 | * a custom defined path when running the command. 22 | */ 23 | 'migrations_paths' => [ 24 | database_path('settings'), 25 | ], 26 | 27 | /* 28 | * When no repository was set for a settings class the following repository 29 | * will be used for loading and saving settings. 30 | */ 31 | 'default_repository' => 'database', 32 | 33 | /* 34 | * Settings will be stored and loaded from these repositories. 35 | */ 36 | 'repositories' => [ 37 | 'database' => [ 38 | 'type' => Spatie\LaravelSettings\SettingsRepositories\DatabaseSettingsRepository::class, 39 | 'model' => null, 40 | 'table' => null, 41 | 'connection' => null, 42 | ], 43 | 'redis' => [ 44 | 'type' => Spatie\LaravelSettings\SettingsRepositories\RedisSettingsRepository::class, 45 | 'connection' => null, 46 | 'prefix' => null, 47 | ], 48 | ], 49 | 50 | /* 51 | * The encoder and decoder will determine how settings are stored and 52 | * retrieved in the database. By default, `json_encode` and `json_decode` 53 | * are used. 54 | */ 55 | 'encoder' => null, 56 | 'decoder' => null, 57 | 58 | /* 59 | * The contents of settings classes can be cached through your application, 60 | * settings will be stored within a provided Laravel store and can have an 61 | * additional prefix. 62 | */ 63 | 'cache' => [ 64 | 'enabled' => env('SETTINGS_CACHE_ENABLED', false), 65 | 'store' => null, 66 | 'prefix' => null, 67 | 'ttl' => null, 68 | ], 69 | 70 | /* 71 | * These global casts will be automatically used whenever a property within 72 | * your settings class isn't a default PHP type. 73 | */ 74 | 'global_casts' => [ 75 | DateTimeInterface::class => Spatie\LaravelSettings\SettingsCasts\DateTimeInterfaceCast::class, 76 | DateTimeZone::class => Spatie\LaravelSettings\SettingsCasts\DateTimeZoneCast::class, 77 | // Spatie\DataTransferObject\DataTransferObject::class => Spatie\LaravelSettings\SettingsCasts\DtoCast::class, 78 | Spatie\LaravelData\Data::class => Spatie\LaravelSettings\SettingsCasts\DataCast::class, 79 | ], 80 | 81 | /* 82 | * The package will look for settings in these paths and automatically 83 | * register them. 84 | */ 85 | 'auto_discover_settings' => [ 86 | app_path('Settings'), 87 | ], 88 | 89 | /* 90 | * Automatically discovered settings classes can be cached, so they don't 91 | * need to be searched each time the application boots up. 92 | */ 93 | 'discovered_settings_cache_path' => base_path('bootstrap/cache'), 94 | ]; 95 | -------------------------------------------------------------------------------- /app/Filament/Resources/Users/UserResource.php: -------------------------------------------------------------------------------- 1 | components([ 33 | TextInput::make('name')->required()->maxLength(255), 34 | TextInput::make('email') 35 | ->email() 36 | ->required() 37 | ->maxLength(255), 38 | FileUpload::make('avatar') 39 | ->disk('public') 40 | ->directory('avatars') 41 | ->avatar(), 42 | Select::make('timezone') 43 | ->options(array_combine(DateTimeZone::listIdentifiers(), DateTimeZone::listIdentifiers())) 44 | ->searchable(), 45 | DateTimePicker::make('email_verified_at'), 46 | TextInput::make('password') 47 | ->password() 48 | ->maxLength(255) 49 | ->required(fn ($component, $get, $livewire, $model, $record, $set, $state): bool => $record === null) 50 | ->dehydrateStateUsing(fn ($state) => empty($state) ? '' : Hash::make($state)), 51 | ]); 52 | } 53 | 54 | public static function table(Table $table): Table 55 | { 56 | return $table 57 | ->columns([ 58 | TextColumn::make('id')->searchable(), 59 | TextColumn::make('name')->searchable(), 60 | TextColumn::make('email')->searchable(), 61 | ImageColumn::make('avatar')->circular(), 62 | TextColumn::make('email_verified_at')->dateTime()->sortable(), 63 | TextColumn::make('created_at') 64 | ->dateTime() 65 | ->sortable() 66 | ->toggleable(isToggledHiddenByDefault: true), 67 | TextColumn::make('updated_at') 68 | ->dateTime() 69 | ->sortable() 70 | ->toggleable(isToggledHiddenByDefault: true), 71 | ]) 72 | ->defaultSort('id', 'desc') 73 | ->filters([ 74 | // 75 | ]) 76 | ->recordActions([ 77 | EditAction::make(), 78 | ]) 79 | ->toolbarActions([ 80 | BulkActionGroup::make([ 81 | DeleteBulkAction::make(), 82 | ]), 83 | ]); 84 | } 85 | 86 | public static function getRelations(): array 87 | { 88 | return [ 89 | // 90 | ]; 91 | } 92 | 93 | public static function getPages(): array 94 | { 95 | return [ 96 | 'index' => ListUsers::route('/'), 97 | 'create' => CreateUser::route('/create'), 98 | 'edit' => EditUser::route('/{record}/edit'), 99 | ]; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://getcomposer.org/schema.json", 3 | "name": "laravel/laravel", 4 | "type": "project", 5 | "description": "The skeleton application for the Laravel framework.", 6 | "keywords": [ 7 | "laravel", 8 | "framework" 9 | ], 10 | "license": "MIT", 11 | "require": { 12 | "php": "^8.4", 13 | "althinect/filament-spatie-roles-permissions": "3.x-dev", 14 | "filament/filament": "^4.2", 15 | "filament/spatie-laravel-settings-plugin": "^4.2", 16 | "laravel/framework": "^12.40", 17 | "laravel/tinker": "^2.10", 18 | "nunomaduro/essentials": "^1.0", 19 | "pinkary-project/type-guard": "^0.1.0", 20 | "spatie/laravel-permission": "^6.23", 21 | "spatie/laravel-settings": "^3.5" 22 | }, 23 | "require-dev": { 24 | "barryvdh/laravel-debugbar": "^3.15", 25 | "barryvdh/laravel-ide-helper": "^3.5", 26 | "driftingly/rector-laravel": "^2.0", 27 | "fakerphp/faker": "^1.24", 28 | "filament/upgrade": "^4.0", 29 | "larastan/larastan": "^3.2", 30 | "laravel/boost": "^1.0", 31 | "laravel/pail": "^1.2", 32 | "laravel/pint": "^1.21", 33 | "laravel/sail": "^1.41", 34 | "mockery/mockery": "^1.6", 35 | "nunomaduro/collision": "^8.7", 36 | "phpunit/phpunit": "^11.5" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "App\\": "app/", 41 | "Database\\Factories\\": "database/factories/", 42 | "Database\\Seeders\\": "database/seeders/" 43 | }, 44 | "files": [ 45 | "app/helpers.php" 46 | ] 47 | }, 48 | "autoload-dev": { 49 | "psr-4": { 50 | "Tests\\": "tests/" 51 | } 52 | }, 53 | "scripts": { 54 | "post-autoload-dump": [ 55 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 56 | "@php artisan package:discover --ansi", 57 | "@php artisan filament:upgrade" 58 | ], 59 | "post-update-cmd": [ 60 | "@php artisan vendor:publish --tag=laravel-assets --ansi --force" 61 | ], 62 | "post-root-package-install": [ 63 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 64 | ], 65 | "post-create-project-cmd": [ 66 | "@php artisan key:generate --ansi", 67 | "@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"", 68 | "@php artisan migrate --graceful --ansi" 69 | ], 70 | "dev": [ 71 | "Composer\\Config::disableProcessTimeout", 72 | "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite" 73 | ], 74 | "ide": [ 75 | "php artisan ide-helper:models --write --reset", 76 | "./vendor/bin/rector", 77 | "./vendor/bin/pint --parallel" 78 | ], 79 | "latest": [ 80 | "composer show --no-dev --direct --name-only | xargs composer require" 81 | ], 82 | "format": [ 83 | "mago fmt", 84 | "./vendor/bin/pint --parallel" 85 | ] 86 | }, 87 | "extra": { 88 | "laravel": { 89 | "dont-discover": [] 90 | } 91 | }, 92 | "config": { 93 | "optimize-autoloader": true, 94 | "preferred-install": "dist", 95 | "sort-packages": true, 96 | "allow-plugins": { 97 | "pestphp/pest-plugin": true, 98 | "php-http/discovery": true 99 | } 100 | }, 101 | "minimum-stability": "stable", 102 | "prefer-stable": true 103 | } 104 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_MAILER', 'log'), 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Mailer Configurations 22 | |-------------------------------------------------------------------------- 23 | | 24 | | Here you may configure all of the mailers used by your application plus 25 | | their respective settings. Several examples have been configured for 26 | | you and you are free to add your own as your application requires. 27 | | 28 | | Laravel supports a variety of mail "transport" drivers that can be used 29 | | when delivering an email. You may specify which one you're using for 30 | | your mailers below. You may also add additional mailers if needed. 31 | | 32 | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", 33 | | "postmark", "resend", "log", "array", 34 | | "failover", "roundrobin" 35 | | 36 | */ 37 | 38 | 'mailers' => [ 39 | 40 | 'smtp' => [ 41 | 'transport' => 'smtp', 42 | 'scheme' => env('MAIL_SCHEME'), 43 | 'url' => env('MAIL_URL'), 44 | 'host' => env('MAIL_HOST', '127.0.0.1'), 45 | 'port' => env('MAIL_PORT', 2525), 46 | 'username' => env('MAIL_USERNAME'), 47 | 'password' => env('MAIL_PASSWORD'), 48 | 'timeout' => null, 49 | 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)), 50 | ], 51 | 52 | 'ses' => [ 53 | 'transport' => 'ses', 54 | ], 55 | 56 | 'postmark' => [ 57 | 'transport' => 'postmark', 58 | // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), 59 | // 'client' => [ 60 | // 'timeout' => 5, 61 | // ], 62 | ], 63 | 64 | 'resend' => [ 65 | 'transport' => 'resend', 66 | ], 67 | 68 | 'sendmail' => [ 69 | 'transport' => 'sendmail', 70 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), 71 | ], 72 | 73 | 'log' => [ 74 | 'transport' => 'log', 75 | 'channel' => env('MAIL_LOG_CHANNEL'), 76 | ], 77 | 78 | 'array' => [ 79 | 'transport' => 'array', 80 | ], 81 | 82 | 'failover' => [ 83 | 'transport' => 'failover', 84 | 'mailers' => [ 85 | 'smtp', 86 | 'log', 87 | ], 88 | 'retry_after' => 60, 89 | ], 90 | 91 | 'roundrobin' => [ 92 | 'transport' => 'roundrobin', 93 | 'mailers' => [ 94 | 'ses', 95 | 'postmark', 96 | ], 97 | 'retry_after' => 60, 98 | ], 99 | 100 | ], 101 | 102 | /* 103 | |-------------------------------------------------------------------------- 104 | | Global "From" Address 105 | |-------------------------------------------------------------------------- 106 | | 107 | | You may wish for all emails sent by your application to be sent from 108 | | the same address. Here you may specify a name and address that is 109 | | used globally for all emails that are sent by your application. 110 | | 111 | */ 112 | 113 | 'from' => [ 114 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 115 | 'name' => env('MAIL_FROM_NAME', 'Example'), 116 | ], 117 | 118 | ]; 119 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_STORE', 'database'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the cache "stores" for your application as 26 | | well as their drivers. You may even define multiple stores for the 27 | | same cache driver to group types of items stored in your caches. 28 | | 29 | | Supported drivers: "array", "database", "file", "memcached", 30 | | "redis", "dynamodb", "octane", 31 | | "failover", "null" 32 | | 33 | */ 34 | 35 | 'stores' => [ 36 | 37 | 'array' => [ 38 | 'driver' => 'array', 39 | 'serialize' => false, 40 | ], 41 | 42 | 'database' => [ 43 | 'driver' => 'database', 44 | 'connection' => env('DB_CACHE_CONNECTION'), 45 | 'table' => env('DB_CACHE_TABLE', 'cache'), 46 | 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), 47 | 'lock_table' => env('DB_CACHE_LOCK_TABLE'), 48 | ], 49 | 50 | 'file' => [ 51 | 'driver' => 'file', 52 | 'path' => storage_path('framework/cache/data'), 53 | 'lock_path' => storage_path('framework/cache/data'), 54 | ], 55 | 56 | 'memcached' => [ 57 | 'driver' => 'memcached', 58 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 59 | 'sasl' => [ 60 | env('MEMCACHED_USERNAME'), 61 | env('MEMCACHED_PASSWORD'), 62 | ], 63 | 'options' => [ 64 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 65 | ], 66 | 'servers' => [ 67 | [ 68 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 69 | 'port' => env('MEMCACHED_PORT', 11211), 70 | 'weight' => 100, 71 | ], 72 | ], 73 | ], 74 | 75 | 'redis' => [ 76 | 'driver' => 'redis', 77 | 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), 78 | 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), 79 | ], 80 | 81 | 'dynamodb' => [ 82 | 'driver' => 'dynamodb', 83 | 'key' => env('AWS_ACCESS_KEY_ID'), 84 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 85 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 86 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 87 | 'endpoint' => env('DYNAMODB_ENDPOINT'), 88 | ], 89 | 90 | 'octane' => [ 91 | 'driver' => 'octane', 92 | ], 93 | 94 | 'failover' => [ 95 | 'driver' => 'failover', 96 | 'stores' => [ 97 | 'database', 98 | 'array', 99 | ], 100 | ], 101 | 102 | ], 103 | 104 | /* 105 | |-------------------------------------------------------------------------- 106 | | Cache Key Prefix 107 | |-------------------------------------------------------------------------- 108 | | 109 | | When utilizing the APC, database, memcached, Redis, and DynamoDB cache 110 | | stores, there might be other applications using the same cache. For 111 | | that reason, you may prefix every cache key to avoid collisions. 112 | | 113 | */ 114 | 115 | 'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'), 116 | 117 | ]; 118 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | $notifications 33 | * @property-read int|null $notifications_count 34 | * @property-read \Illuminate\Database\Eloquent\Collection $permissions 35 | * @property-read int|null $permissions_count 36 | * @property-read \Illuminate\Database\Eloquent\Collection $roles 37 | * @property-read int|null $roles_count 38 | * 39 | * @method static \Database\Factories\UserFactory factory($count = null, $state = []) 40 | * @method static Builder|User newModelQuery() 41 | * @method static Builder|User newQuery() 42 | * @method static Builder|User permission($permissions, $without = false) 43 | * @method static Builder|User query() 44 | * @method static Builder|User role($roles, $guard = null, $without = false) 45 | * @method static Builder|User withoutPermission($permissions) 46 | * @method static Builder|User withoutRole($roles, $guard = null) 47 | * 48 | * @mixin \Illuminate\Database\Eloquent\Model 49 | */ 50 | class User extends Authenticatable implements FilamentUser, HasAvatar, MustVerifyEmail 51 | { 52 | use AuthMustVerifyEmail; 53 | 54 | /** @use HasFactory */ 55 | use HasFactory; 56 | 57 | use HasRoles; 58 | use Notifiable; 59 | 60 | /** 61 | * The attributes that are mass assignable. 62 | * 63 | * @var list 64 | */ 65 | protected $fillable = [ 66 | 'name', 67 | 'email', 68 | 'avatar', 69 | 'password', 70 | 'timezone', 71 | ]; 72 | 73 | /** 74 | * The attributes that should be hidden for serialization. 75 | * 76 | * @var list 77 | */ 78 | protected $hidden = [ 79 | 'password', 80 | 'remember_token', 81 | ]; 82 | 83 | /** 84 | * Get the attributes that should be cast. 85 | * 86 | * @return array 87 | */ 88 | protected function casts(): array 89 | { 90 | return [ 91 | 'email_verified_at' => 'datetime', 92 | 'password' => 'hashed', 93 | ]; 94 | } 95 | 96 | public function isAdmin(): bool 97 | { 98 | return $this->email === config('app.admin_email'); 99 | } 100 | 101 | public function canAccessPanel(Panel $panel): bool 102 | { 103 | if ($panel->getId() === 'admin') { 104 | return $this->isAdmin(); 105 | } 106 | 107 | return true; 108 | } 109 | 110 | public function getFilamentAvatarUrl(): ?string 111 | { 112 | return $this->avatar 113 | ? diskPublic()->url($this->avatar) 114 | : ('https://gravatar.com/avatar/'.hash('sha256', $this->email)); 115 | } 116 | 117 | /** 118 | * Send the email verification notification. 119 | */ 120 | public function sendEmailVerificationNotification(): void 121 | { 122 | $this->notify(new VerifyEmail); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => env('AUTH_GUARD', 'web'), 18 | 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | which utilizes session storage plus the Eloquent user provider. 29 | | 30 | | All authentication guards have a user provider, which defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | system used by the application. Typically, Eloquent is utilized. 33 | | 34 | | Supported: "session" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'web' => [ 40 | 'driver' => 'session', 41 | 'provider' => 'users', 42 | ], 43 | ], 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | User Providers 48 | |-------------------------------------------------------------------------- 49 | | 50 | | All authentication guards have a user provider, which defines how the 51 | | users are actually retrieved out of your database or other storage 52 | | system used by the application. Typically, Eloquent is utilized. 53 | | 54 | | If you have multiple user tables or models you may configure multiple 55 | | providers to represent the model / table. These providers may then 56 | | be assigned to any extra authentication guards you have defined. 57 | | 58 | | Supported: "database", "eloquent" 59 | | 60 | */ 61 | 62 | 'providers' => [ 63 | 'users' => [ 64 | 'driver' => 'eloquent', 65 | 'model' => env('AUTH_MODEL', App\Models\User::class), 66 | ], 67 | 68 | // 'users' => [ 69 | // 'driver' => 'database', 70 | // 'table' => 'users', 71 | // ], 72 | ], 73 | 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | Resetting Passwords 77 | |-------------------------------------------------------------------------- 78 | | 79 | | These configuration options specify the behavior of Laravel's password 80 | | reset functionality, including the table utilized for token storage 81 | | and the user provider that is invoked to actually retrieve users. 82 | | 83 | | The expiry time is the number of minutes that each reset token will be 84 | | considered valid. This security feature keeps tokens short-lived so 85 | | they have less time to be guessed. You may change this as needed. 86 | | 87 | | The throttle setting is the number of seconds a user must wait before 88 | | generating more password reset tokens. This prevents the user from 89 | | quickly generating a very large amount of password reset tokens. 90 | | 91 | */ 92 | 93 | 'passwords' => [ 94 | 'users' => [ 95 | 'provider' => 'users', 96 | 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), 97 | 'expire' => 60, 98 | 'throttle' => 60, 99 | ], 100 | ], 101 | 102 | /* 103 | |-------------------------------------------------------------------------- 104 | | Password Confirmation Timeout 105 | |-------------------------------------------------------------------------- 106 | | 107 | | Here you may define the number of seconds before a password confirmation 108 | | window expires and users are asked to re-enter their password via the 109 | | confirmation screen. By default, the timeout lasts for three hours. 110 | | 111 | */ 112 | 113 | 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), 114 | 115 | ]; 116 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_CONNECTION', 'database'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Queue Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the connection options for every queue backend 24 | | used by your application. An example configuration is provided for 25 | | each backend supported by Laravel. You're also free to add more. 26 | | 27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", 28 | | "deferred", "background", "failover", "null" 29 | | 30 | */ 31 | 32 | 'connections' => [ 33 | 34 | 'sync' => [ 35 | 'driver' => 'sync', 36 | ], 37 | 38 | 'database' => [ 39 | 'driver' => 'database', 40 | 'connection' => env('DB_QUEUE_CONNECTION'), 41 | 'table' => env('DB_QUEUE_TABLE', 'jobs'), 42 | 'queue' => env('DB_QUEUE', 'default'), 43 | 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), 44 | 'after_commit' => false, 45 | ], 46 | 47 | 'beanstalkd' => [ 48 | 'driver' => 'beanstalkd', 49 | 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), 50 | 'queue' => env('BEANSTALKD_QUEUE', 'default'), 51 | 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), 52 | 'block_for' => 0, 53 | 'after_commit' => false, 54 | ], 55 | 56 | 'sqs' => [ 57 | 'driver' => 'sqs', 58 | 'key' => env('AWS_ACCESS_KEY_ID'), 59 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 60 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 61 | 'queue' => env('SQS_QUEUE', 'default'), 62 | 'suffix' => env('SQS_SUFFIX'), 63 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 64 | 'after_commit' => false, 65 | ], 66 | 67 | 'redis' => [ 68 | 'driver' => 'redis', 69 | 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), 70 | 'queue' => env('REDIS_QUEUE', 'default'), 71 | 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), 72 | 'block_for' => null, 73 | 'after_commit' => false, 74 | ], 75 | 76 | 'deferred' => [ 77 | 'driver' => 'deferred', 78 | ], 79 | 80 | 'background' => [ 81 | 'driver' => 'background', 82 | ], 83 | 84 | 'failover' => [ 85 | 'driver' => 'failover', 86 | 'connections' => [ 87 | 'database', 88 | 'deferred', 89 | ], 90 | ], 91 | 92 | ], 93 | 94 | /* 95 | |-------------------------------------------------------------------------- 96 | | Job Batching 97 | |-------------------------------------------------------------------------- 98 | | 99 | | The following options configure the database and table that store job 100 | | batching information. These options can be updated to any database 101 | | connection and table which has been defined by your application. 102 | | 103 | */ 104 | 105 | 'batching' => [ 106 | 'database' => env('DB_CONNECTION', 'sqlite'), 107 | 'table' => 'job_batches', 108 | ], 109 | 110 | /* 111 | |-------------------------------------------------------------------------- 112 | | Failed Queue Jobs 113 | |-------------------------------------------------------------------------- 114 | | 115 | | These options configure the behavior of failed queue job logging so you 116 | | can control how and where failed jobs are stored. Laravel ships with 117 | | support for storing failed jobs in a simple file or in a database. 118 | | 119 | | Supported drivers: "database-uuids", "dynamodb", "file", "null" 120 | | 121 | */ 122 | 123 | 'failed' => [ 124 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 125 | 'database' => env('DB_CONNECTION', 'sqlite'), 126 | 'table' => 'failed_jobs', 127 | ], 128 | 129 | ]; 130 | -------------------------------------------------------------------------------- /config/app.php: -------------------------------------------------------------------------------- 1 | env('APP_NAME', 'Laravel'), 17 | 'admin_email' => env('ADMIN_EMAIL', 'test@example.com'), 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Application Environment 22 | |-------------------------------------------------------------------------- 23 | | 24 | | This value determines the "environment" your application is currently 25 | | running in. This may determine how you prefer to configure various 26 | | services the application utilizes. Set this in your ".env" file. 27 | | 28 | */ 29 | 30 | 'env' => env('APP_ENV', 'production'), 31 | 32 | /* 33 | |-------------------------------------------------------------------------- 34 | | Application Debug Mode 35 | |-------------------------------------------------------------------------- 36 | | 37 | | When your application is in debug mode, detailed error messages with 38 | | stack traces will be shown on every error that occurs within your 39 | | application. If disabled, a simple generic error page is shown. 40 | | 41 | */ 42 | 43 | 'debug' => (bool) env('APP_DEBUG', false), 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | Application URL 48 | |-------------------------------------------------------------------------- 49 | | 50 | | This URL is used by the console to properly generate URLs when using 51 | | the Artisan command line tool. You should set this to the root of 52 | | the application so that it's available within Artisan commands. 53 | | 54 | */ 55 | 56 | 'url' => env('APP_URL', 'http://localhost'), 57 | 58 | /* 59 | |-------------------------------------------------------------------------- 60 | | Application Timezone 61 | |-------------------------------------------------------------------------- 62 | | 63 | | Here you may specify the default timezone for your application, which 64 | | will be used by the PHP date and date-time functions. The timezone 65 | | is set to "UTC" by default as it is suitable for most use cases. 66 | | 67 | */ 68 | 69 | 'timezone' => env('APP_TIMEZONE', 'UTC'), 70 | 71 | /* 72 | |-------------------------------------------------------------------------- 73 | | Application Locale Configuration 74 | |-------------------------------------------------------------------------- 75 | | 76 | | The application locale determines the default locale that will be used 77 | | by Laravel's translation / localization methods. This option can be 78 | | set to any locale for which you plan to have translation strings. 79 | | 80 | */ 81 | 82 | 'locale' => env('APP_LOCALE', 'en'), 83 | 84 | 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), 85 | 86 | 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), 87 | 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | Encryption Key 91 | |-------------------------------------------------------------------------- 92 | | 93 | | This key is utilized by Laravel's encryption services and should be set 94 | | to a random, 32 character string to ensure that all encrypted values 95 | | are secure. You should do this prior to deploying the application. 96 | | 97 | */ 98 | 99 | 'cipher' => 'AES-256-CBC', 100 | 101 | 'key' => env('APP_KEY'), 102 | 103 | 'previous_keys' => [ 104 | ...array_filter( 105 | explode(',', (string) env('APP_PREVIOUS_KEYS', '')) 106 | ), 107 | ], 108 | 109 | /* 110 | |-------------------------------------------------------------------------- 111 | | Maintenance Mode Driver 112 | |-------------------------------------------------------------------------- 113 | | 114 | | These configuration options determine the driver used to determine and 115 | | manage Laravel's "maintenance mode" status. The "cache" driver will 116 | | allow maintenance mode to be controlled across multiple machines. 117 | | 118 | | Supported drivers: "file", "cache" 119 | | 120 | */ 121 | 122 | 'maintenance' => [ 123 | 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), 124 | 'store' => env('APP_MAINTENANCE_STORE', 'database'), 125 | ], 126 | 127 | ]; 128 | -------------------------------------------------------------------------------- /config/logging.php: -------------------------------------------------------------------------------- 1 | env('LOG_CHANNEL', 'stack'), 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | Deprecations Log Channel 26 | |-------------------------------------------------------------------------- 27 | | 28 | | This option controls the log channel that should be used to log warnings 29 | | regarding deprecated PHP and library features. This allows you to get 30 | | your application ready for upcoming major versions of dependencies. 31 | | 32 | */ 33 | 34 | 'deprecations' => [ 35 | 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), 36 | 'trace' => env('LOG_DEPRECATIONS_TRACE', false), 37 | ], 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | Log Channels 42 | |-------------------------------------------------------------------------- 43 | | 44 | | Here you may configure the log channels for your application. Laravel 45 | | utilizes the Monolog PHP logging library, which includes a variety 46 | | of powerful log handlers and formatters that you're free to use. 47 | | 48 | | Available drivers: "single", "daily", "slack", "syslog", 49 | | "errorlog", "monolog", "custom", "stack" 50 | | 51 | */ 52 | 53 | 'channels' => [ 54 | 55 | 'stack' => [ 56 | 'driver' => 'stack', 57 | 'channels' => explode(',', (string) env('LOG_STACK', 'single')), 58 | 'ignore_exceptions' => false, 59 | ], 60 | 61 | 'single' => [ 62 | 'driver' => 'single', 63 | 'path' => storage_path('logs/laravel.log'), 64 | 'level' => env('LOG_LEVEL', 'debug'), 65 | 'replace_placeholders' => true, 66 | ], 67 | 68 | 'daily' => [ 69 | 'driver' => 'daily', 70 | 'path' => storage_path('logs/laravel.log'), 71 | 'level' => env('LOG_LEVEL', 'debug'), 72 | 'days' => env('LOG_DAILY_DAYS', 14), 73 | 'replace_placeholders' => true, 74 | ], 75 | 76 | 'slack' => [ 77 | 'driver' => 'slack', 78 | 'url' => env('LOG_SLACK_WEBHOOK_URL'), 79 | 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), 80 | 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), 81 | 'level' => env('LOG_LEVEL', 'critical'), 82 | 'replace_placeholders' => true, 83 | ], 84 | 85 | 'papertrail' => [ 86 | 'driver' => 'monolog', 87 | 'level' => env('LOG_LEVEL', 'debug'), 88 | 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), 89 | 'handler_with' => [ 90 | 'host' => env('PAPERTRAIL_URL'), 91 | 'port' => env('PAPERTRAIL_PORT'), 92 | 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), 93 | ], 94 | 'processors' => [PsrLogMessageProcessor::class], 95 | ], 96 | 97 | 'stderr' => [ 98 | 'driver' => 'monolog', 99 | 'level' => env('LOG_LEVEL', 'debug'), 100 | 'handler' => StreamHandler::class, 101 | 'handler_with' => [ 102 | 'stream' => 'php://stderr', 103 | ], 104 | 'formatter' => env('LOG_STDERR_FORMATTER'), 105 | 'processors' => [PsrLogMessageProcessor::class], 106 | ], 107 | 108 | 'syslog' => [ 109 | 'driver' => 'syslog', 110 | 'level' => env('LOG_LEVEL', 'debug'), 111 | 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), 112 | 'replace_placeholders' => true, 113 | ], 114 | 115 | 'errorlog' => [ 116 | 'driver' => 'errorlog', 117 | 'level' => env('LOG_LEVEL', 'debug'), 118 | 'replace_placeholders' => true, 119 | ], 120 | 121 | 'null' => [ 122 | 'driver' => 'monolog', 123 | 'handler' => NullHandler::class, 124 | ], 125 | 126 | 'emergency' => [ 127 | 'path' => storage_path('logs/laravel.log'), 128 | ], 129 | 130 | ], 131 | 132 | ]; 133 | -------------------------------------------------------------------------------- /public/js/filament/support/async-alpine.js: -------------------------------------------------------------------------------- 1 | (()=>{(()=>{var d=Object.defineProperty,m=t=>d(t,"__esModule",{value:!0}),f=(t,e)=>{m(t);for(var i in e)d(t,i,{get:e[i],enumerable:!0})},o={};f(o,{eager:()=>g,event:()=>w,idle:()=>y,media:()=>b,visible:()=>E});var c=()=>!0,g=c,v=({component:t,argument:e})=>new Promise(i=>{if(e)window.addEventListener(e,()=>i(),{once:!0});else{let n=a=>{a.detail.id===t.id&&(window.removeEventListener("async-alpine:load",n),i())};window.addEventListener("async-alpine:load",n)}}),w=v,x=()=>new Promise(t=>{"requestIdleCallback"in window?window.requestIdleCallback(t):setTimeout(t,200)}),y=x,A=({argument:t})=>new Promise(e=>{if(!t)return console.log("Async Alpine: media strategy requires a media query. Treating as 'eager'"),e();let i=window.matchMedia(`(${t})`);i.matches?e():i.addEventListener("change",e,{once:!0})}),b=A,$=({component:t,argument:e})=>new Promise(i=>{let n=e||"0px 0px 0px 0px",a=new IntersectionObserver(r=>{r[0].isIntersecting&&(a.disconnect(),i())},{rootMargin:n});a.observe(t.el)}),E=$;function P(t){let e=q(t),i=u(e);return i.type==="method"?{type:"expression",operator:"&&",parameters:[i]}:i}function q(t){let e=/\s*([()])\s*|\s*(\|\||&&|\|)\s*|\s*((?:[^()&|]+\([^()]+\))|[^()&|]+)\s*/g,i=[],n;for(;(n=e.exec(t))!==null;){let[,a,r,s]=n;if(a!==void 0)i.push({type:"parenthesis",value:a});else if(r!==void 0)i.push({type:"operator",value:r==="|"?"&&":r});else{let p={type:"method",method:s.trim()};s.includes("(")&&(p.method=s.substring(0,s.indexOf("(")).trim(),p.argument=s.substring(s.indexOf("(")+1,s.indexOf(")"))),s.method==="immediate"&&(s.method="eager"),i.push(p)}}return i}function u(t){let e=h(t);for(;t.length>0&&(t[0].value==="&&"||t[0].value==="|"||t[0].value==="||");){let i=t.shift().value,n=h(t);e.type==="expression"&&e.operator===i?e.parameters.push(n):e={type:"expression",operator:i,parameters:[e,n]}}return e}function h(t){if(t[0].value==="("){t.shift();let e=u(t);return t[0].value===")"&&t.shift(),e}else return t.shift()}var _="__internal_",l={Alpine:null,_options:{prefix:"ax-",alpinePrefix:"x-",root:"load",inline:"load-src",defaultStrategy:"eager"},_alias:!1,_data:{},_realIndex:0,get _index(){return this._realIndex++},init(t,e={}){return this.Alpine=t,this._options={...this._options,...e},this},start(){return this._processInline(),this._setupComponents(),this._mutations(),this},data(t,e=!1){return this._data[t]={loaded:!1,download:e},this},url(t,e){!t||!e||(this._data[t]||this.data(t),this._data[t].download=()=>import(this._parseUrl(e)))},alias(t){this._alias=t},_processInline(){let t=document.querySelectorAll(`[${this._options.prefix}${this._options.inline}]`);for(let e of t)this._inlineElement(e)},_inlineElement(t){let e=t.getAttribute(`${this._options.alpinePrefix}data`),i=t.getAttribute(`${this._options.prefix}${this._options.inline}`);if(!e||!i)return;let n=this._parseName(e);this.url(n,i)},_setupComponents(){let t=document.querySelectorAll(`[${this._options.prefix}${this._options.root}]`);for(let e of t)this._setupComponent(e)},_setupComponent(t){let e=t.getAttribute(`${this._options.alpinePrefix}data`);t.setAttribute(`${this._options.alpinePrefix}ignore`,"");let i=this._parseName(e),n=t.getAttribute(`${this._options.prefix}${this._options.root}`)||this._options.defaultStrategy;this._componentStrategy({name:i,strategy:n,el:t,id:t.id||this._index})},async _componentStrategy(t){let e=P(t.strategy);await this._generateRequirements(t,e),await this._download(t.name),this._activate(t)},_generateRequirements(t,e){if(e.type==="expression"){if(e.operator==="&&")return Promise.all(e.parameters.map(i=>this._generateRequirements(t,i)));if(e.operator==="||")return Promise.any(e.parameters.map(i=>this._generateRequirements(t,i)))}return o[e.method]?o[e.method]({component:t,argument:e.argument}):!1},async _download(t){if(t.startsWith(_)||(this._handleAlias(t),!this._data[t]||this._data[t].loaded))return;let e=await this._getModule(t);this.Alpine.data(t,e),this._data[t].loaded=!0},async _getModule(t){if(!this._data[t])return;let e=await this._data[t].download(t);return typeof e=="function"?e:e[t]||e.default||Object.values(e)[0]||!1},_activate(t){this.Alpine.destroyTree(t.el),t.el.removeAttribute(`${this._options.alpinePrefix}ignore`),t.el._x_ignore=!1,this.Alpine.initTree(t.el)},_mutations(){new MutationObserver(t=>{for(let e of t)if(e.addedNodes)for(let i of e.addedNodes)i.nodeType===1&&(i.hasAttribute(`${this._options.prefix}${this._options.root}`)&&this._mutationEl(i),i.querySelectorAll(`[${this._options.prefix}${this._options.root}]`).forEach(n=>this._mutationEl(n)))}).observe(document,{attributes:!0,childList:!0,subtree:!0})},_mutationEl(t){t.hasAttribute(`${this._options.prefix}${this._options.inline}`)&&this._inlineElement(t),this._setupComponent(t)},_handleAlias(t){if(!(!this._alias||this._data[t])){if(typeof this._alias=="function"){this.data(t,this._alias);return}this.url(t,this._alias.replaceAll("[name]",t))}},_parseName(t){return(t||"").split(/[({]/g)[0]||`${_}${this._index}`},_parseUrl(t){return new RegExp("^(?:[a-z+]+:)?//","i").test(t)?t:new URL(t,document.baseURI).href}};document.addEventListener("alpine:init",()=>{window.AsyncAlpine=l,l.init(Alpine,window.AsyncAlpineOptions||{}),document.dispatchEvent(new CustomEvent("async-alpine:init")),l.start()})})();})(); 2 | -------------------------------------------------------------------------------- /config/essentials.php: -------------------------------------------------------------------------------- 1 | true, 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Automatically Eager Load Relationships 24 | |-------------------------------------------------------------------------- 25 | | 26 | | This option allows you to automatically eagerly load relationships 27 | | for your models. It reduces N+1 query issues and improves 28 | | performance without needing with() everywhere. 29 | | 30 | | Enabled by default. 31 | | 32 | | Note: This option is only available in Laravel 12.8 and above. 33 | | 34 | */ 35 | 36 | NunoMaduro\Essentials\Configurables\AutomaticallyEagerLoadRelationships::class => true, 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Fake Sleep when running tests 41 | |-------------------------------------------------------------------------- 42 | | 43 | | This option allows you to fake sleep when running tests. When 44 | | enabled, the framework will fake sleep for all tests. 45 | | 46 | | Enabled by default. 47 | | 48 | */ 49 | 50 | NunoMaduro\Essentials\Configurables\FakeSleep::class => true, 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Force HTTPS scheme 55 | |-------------------------------------------------------------------------- 56 | | 57 | | This option allows you to force the HTTPS scheme for your 58 | | application. When enabled, all URLs generated by the Laravel 59 | | will use the HTTPS scheme. 60 | | 61 | | Enabled by default. 62 | | 63 | */ 64 | 65 | NunoMaduro\Essentials\Configurables\ForceScheme::class => true, 66 | 67 | /* 68 | |-------------------------------------------------------------------------- 69 | | Per-environment overrides 70 | |-------------------------------------------------------------------------- 71 | | 72 | | Specify environments for which each configurable should be active. 73 | | 74 | */ 75 | 76 | 'environments' => [ 77 | NunoMaduro\Essentials\Configurables\ForceScheme::class => ['production'], 78 | ], 79 | 80 | /* 81 | |-------------------------------------------------------------------------- 82 | | Immutable Carbon Dates 83 | |-------------------------------------------------------------------------- 84 | | 85 | | This option allows you to make all Carbon dates immutable in 86 | | your application. When enabled, all date functions will 87 | | return CarbonImmutable instances instead of Carbon. 88 | | 89 | | Enabled by default. 90 | | 91 | */ 92 | 93 | NunoMaduro\Essentials\Configurables\ImmutableDates::class => true, 94 | 95 | /* 96 | |-------------------------------------------------------------------------- 97 | | Prevent Stray Requests when running tests 98 | |-------------------------------------------------------------------------- 99 | | 100 | | This option allows you to prevent stray requests when running 101 | | tests. When enabled, the framework will prevent requests 102 | | from being sent during tests unless faked. 103 | | 104 | | Enabled by default. 105 | | 106 | */ 107 | 108 | NunoMaduro\Essentials\Configurables\PreventStrayRequests::class => true, 109 | 110 | /* 111 | |-------------------------------------------------------------------------- 112 | | Prohibit Destructive Commands 113 | |-------------------------------------------------------------------------- 114 | | 115 | | This option allows you to prohibit destructive commands 116 | | from being run in your application. When enabled, the 117 | | framework will prevent commands that could potentially 118 | | destroy data from being run in your application. 119 | | 120 | | Enabled by default. 121 | | 122 | */ 123 | 124 | NunoMaduro\Essentials\Configurables\ProhibitDestructiveCommands::class => true, 125 | 126 | /* 127 | |-------------------------------------------------------------------------- 128 | | Set Default Password complexity 129 | |-------------------------------------------------------------------------- 130 | | 131 | | This option sets the default password complexity for your 132 | | application to be at least 12 characters long, maximum 133 | | 255 characters long, and not compromised. 134 | | 135 | | Enabled by default. 136 | | 137 | */ 138 | 139 | NunoMaduro\Essentials\Configurables\SetDefaultPassword::class => true, 140 | 141 | /* 142 | |-------------------------------------------------------------------------- 143 | | Model should be strict 144 | |-------------------------------------------------------------------------- 145 | | 146 | | This option allows you to enable strict mode for your 147 | | application. It will prevent lazy loading, silently discarding 148 | | attributes and prevents accessing missing attributes. 149 | | 150 | | Enabled by default. 151 | | 152 | */ 153 | 154 | NunoMaduro\Essentials\Configurables\ShouldBeStrict::class => true, 155 | 156 | /* 157 | |-------------------------------------------------------------------------- 158 | | Unguard models 159 | |-------------------------------------------------------------------------- 160 | | 161 | | This option allows you to enable unguard mode for your 162 | | models. When enabled, the framework will unguard 163 | | all models, allowing you to mass assign any attributes. 164 | | 165 | | Disabled by default. 166 | | 167 | */ 168 | 169 | NunoMaduro\Essentials\Configurables\Unguard::class => false, 170 | 171 | ]; 172 | -------------------------------------------------------------------------------- /public/js/filament/notifications/notifications.js: -------------------------------------------------------------------------------- 1 | (()=>{var O=Object.create;var N=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var H=Object.getPrototypeOf,W=Object.prototype.hasOwnProperty;var d=(i,t)=>()=>(t||i((t={exports:{}}).exports,t),t.exports);var j=(i,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Y(t))!W.call(i,n)&&n!==e&&N(i,n,{get:()=>t[n],enumerable:!(s=V(t,n))||s.enumerable});return i};var J=(i,t,e)=>(e=i!=null?O(H(i)):{},j(t||!i||!i.__esModule?N(e,"default",{value:i,enumerable:!0}):e,i));var S=d((ut,_)=>{var v,g=typeof global<"u"&&(global.crypto||global.msCrypto);g&&g.getRandomValues&&(y=new Uint8Array(16),v=function(){return g.getRandomValues(y),y});var y;v||(T=new Array(16),v=function(){for(var i=0,t;i<16;i++)(i&3)===0&&(t=Math.random()*4294967296),T[i]=t>>>((i&3)<<3)&255;return T});var T;_.exports=v});var C=d((ct,U)=>{var P=[];for(f=0;f<256;++f)P[f]=(f+256).toString(16).substr(1);var f;function K(i,t){var e=t||0,s=P;return s[i[e++]]+s[i[e++]]+s[i[e++]]+s[i[e++]]+"-"+s[i[e++]]+s[i[e++]]+"-"+s[i[e++]]+s[i[e++]]+"-"+s[i[e++]]+s[i[e++]]+"-"+s[i[e++]]+s[i[e++]]+s[i[e++]]+s[i[e++]]+s[i[e++]]+s[i[e++]]}U.exports=K});var R=d((lt,F)=>{var Q=S(),X=C(),a=Q(),Z=[a[0]|1,a[1],a[2],a[3],a[4],a[5]],b=(a[6]<<8|a[7])&16383,D=0,A=0;function tt(i,t,e){var s=t&&e||0,n=t||[];i=i||{};var r=i.clockseq!==void 0?i.clockseq:b,o=i.msecs!==void 0?i.msecs:new Date().getTime(),h=i.nsecs!==void 0?i.nsecs:A+1,l=o-D+(h-A)/1e4;if(l<0&&i.clockseq===void 0&&(r=r+1&16383),(l<0||o>D)&&i.nsecs===void 0&&(h=0),h>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");D=o,A=h,b=r,o+=122192928e5;var c=((o&268435455)*1e4+h)%4294967296;n[s++]=c>>>24&255,n[s++]=c>>>16&255,n[s++]=c>>>8&255,n[s++]=c&255;var u=o/4294967296*1e4&268435455;n[s++]=u>>>8&255,n[s++]=u&255,n[s++]=u>>>24&15|16,n[s++]=u>>>16&255,n[s++]=r>>>8|128,n[s++]=r&255;for(var $=i.node||Z,m=0;m<6;++m)n[s+m]=$[m];return t||X(n)}F.exports=tt});var G=d((dt,B)=>{var it=S(),et=C();function st(i,t,e){var s=t&&e||0;typeof i=="string"&&(t=i=="binary"?new Array(16):null,i=null),i=i||{};var n=i.random||(i.rng||it)();if(n[6]=n[6]&15|64,n[8]=n[8]&63|128,t)for(var r=0;r<16;++r)t[s+r]=n[r];return t||et(n)}B.exports=st});var M=d((ft,L)=>{var nt=R(),I=G(),E=I;E.v1=nt;E.v4=I;L.exports=E});function k(i,t=()=>{}){let e=!1;return function(){e?t.apply(this,arguments):(e=!0,i.apply(this,arguments))}}var q=i=>{i.data("notificationComponent",({notification:t})=>({isShown:!1,computedStyle:null,transitionDuration:null,transitionEasing:null,init(){this.computedStyle=window.getComputedStyle(this.$el),this.transitionDuration=parseFloat(this.computedStyle.transitionDuration)*1e3,this.transitionEasing=this.computedStyle.transitionTimingFunction,this.configureTransitions(),this.configureAnimations(),t.duration&&t.duration!=="persistent"&&setTimeout(()=>{if(!this.$el.matches(":hover")){this.close();return}this.$el.addEventListener("mouseleave",()=>this.close())},t.duration),this.isShown=!0},configureTransitions(){let e=this.computedStyle.display,s=()=>{i.mutateDom(()=>{this.$el.style.setProperty("display",e),this.$el.style.setProperty("visibility","visible")}),this.$el._x_isShown=!0},n=()=>{i.mutateDom(()=>{this.$el._x_isShown?this.$el.style.setProperty("visibility","hidden"):this.$el.style.setProperty("display","none")})},r=k(o=>o?s():n(),o=>{this.$el._x_toggleAndCascadeWithTransitions(this.$el,o,s,n)});i.effect(()=>r(this.isShown))},configureAnimations(){let e;Livewire.hook("commit",({component:s,commit:n,succeed:r,fail:o,respond:h})=>{s.snapshot.data.isFilamentNotificationsComponent&&requestAnimationFrame(()=>{let l=()=>this.$el.getBoundingClientRect().top,c=l();h(()=>{e=()=>{this.isShown&&this.$el.animate([{transform:`translateY(${c-l()}px)`},{transform:"translateY(0px)"}],{duration:this.transitionDuration,easing:this.transitionEasing})},this.$el.getAnimations().forEach(u=>u.finish())}),r(({snapshot:u,effect:$})=>{e()})})})},close(){this.isShown=!1,setTimeout(()=>window.dispatchEvent(new CustomEvent("notificationClosed",{detail:{id:t.id}})),this.transitionDuration)},markAsRead(){window.dispatchEvent(new CustomEvent("markedNotificationAsRead",{detail:{id:t.id}}))},markAsUnread(){window.dispatchEvent(new CustomEvent("markedNotificationAsUnread",{detail:{id:t.id}}))}}))};var z=J(M(),1),p=class{constructor(){return this.id((0,z.v4)()),this}id(t){return this.id=t,this}title(t){return this.title=t,this}body(t){return this.body=t,this}actions(t){return this.actions=t,this}status(t){return this.status=t,this}color(t){return this.color=t,this}icon(t){return this.icon=t,this}iconColor(t){return this.iconColor=t,this}duration(t){return this.duration=t,this}seconds(t){return this.duration(t*1e3),this}persistent(){return this.duration("persistent"),this}danger(){return this.status("danger"),this}info(){return this.status("info"),this}success(){return this.status("success"),this}warning(){return this.status("warning"),this}view(t){return this.view=t,this}viewData(t){return this.viewData=t,this}send(){return window.dispatchEvent(new CustomEvent("notificationSent",{detail:{notification:this}})),this}},w=class{constructor(t){return this.name(t),this}name(t){return this.name=t,this}color(t){return this.color=t,this}dispatch(t,e){return this.event(t),this.eventData(e),this}dispatchSelf(t,e){return this.dispatch(t,e),this.dispatchDirection="self",this}dispatchTo(t,e,s){return this.dispatch(e,s),this.dispatchDirection="to",this.dispatchToComponent=t,this}emit(t,e){return this.dispatch(t,e),this}emitSelf(t,e){return this.dispatchSelf(t,e),this}emitTo(t,e,s){return this.dispatchTo(t,e,s),this}dispatchDirection(t){return this.dispatchDirection=t,this}dispatchToComponent(t){return this.dispatchToComponent=t,this}event(t){return this.event=t,this}eventData(t){return this.eventData=t,this}extraAttributes(t){return this.extraAttributes=t,this}icon(t){return this.icon=t,this}iconPosition(t){return this.iconPosition=t,this}outlined(t=!0){return this.isOutlined=t,this}disabled(t=!0){return this.isDisabled=t,this}label(t){return this.label=t,this}close(t=!0){return this.shouldClose=t,this}openUrlInNewTab(t=!0){return this.shouldOpenUrlInNewTab=t,this}size(t){return this.size=t,this}url(t){return this.url=t,this}view(t){return this.view=t,this}button(){return this.view("filament::components.button.index"),this}grouped(){return this.view("filament::components.dropdown.list.item"),this}iconButton(){return this.view("filament::components.icon-button"),this}link(){return this.view("filament::components.link"),this}},x=class{constructor(t){return this.actions(t),this}actions(t){return this.actions=t.map(e=>e.grouped()),this}color(t){return this.color=t,this}icon(t){return this.icon=t,this}iconPosition(t){return this.iconPosition=t,this}label(t){return this.label=t,this}tooltip(t){return this.tooltip=t,this}};window.FilamentNotificationAction=w;window.FilamentNotificationActionGroup=x;window.FilamentNotification=p;document.addEventListener("alpine:init",()=>{window.Alpine.plugin(q)});})(); 2 | -------------------------------------------------------------------------------- /database/migrations/2025_07_24_073400_create_permission_tables.php: -------------------------------------------------------------------------------- 1 | engine('InnoDB'); 25 | $blueprint->bigIncrements('id'); // permission id 26 | $blueprint->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) 27 | $blueprint->string('guard_name'); // For MyISAM use string('guard_name', 25); 28 | $blueprint->timestamps(); 29 | 30 | $blueprint->unique(['name', 'guard_name']); 31 | }); 32 | 33 | Schema::create($tableNames['roles'], static function (Blueprint $blueprint) use ($teams, $columnNames): void { 34 | // $table->engine('InnoDB'); 35 | $blueprint->bigIncrements('id'); // role id 36 | if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing 37 | $blueprint->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); 38 | $blueprint->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); 39 | } 40 | 41 | $blueprint->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) 42 | $blueprint->string('guard_name'); // For MyISAM use string('guard_name', 25); 43 | $blueprint->timestamps(); 44 | if ($teams || config('permission.testing')) { 45 | $blueprint->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); 46 | } else { 47 | $blueprint->unique(['name', 'guard_name']); 48 | } 49 | }); 50 | 51 | Schema::create($tableNames['model_has_permissions'], static function (Blueprint $blueprint) use ($tableNames, $columnNames, $pivotPermission, $teams): void { 52 | $blueprint->unsignedBigInteger($pivotPermission); 53 | 54 | $blueprint->string('model_type'); 55 | $blueprint->unsignedBigInteger($columnNames['model_morph_key']); 56 | $blueprint->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); 57 | 58 | $blueprint->foreign($pivotPermission) 59 | ->references('id') // permission id 60 | ->on($tableNames['permissions']) 61 | ->onDelete('cascade'); 62 | if ($teams) { 63 | $blueprint->unsignedBigInteger($columnNames['team_foreign_key']); 64 | $blueprint->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); 65 | 66 | $blueprint->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], 67 | 'model_has_permissions_permission_model_type_primary'); 68 | } else { 69 | $blueprint->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], 70 | 'model_has_permissions_permission_model_type_primary'); 71 | } 72 | 73 | }); 74 | 75 | Schema::create($tableNames['model_has_roles'], static function (Blueprint $blueprint) use ($tableNames, $columnNames, $pivotRole, $teams): void { 76 | $blueprint->unsignedBigInteger($pivotRole); 77 | 78 | $blueprint->string('model_type'); 79 | $blueprint->unsignedBigInteger($columnNames['model_morph_key']); 80 | $blueprint->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); 81 | 82 | $blueprint->foreign($pivotRole) 83 | ->references('id') // role id 84 | ->on($tableNames['roles']) 85 | ->onDelete('cascade'); 86 | if ($teams) { 87 | $blueprint->unsignedBigInteger($columnNames['team_foreign_key']); 88 | $blueprint->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); 89 | 90 | $blueprint->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], 91 | 'model_has_roles_role_model_type_primary'); 92 | } else { 93 | $blueprint->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], 94 | 'model_has_roles_role_model_type_primary'); 95 | } 96 | }); 97 | 98 | Schema::create($tableNames['role_has_permissions'], static function (Blueprint $blueprint) use ($tableNames, $pivotRole, $pivotPermission): void { 99 | $blueprint->unsignedBigInteger($pivotPermission); 100 | $blueprint->unsignedBigInteger($pivotRole); 101 | 102 | $blueprint->foreign($pivotPermission) 103 | ->references('id') // permission id 104 | ->on($tableNames['permissions']) 105 | ->onDelete('cascade'); 106 | 107 | $blueprint->foreign($pivotRole) 108 | ->references('id') // role id 109 | ->on($tableNames['roles']) 110 | ->onDelete('cascade'); 111 | 112 | $blueprint->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); 113 | }); 114 | 115 | app(\Illuminate\Contracts\Cache\Factory::class) 116 | ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) 117 | ->forget(config('permission.cache.key')); 118 | } 119 | 120 | /** 121 | * Reverse the migrations. 122 | */ 123 | public function down(): void 124 | { 125 | $tableNames = config('permission.table_names'); 126 | 127 | throw_if(empty($tableNames), new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.')); 128 | 129 | Schema::drop($tableNames['role_has_permissions']); 130 | Schema::drop($tableNames['model_has_roles']); 131 | Schema::drop($tableNames['model_has_permissions']); 132 | Schema::drop($tableNames['roles']); 133 | Schema::drop($tableNames['permissions']); 134 | } 135 | }; 136 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | env('DB_CONNECTION', 'sqlite'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Database Connections 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Below are all of the database connections defined for your application. 27 | | An example configuration is provided for each database system which 28 | | is supported by Laravel. You're free to add / remove connections. 29 | | 30 | */ 31 | 32 | 'connections' => [ 33 | 34 | 'sqlite' => [ 35 | 'driver' => 'sqlite', 36 | 'url' => env('DB_URL'), 37 | 'database' => env('DB_DATABASE', database_path('database.sqlite')), 38 | 'prefix' => '', 39 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 40 | 'busy_timeout' => null, 41 | 'journal_mode' => null, 42 | 'synchronous' => null, 43 | 'transaction_mode' => 'DEFERRED', 44 | ], 45 | 46 | 'mysql' => [ 47 | 'driver' => 'mysql', 48 | 'url' => env('DB_URL'), 49 | 'host' => env('DB_HOST', '127.0.0.1'), 50 | 'port' => env('DB_PORT', '3306'), 51 | 'database' => env('DB_DATABASE', 'laravel'), 52 | 'username' => env('DB_USERNAME', 'root'), 53 | 'password' => env('DB_PASSWORD', ''), 54 | 'unix_socket' => env('DB_SOCKET', ''), 55 | 'charset' => env('DB_CHARSET', 'utf8mb4'), 56 | 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 57 | 'prefix' => '', 58 | 'prefix_indexes' => true, 59 | 'strict' => true, 60 | 'engine' => null, 61 | 'options' => extension_loaded('pdo_mysql') ? array_filter([ 62 | (PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), 63 | ]) : [], 64 | ], 65 | 66 | 'mariadb' => [ 67 | 'driver' => 'mariadb', 68 | 'url' => env('DB_URL'), 69 | 'host' => env('DB_HOST', '127.0.0.1'), 70 | 'port' => env('DB_PORT', '3306'), 71 | 'database' => env('DB_DATABASE', 'laravel'), 72 | 'username' => env('DB_USERNAME', 'root'), 73 | 'password' => env('DB_PASSWORD', ''), 74 | 'unix_socket' => env('DB_SOCKET', ''), 75 | 'charset' => env('DB_CHARSET', 'utf8mb4'), 76 | 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 77 | 'prefix' => '', 78 | 'prefix_indexes' => true, 79 | 'strict' => true, 80 | 'engine' => null, 81 | 'options' => extension_loaded('pdo_mysql') ? array_filter([ 82 | (PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), 83 | ]) : [], 84 | ], 85 | 86 | 'pgsql' => [ 87 | 'driver' => 'pgsql', 88 | 'url' => env('DB_URL'), 89 | 'host' => env('DB_HOST', '127.0.0.1'), 90 | 'port' => env('DB_PORT', '5432'), 91 | 'database' => env('DB_DATABASE', 'laravel'), 92 | 'username' => env('DB_USERNAME', 'root'), 93 | 'password' => env('DB_PASSWORD', ''), 94 | 'charset' => env('DB_CHARSET', 'utf8'), 95 | 'prefix' => '', 96 | 'prefix_indexes' => true, 97 | 'search_path' => 'public', 98 | 'sslmode' => 'prefer', 99 | ], 100 | 101 | 'sqlsrv' => [ 102 | 'driver' => 'sqlsrv', 103 | 'url' => env('DB_URL'), 104 | 'host' => env('DB_HOST', 'localhost'), 105 | 'port' => env('DB_PORT', '1433'), 106 | 'database' => env('DB_DATABASE', 'laravel'), 107 | 'username' => env('DB_USERNAME', 'root'), 108 | 'password' => env('DB_PASSWORD', ''), 109 | 'charset' => env('DB_CHARSET', 'utf8'), 110 | 'prefix' => '', 111 | 'prefix_indexes' => true, 112 | // 'encrypt' => env('DB_ENCRYPT', 'yes'), 113 | // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), 114 | ], 115 | 116 | ], 117 | 118 | /* 119 | |-------------------------------------------------------------------------- 120 | | Migration Repository Table 121 | |-------------------------------------------------------------------------- 122 | | 123 | | This table keeps track of all the migrations that have already run for 124 | | your application. Using this information, we can determine which of 125 | | the migrations on disk haven't actually been run on the database. 126 | | 127 | */ 128 | 129 | 'migrations' => [ 130 | 'table' => 'migrations', 131 | 'update_date_on_publish' => true, 132 | ], 133 | 134 | /* 135 | |-------------------------------------------------------------------------- 136 | | Redis Databases 137 | |-------------------------------------------------------------------------- 138 | | 139 | | Redis is an open source, fast, and advanced key-value store that also 140 | | provides a richer body of commands than a typical key-value system 141 | | such as Memcached. You may define your connection settings here. 142 | | 143 | */ 144 | 145 | 'redis' => [ 146 | 147 | 'client' => env('REDIS_CLIENT', 'phpredis'), 148 | 149 | 'options' => [ 150 | 'cluster' => env('REDIS_CLUSTER', 'redis'), 151 | 'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'), 152 | 'persistent' => env('REDIS_PERSISTENT', false), 153 | ], 154 | 155 | 'default' => [ 156 | 'url' => env('REDIS_URL'), 157 | 'host' => env('REDIS_HOST', '127.0.0.1'), 158 | 'username' => env('REDIS_USERNAME'), 159 | 'password' => env('REDIS_PASSWORD'), 160 | 'port' => env('REDIS_PORT', '6379'), 161 | 'database' => env('REDIS_DB', '0'), 162 | 'max_retries' => env('REDIS_MAX_RETRIES', 3), 163 | 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), 164 | 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), 165 | 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), 166 | ], 167 | 168 | 'cache' => [ 169 | 'url' => env('REDIS_URL'), 170 | 'host' => env('REDIS_HOST', '127.0.0.1'), 171 | 'username' => env('REDIS_USERNAME'), 172 | 'password' => env('REDIS_PASSWORD'), 173 | 'port' => env('REDIS_PORT', '6379'), 174 | 'database' => env('REDIS_CACHE_DB', '1'), 175 | 'max_retries' => env('REDIS_MAX_RETRIES', 3), 176 | 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), 177 | 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), 178 | 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), 179 | ], 180 | 181 | ], 182 | 183 | ]; 184 | -------------------------------------------------------------------------------- /config/permission.php: -------------------------------------------------------------------------------- 1 | [ 6 | 7 | /* 8 | * When using the "HasPermissions" trait from this package, we need to know which 9 | * Eloquent model should be used to retrieve your permissions. Of course, it 10 | * is often just the "Permission" model but you may use whatever you like. 11 | * 12 | * The model you want to use as a Permission model needs to implement the 13 | * `Spatie\Permission\Contracts\Permission` contract. 14 | */ 15 | 16 | 'permission' => Spatie\Permission\Models\Permission::class, 17 | 18 | /* 19 | * When using the "HasRoles" trait from this package, we need to know which 20 | * Eloquent model should be used to retrieve your roles. Of course, it 21 | * is often just the "Role" model but you may use whatever you like. 22 | * 23 | * The model you want to use as a Role model needs to implement the 24 | * `Spatie\Permission\Contracts\Role` contract. 25 | */ 26 | 27 | 'role' => Spatie\Permission\Models\Role::class, 28 | 29 | ], 30 | 31 | 'table_names' => [ 32 | 33 | /* 34 | * When using the "HasRoles" trait from this package, we need to know which 35 | * table should be used to retrieve your roles. We have chosen a basic 36 | * default value but you may easily change it to any table you like. 37 | */ 38 | 39 | 'roles' => 'roles', 40 | 41 | /* 42 | * When using the "HasPermissions" trait from this package, we need to know which 43 | * table should be used to retrieve your permissions. We have chosen a basic 44 | * default value but you may easily change it to any table you like. 45 | */ 46 | 47 | 'permissions' => 'permissions', 48 | 49 | /* 50 | * When using the "HasPermissions" trait from this package, we need to know which 51 | * table should be used to retrieve your models permissions. We have chosen a 52 | * basic default value but you may easily change it to any table you like. 53 | */ 54 | 55 | 'model_has_permissions' => 'model_has_permissions', 56 | 57 | /* 58 | * When using the "HasRoles" trait from this package, we need to know which 59 | * table should be used to retrieve your models roles. We have chosen a 60 | * basic default value but you may easily change it to any table you like. 61 | */ 62 | 63 | 'model_has_roles' => 'model_has_roles', 64 | 65 | /* 66 | * When using the "HasRoles" trait from this package, we need to know which 67 | * table should be used to retrieve your roles permissions. We have chosen a 68 | * basic default value but you may easily change it to any table you like. 69 | */ 70 | 71 | 'role_has_permissions' => 'role_has_permissions', 72 | ], 73 | 74 | 'column_names' => [ 75 | /* 76 | * Change this if you want to name the related pivots other than defaults 77 | */ 78 | 'role_pivot_key' => null, // default 'role_id', 79 | 'permission_pivot_key' => null, // default 'permission_id', 80 | 81 | /* 82 | * Change this if you want to name the related model primary key other than 83 | * `model_id`. 84 | * 85 | * For example, this would be nice if your primary keys are all UUIDs. In 86 | * that case, name this `model_uuid`. 87 | */ 88 | 89 | 'model_morph_key' => 'model_id', 90 | 91 | /* 92 | * Change this if you want to use the teams feature and your related model's 93 | * foreign key is other than `team_id`. 94 | */ 95 | 96 | 'team_foreign_key' => 'team_id', 97 | ], 98 | 99 | /* 100 | * When set to true, the method for checking permissions will be registered on the gate. 101 | * Set this to false if you want to implement custom logic for checking permissions. 102 | */ 103 | 104 | 'register_permission_check_method' => true, 105 | 106 | /* 107 | * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered 108 | * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated 109 | * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. 110 | */ 111 | 'register_octane_reset_listener' => false, 112 | 113 | /* 114 | * Events will fire when a role or permission is assigned/unassigned: 115 | * \Spatie\Permission\Events\RoleAttached 116 | * \Spatie\Permission\Events\RoleDetached 117 | * \Spatie\Permission\Events\PermissionAttached 118 | * \Spatie\Permission\Events\PermissionDetached 119 | * 120 | * To enable, set to true, and then create listeners to watch these events. 121 | */ 122 | 'events_enabled' => false, 123 | 124 | /* 125 | * Teams Feature. 126 | * When set to true the package implements teams using the 'team_foreign_key'. 127 | * If you want the migrations to register the 'team_foreign_key', you must 128 | * set this to true before doing the migration. 129 | * If you already did the migration then you must make a new migration to also 130 | * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions' 131 | * (view the latest version of this package's migration file) 132 | */ 133 | 134 | 'teams' => false, 135 | 136 | /* 137 | * The class to use to resolve the permissions team id 138 | */ 139 | 'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class, 140 | 141 | /* 142 | * Passport Client Credentials Grant 143 | * When set to true the package will use Passports Client to check permissions 144 | */ 145 | 146 | 'use_passport_client_credentials' => false, 147 | 148 | /* 149 | * When set to true, the required permission names are added to exception messages. 150 | * This could be considered an information leak in some contexts, so the default 151 | * setting is false here for optimum safety. 152 | */ 153 | 154 | 'display_permission_in_exception' => false, 155 | 156 | /* 157 | * When set to true, the required role names are added to exception messages. 158 | * This could be considered an information leak in some contexts, so the default 159 | * setting is false here for optimum safety. 160 | */ 161 | 162 | 'display_role_in_exception' => false, 163 | 164 | /* 165 | * By default wildcard permission lookups are disabled. 166 | * See documentation to understand supported syntax. 167 | */ 168 | 169 | 'enable_wildcard_permission' => false, 170 | 171 | /* 172 | * The class to use for interpreting wildcard permissions. 173 | * If you need to modify delimiters, override the class and specify its name here. 174 | */ 175 | // 'wildcard_permission' => Spatie\Permission\WildcardPermission::class, 176 | 177 | /* Cache-specific settings */ 178 | 179 | 'cache' => [ 180 | 181 | /* 182 | * By default all permissions are cached for 24 hours to speed up performance. 183 | * When permissions or roles are updated the cache is flushed automatically. 184 | */ 185 | 186 | 'expiration_time' => \DateInterval::createFromDateString('24 hours'), 187 | 188 | /* 189 | * The cache key used to store all permissions. 190 | */ 191 | 192 | 'key' => 'spatie.permission.cache', 193 | 194 | /* 195 | * You may optionally indicate a specific cache driver to use for permission and 196 | * role caching using any of the `store` drivers listed in the cache.php config 197 | * file. Using 'default' here means to use the `default` set in cache.php. 198 | */ 199 | 200 | 'store' => 'default', 201 | ], 202 | ]; 203 | -------------------------------------------------------------------------------- /config/session.php: -------------------------------------------------------------------------------- 1 | env('SESSION_DRIVER', 'database'), 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | Session Lifetime 26 | |-------------------------------------------------------------------------- 27 | | 28 | | Here you may specify the number of minutes that you wish the session 29 | | to be allowed to remain idle before it expires. If you want them 30 | | to expire immediately when the browser is closed then you may 31 | | indicate that via the expire_on_close configuration option. 32 | | 33 | */ 34 | 35 | 'lifetime' => (int) env('SESSION_LIFETIME', 120), 36 | 37 | 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | Session Encryption 42 | |-------------------------------------------------------------------------- 43 | | 44 | | This option allows you to easily specify that all of your session data 45 | | should be encrypted before it's stored. All encryption is performed 46 | | automatically by Laravel and you may use the session like normal. 47 | | 48 | */ 49 | 50 | 'encrypt' => env('SESSION_ENCRYPT', false), 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Session File Location 55 | |-------------------------------------------------------------------------- 56 | | 57 | | When utilizing the "file" session driver, the session files are placed 58 | | on disk. The default storage location is defined here; however, you 59 | | are free to provide another location where they should be stored. 60 | | 61 | */ 62 | 63 | 'files' => storage_path('framework/sessions'), 64 | 65 | /* 66 | |-------------------------------------------------------------------------- 67 | | Session Database Connection 68 | |-------------------------------------------------------------------------- 69 | | 70 | | When using the "database" or "redis" session drivers, you may specify a 71 | | connection that should be used to manage these sessions. This should 72 | | correspond to a connection in your database configuration options. 73 | | 74 | */ 75 | 76 | 'connection' => env('SESSION_CONNECTION'), 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Session Database Table 81 | |-------------------------------------------------------------------------- 82 | | 83 | | When using the "database" session driver, you may specify the table to 84 | | be used to store sessions. Of course, a sensible default is defined 85 | | for you; however, you're welcome to change this to another table. 86 | | 87 | */ 88 | 89 | 'table' => env('SESSION_TABLE', 'sessions'), 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | Session Cache Store 94 | |-------------------------------------------------------------------------- 95 | | 96 | | When using one of the framework's cache driven session backends, you may 97 | | define the cache store which should be used to store the session data 98 | | between requests. This must match one of your defined cache stores. 99 | | 100 | | Affects: "dynamodb", "memcached", "redis" 101 | | 102 | */ 103 | 104 | 'store' => env('SESSION_STORE'), 105 | 106 | /* 107 | |-------------------------------------------------------------------------- 108 | | Session Sweeping Lottery 109 | |-------------------------------------------------------------------------- 110 | | 111 | | Some session drivers must manually sweep their storage location to get 112 | | rid of old sessions from storage. Here are the chances that it will 113 | | happen on a given request. By default, the odds are 2 out of 100. 114 | | 115 | */ 116 | 117 | 'lottery' => [2, 100], 118 | 119 | /* 120 | |-------------------------------------------------------------------------- 121 | | Session Cookie Name 122 | |-------------------------------------------------------------------------- 123 | | 124 | | Here you may change the name of the session cookie that is created by 125 | | the framework. Typically, you should not need to change this value 126 | | since doing so does not grant a meaningful security improvement. 127 | | 128 | */ 129 | 130 | 'cookie' => env( 131 | 'SESSION_COOKIE', 132 | Str::slug((string) env('APP_NAME', 'laravel')).'-session' 133 | ), 134 | 135 | /* 136 | |-------------------------------------------------------------------------- 137 | | Session Cookie Path 138 | |-------------------------------------------------------------------------- 139 | | 140 | | The session cookie path determines the path for which the cookie will 141 | | be regarded as available. Typically, this will be the root path of 142 | | your application, but you're free to change this when necessary. 143 | | 144 | */ 145 | 146 | 'path' => env('SESSION_PATH', '/'), 147 | 148 | /* 149 | |-------------------------------------------------------------------------- 150 | | Session Cookie Domain 151 | |-------------------------------------------------------------------------- 152 | | 153 | | This value determines the domain and subdomains the session cookie is 154 | | available to. By default, the cookie will be available to the root 155 | | domain without subdomains. Typically, this shouldn't be changed. 156 | | 157 | */ 158 | 159 | 'domain' => env('SESSION_DOMAIN'), 160 | 161 | /* 162 | |-------------------------------------------------------------------------- 163 | | HTTPS Only Cookies 164 | |-------------------------------------------------------------------------- 165 | | 166 | | By setting this option to true, session cookies will only be sent back 167 | | to the server if the browser has a HTTPS connection. This will keep 168 | | the cookie from being sent to you when it can't be done securely. 169 | | 170 | */ 171 | 172 | 'secure' => env('SESSION_SECURE_COOKIE'), 173 | 174 | /* 175 | |-------------------------------------------------------------------------- 176 | | HTTP Access Only 177 | |-------------------------------------------------------------------------- 178 | | 179 | | Setting this value to true will prevent JavaScript from accessing the 180 | | value of the cookie and the cookie will only be accessible through 181 | | the HTTP protocol. It's unlikely you should disable this option. 182 | | 183 | */ 184 | 185 | 'http_only' => env('SESSION_HTTP_ONLY', true), 186 | 187 | /* 188 | |-------------------------------------------------------------------------- 189 | | Same-Site Cookies 190 | |-------------------------------------------------------------------------- 191 | | 192 | | This option determines how your cookies behave when cross-site requests 193 | | take place, and can be used to mitigate CSRF attacks. By default, we 194 | | will set this value to "lax" to permit secure cross-site requests. 195 | | 196 | | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value 197 | | 198 | | Supported: "lax", "strict", "none", null 199 | | 200 | */ 201 | 202 | 'same_site' => env('SESSION_SAME_SITE', 'lax'), 203 | 204 | /* 205 | |-------------------------------------------------------------------------- 206 | | Partitioned Cookies 207 | |-------------------------------------------------------------------------- 208 | | 209 | | Setting this value to true will tie the cookie to the top-level site for 210 | | a cross-site context. Partitioned cookies are accepted by the browser 211 | | when flagged "secure" and the Same-Site attribute is set to "none". 212 | | 213 | */ 214 | 215 | 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), 216 | 217 | ]; 218 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/color-picker.js: -------------------------------------------------------------------------------- 1 | var c=(e,t=0,r=1)=>e>r?r:eMath.round(r*e)/r;var at={grad:360/400,turn:360,rad:360/(Math.PI*2)},F=e=>G(v(e)),v=e=>(e[0]==="#"&&(e=e.substring(1)),e.length<6?{r:parseInt(e[0]+e[0],16),g:parseInt(e[1]+e[1],16),b:parseInt(e[2]+e[2],16),a:e.length===4?a(parseInt(e[3]+e[3],16)/255,2):1}:{r:parseInt(e.substring(0,2),16),g:parseInt(e.substring(2,4),16),b:parseInt(e.substring(4,6),16),a:e.length===8?a(parseInt(e.substring(6,8),16)/255,2):1}),nt=(e,t="deg")=>Number(e)*(at[t]||1),it=e=>{let r=/hsla?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i.exec(e);return r?lt({h:nt(r[1],r[2]),s:Number(r[3]),l:Number(r[4]),a:r[5]===void 0?1:Number(r[5])/(r[6]?100:1)}):{h:0,s:0,v:0,a:1}},J=it,lt=({h:e,s:t,l:r,a:o})=>(t*=(r<50?r:100-r)/100,{h:e,s:t>0?2*t/(r+t)*100:0,v:r+t,a:o}),X=e=>ct(A(e)),Y=({h:e,s:t,v:r,a:o})=>{let s=(200-t)*r/100;return{h:a(e),s:a(s>0&&s<200?t*r/100/(s<=100?s:200-s)*100:0),l:a(s/2),a:a(o,2)}};var d=e=>{let{h:t,s:r,l:o}=Y(e);return`hsl(${t}, ${r}%, ${o}%)`},$=e=>{let{h:t,s:r,l:o,a:s}=Y(e);return`hsla(${t}, ${r}%, ${o}%, ${s})`},A=({h:e,s:t,v:r,a:o})=>{e=e/360*6,t=t/100,r=r/100;let s=Math.floor(e),n=r*(1-t),i=r*(1-(e-s)*t),l=r*(1-(1-e+s)*t),q=s%6;return{r:a([r,i,n,n,l,r][q]*255),g:a([l,r,r,i,n,n][q]*255),b:a([n,n,l,r,r,i][q]*255),a:a(o,2)}},B=e=>{let{r:t,g:r,b:o}=A(e);return`rgb(${t}, ${r}, ${o})`},D=e=>{let{r:t,g:r,b:o,a:s}=A(e);return`rgba(${t}, ${r}, ${o}, ${s})`};var I=e=>{let r=/rgba?\(?\s*(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i.exec(e);return r?G({r:Number(r[1])/(r[2]?100/255:1),g:Number(r[3])/(r[4]?100/255:1),b:Number(r[5])/(r[6]?100/255:1),a:r[7]===void 0?1:Number(r[7])/(r[8]?100:1)}):{h:0,s:0,v:0,a:1}},U=I,b=e=>{let t=e.toString(16);return t.length<2?"0"+t:t},ct=({r:e,g:t,b:r,a:o})=>{let s=o<1?b(a(o*255)):"";return"#"+b(e)+b(t)+b(r)+s},G=({r:e,g:t,b:r,a:o})=>{let s=Math.max(e,t,r),n=s-Math.min(e,t,r),i=n?s===e?(t-r)/n:s===t?2+(r-e)/n:4+(e-t)/n:0;return{h:a(60*(i<0?i+6:i)),s:a(s?n/s*100:0),v:a(s/255*100),a:o}};var L=(e,t)=>{if(e===t)return!0;for(let r in e)if(e[r]!==t[r])return!1;return!0},h=(e,t)=>e.replace(/\s/g,"")===t.replace(/\s/g,""),K=(e,t)=>e.toLowerCase()===t.toLowerCase()?!0:L(v(e),v(t));var Q={},H=e=>{let t=Q[e];return t||(t=document.createElement("template"),t.innerHTML=e,Q[e]=t),t},f=(e,t,r)=>{e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:r}))};var m=!1,O=e=>"touches"in e,pt=e=>m&&!O(e)?!1:(m||(m=O(e)),!0),W=(e,t)=>{let r=O(t)?t.touches[0]:t,o=e.el.getBoundingClientRect();f(e.el,"move",e.getMove({x:c((r.pageX-(o.left+window.pageXOffset))/o.width),y:c((r.pageY-(o.top+window.pageYOffset))/o.height)}))},ut=(e,t)=>{let r=t.keyCode;r>40||e.xy&&r<37||r<33||(t.preventDefault(),f(e.el,"move",e.getMove({x:r===39?.01:r===37?-.01:r===34?.05:r===33?-.05:r===35?1:r===36?-1:0,y:r===40?.01:r===38?-.01:0},!0)))},u=class{constructor(t,r,o,s){let n=H(`
`);t.appendChild(n.content.cloneNode(!0));let i=t.querySelector(`[part=${r}]`);i.addEventListener("mousedown",this),i.addEventListener("touchstart",this),i.addEventListener("keydown",this),this.el=i,this.xy=s,this.nodes=[i.firstChild,i]}set dragging(t){let r=t?document.addEventListener:document.removeEventListener;r(m?"touchmove":"mousemove",this),r(m?"touchend":"mouseup",this)}handleEvent(t){switch(t.type){case"mousedown":case"touchstart":if(t.preventDefault(),!pt(t)||!m&&t.button!=0)return;this.el.focus(),W(this,t),this.dragging=!0;break;case"mousemove":case"touchmove":t.preventDefault(),W(this,t);break;case"mouseup":case"touchend":this.dragging=!1;break;case"keydown":ut(this,t);break}}style(t){t.forEach((r,o)=>{for(let s in r)this.nodes[o].style.setProperty(s,r[s])})}};var S=class extends u{constructor(t){super(t,"hue",'aria-label="Hue" aria-valuemin="0" aria-valuemax="360"',!1)}update({h:t}){this.h=t,this.style([{left:`${t/360*100}%`,color:d({h:t,s:100,v:100,a:1})}]),this.el.setAttribute("aria-valuenow",`${a(t)}`)}getMove(t,r){return{h:r?c(this.h+t.x*360,0,360):360*t.x}}};var T=class extends u{constructor(t){super(t,"saturation",'aria-label="Color"',!0)}update(t){this.hsva=t,this.style([{top:`${100-t.v}%`,left:`${t.s}%`,color:d(t)},{"background-color":d({h:t.h,s:100,v:100,a:1})}]),this.el.setAttribute("aria-valuetext",`Saturation ${a(t.s)}%, Brightness ${a(t.v)}%`)}getMove(t,r){return{s:r?c(this.hsva.s+t.x*100,0,100):t.x*100,v:r?c(this.hsva.v-t.y*100,0,100):Math.round(100-t.y*100)}}};var Z=':host{display:flex;flex-direction:column;position:relative;width:200px;height:200px;user-select:none;-webkit-user-select:none;cursor:default}:host([hidden]){display:none!important}[role=slider]{position:relative;touch-action:none;user-select:none;-webkit-user-select:none;outline:0}[role=slider]:last-child{border-radius:0 0 8px 8px}[part$=pointer]{position:absolute;z-index:1;box-sizing:border-box;width:28px;height:28px;display:flex;place-content:center center;transform:translate(-50%,-50%);background-color:#fff;border:2px solid #fff;border-radius:50%;box-shadow:0 2px 4px rgba(0,0,0,.2)}[part$=pointer]::after{content:"";width:100%;height:100%;border-radius:inherit;background-color:currentColor}[role=slider]:focus [part$=pointer]{transform:translate(-50%,-50%) scale(1.1)}';var tt="[part=hue]{flex:0 0 24px;background:linear-gradient(to right,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%)}[part=hue-pointer]{top:50%;z-index:2}";var rt="[part=saturation]{flex-grow:1;border-color:transparent;border-bottom:12px solid #000;border-radius:8px 8px 0 0;background-image:linear-gradient(to top,#000,transparent),linear-gradient(to right,#fff,rgba(255,255,255,0));box-shadow:inset 0 0 0 1px rgba(0,0,0,.05)}[part=saturation-pointer]{z-index:3}";var w=Symbol("same"),R=Symbol("color"),et=Symbol("hsva"),_=Symbol("update"),ot=Symbol("parts"),g=Symbol("css"),x=Symbol("sliders"),p=class extends HTMLElement{static get observedAttributes(){return["color"]}get[g](){return[Z,tt,rt]}get[x](){return[T,S]}get color(){return this[R]}set color(t){if(!this[w](t)){let r=this.colorModel.toHsva(t);this[_](r),this[R]=t}}constructor(){super();let t=H(``),r=this.attachShadow({mode:"open"});r.appendChild(t.content.cloneNode(!0)),r.addEventListener("move",this),this[ot]=this[x].map(o=>new o(r))}connectedCallback(){if(this.hasOwnProperty("color")){let t=this.color;delete this.color,this.color=t}else this.color||(this.color=this.colorModel.defaultColor)}attributeChangedCallback(t,r,o){let s=this.colorModel.fromAttr(o);this[w](s)||(this.color=s)}handleEvent(t){let r=this[et],o={...r,...t.detail};this[_](o);let s;!L(o,r)&&!this[w](s=this.colorModel.fromHsva(o))&&(this[R]=s,f(this,"color-changed",{value:s}))}[w](t){return this.color&&this.colorModel.equal(t,this.color)}[_](t){this[et]=t,this[ot].forEach(r=>r.update(t))}};var dt={defaultColor:"#000",toHsva:F,fromHsva:({h:e,s:t,v:r})=>X({h:e,s:t,v:r,a:1}),equal:K,fromAttr:e=>e},y=class extends p{get colorModel(){return dt}};var P=class extends y{};customElements.define("hex-color-picker",P);var ht={defaultColor:"hsl(0, 0%, 0%)",toHsva:J,fromHsva:d,equal:h,fromAttr:e=>e},M=class extends p{get colorModel(){return ht}};var z=class extends M{};customElements.define("hsl-string-color-picker",z);var mt={defaultColor:"rgb(0, 0, 0)",toHsva:U,fromHsva:B,equal:h,fromAttr:e=>e},C=class extends p{get colorModel(){return mt}};var V=class extends C{};customElements.define("rgb-string-color-picker",V);var k=class extends u{constructor(t){super(t,"alpha",'aria-label="Alpha" aria-valuemin="0" aria-valuemax="1"',!1)}update(t){this.hsva=t;let r=$({...t,a:0}),o=$({...t,a:1}),s=t.a*100;this.style([{left:`${s}%`,color:$(t)},{"--gradient":`linear-gradient(90deg, ${r}, ${o}`}]);let n=a(s);this.el.setAttribute("aria-valuenow",`${n}`),this.el.setAttribute("aria-valuetext",`${n}%`)}getMove(t,r){return{a:r?c(this.hsva.a+t.x):t.x}}};var st=`[part=alpha]{flex:0 0 24px}[part=alpha]::after{display:block;content:"";position:absolute;top:0;left:0;right:0;bottom:0;border-radius:inherit;background-image:var(--gradient);box-shadow:inset 0 0 0 1px rgba(0,0,0,.05)}[part^=alpha]{background-color:#fff;background-image:url('data:image/svg+xml,')}[part=alpha-pointer]{top:50%}`;var E=class extends p{get[g](){return[...super[g],st]}get[x](){return[...super[x],k]}};var ft={defaultColor:"rgba(0, 0, 0, 1)",toHsva:I,fromHsva:D,equal:h,fromAttr:e=>e},N=class extends E{get colorModel(){return ft}};var j=class extends N{};customElements.define("rgba-string-color-picker",j);function gt({isAutofocused:e,isDisabled:t,isLive:r,isLiveDebounced:o,isLiveOnBlur:s,liveDebounce:n,state:i}){return{state:i,init(){this.state===null||this.state===""||this.setState(this.state),e&&this.togglePanelVisibility(this.$refs.input),this.$refs.input.addEventListener("change",l=>{this.setState(l.target.value)}),this.$refs.panel.addEventListener("color-changed",l=>{this.setState(l.detail.value),!(s||!(r||o))&&setTimeout(()=>{this.state===l.detail.value&&this.commitState()},o?n:250)}),(r||o||s)&&new MutationObserver(()=>this.isOpen()?null:this.commitState()).observe(this.$refs.panel,{attributes:!0,childList:!0})},togglePanelVisibility(){t||this.$refs.panel.toggle(this.$refs.input)},setState(l){this.state=l,this.$refs.input.value=l,this.$refs.panel.color=l},isOpen(){return this.$refs.panel.style.display==="block"},commitState(){JSON.stringify(this.$wire.__instance.canonical)!==JSON.stringify(this.$wire.__instance.ephemeral)&&this.$wire.$commit()}}}export{gt as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/filament/app.js: -------------------------------------------------------------------------------- 1 | (()=>{var te=Object.create,x=Object.defineProperty,re=Object.getPrototypeOf,ne=Object.prototype.hasOwnProperty,ie=Object.getOwnPropertyNames,ae=Object.getOwnPropertyDescriptor,se=t=>x(t,"__esModule",{value:!0}),oe=(t,n)=>()=>(n||(n={exports:{}},t(n.exports,n)),n.exports),le=(t,n,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let l of ie(n))!ne.call(t,l)&&l!=="default"&&x(t,l,{get:()=>n[l],enumerable:!(i=ae(n,l))||i.enumerable});return t},fe=t=>le(se(x(t!=null?te(re(t)):{},"default",t&&t.__esModule&&"default"in t?{get:()=>t.default,enumerable:!0}:{value:t,enumerable:!0})),t),ue=oe((t,n)=>{(function(i,l,g){if(!i)return;for(var c={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},_={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},y={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},G={option:"alt",command:"meta",return:"enter",escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},M,b=1;b<20;++b)c[111+b]="f"+b;for(b=0;b<=9;++b)c[b+96]=b.toString();function P(e,r,s){if(e.addEventListener){e.addEventListener(r,s,!1);return}e.attachEvent("on"+r,s)}function T(e){if(e.type=="keypress"){var r=String.fromCharCode(e.which);return e.shiftKey||(r=r.toLowerCase()),r}return c[e.which]?c[e.which]:_[e.which]?_[e.which]:String.fromCharCode(e.which).toLowerCase()}function V(e,r){return e.sort().join(",")===r.sort().join(",")}function J(e){var r=[];return e.shiftKey&&r.push("shift"),e.altKey&&r.push("alt"),e.ctrlKey&&r.push("ctrl"),e.metaKey&&r.push("meta"),r}function H(e){if(e.preventDefault){e.preventDefault();return}e.returnValue=!1}function F(e){if(e.stopPropagation){e.stopPropagation();return}e.cancelBubble=!0}function C(e){return e=="shift"||e=="ctrl"||e=="alt"||e=="meta"}function B(){if(!M){M={};for(var e in c)e>95&&e<112||c.hasOwnProperty(e)&&(M[c[e]]=e)}return M}function X(e,r,s){return s||(s=B()[e]?"keydown":"keypress"),s=="keypress"&&r.length&&(s="keydown"),s}function Y(e){return e==="+"?["+"]:(e=e.replace(/\+{2}/g,"+plus"),e.split("+"))}function R(e,r){var s,h,k,S=[];for(s=Y(e),k=0;k1){Z(a,m,f,p);return}u=R(a,p),r._callbacks[u.key]=r._callbacks[u.key]||[],z(u.key,u.modifiers,{type:u.action},o,a,d),r._callbacks[u.key][o?"unshift":"push"]({callback:f,modifiers:u.modifiers,action:u.action,seq:o,level:d,combo:a})}r._bindMultiple=function(a,f,p){for(var o=0;o-1||I(r,s.target))return!1;if("composedPath"in e&&typeof e.composedPath=="function"){var h=e.composedPath()[0];h!==e.target&&(r=h)}return r.tagName=="INPUT"||r.tagName=="SELECT"||r.tagName=="TEXTAREA"||r.isContentEditable},v.prototype.handleKey=function(){var e=this;return e._handleKey.apply(e,arguments)},v.addKeycodes=function(e){for(var r in e)e.hasOwnProperty(r)&&(c[r]=e[r]);M=null},v.init=function(){var e=v(l);for(var r in e)r.charAt(0)!=="_"&&(v[r]=(function(s){return function(){return e[s].apply(e,arguments)}})(r))},v.init(),i.Mousetrap=v,typeof n<"u"&&n.exports&&(n.exports=v),typeof define=="function"&&define.amd&&define(function(){return v})})(typeof window<"u"?window:null,typeof window<"u"?document:null)}),q=fe(ue());(function(t){if(t){var n={},i=t.prototype.stopCallback;t.prototype.stopCallback=function(l,g,c,_){var y=this;return y.paused?!0:n[c]||n[_]?!1:i.call(y,l,g,c)},t.prototype.bindGlobal=function(l,g,c){var _=this;if(_.bind(l,g,c),l instanceof Array){for(var y=0;y{t.directive("mousetrap",(n,{modifiers:i,expression:l},{evaluate:g})=>{let c=()=>l?g(l):n.click();i=i.map(_=>_.replace(/--/g," ").replace(/-/g,"+").replace(/\bslash\b/g,"/")),i.includes("global")&&(i=i.filter(_=>_!=="global"),q.default.bindGlobal(i,_=>{_.preventDefault(),c()})),q.default.bind(i,_=>{_.preventDefault(),c()}),document.addEventListener("livewire:navigating",()=>{q.default.unbind(i)},{once:!0})})},W=pe;var j=()=>({isOpen:window.Alpine.$persist(!0).as("isOpen"),isOpenDesktop:window.Alpine.$persist(!0).as("isOpenDesktop"),collapsedGroups:window.Alpine.$persist(null).as("collapsedGroups"),init(){this.resizeObserver=null,this.setUpResizeObserver(),document.addEventListener("livewire:navigated",()=>{this.setUpResizeObserver()})},setUpResizeObserver(){this.resizeObserver&&this.resizeObserver.disconnect();let t=window.innerWidth;this.resizeObserver=new ResizeObserver(()=>{let n=window.innerWidth,i=t>=1024,l=n<1024,g=n>=1024;i&&l?(this.isOpenDesktop=this.isOpen,this.isOpen&&this.close()):!i&&g&&(this.isOpen=this.isOpenDesktop),t=n}),this.resizeObserver.observe(document.body),window.innerWidth<1024?this.isOpen&&(this.isOpenDesktop=!0,this.close()):this.isOpenDesktop=this.isOpen},groupIsCollapsed(t){return this.collapsedGroups.includes(t)},collapseGroup(t){this.collapsedGroups.includes(t)||(this.collapsedGroups=this.collapsedGroups.concat(t))},toggleCollapsedGroup(t){this.collapsedGroups=this.collapsedGroups.includes(t)?this.collapsedGroups.filter(n=>n!==t):this.collapsedGroups.concat(t)},close(){this.isOpen=!1,window.innerWidth>=1024&&(this.isOpenDesktop=!1)},open(){this.isOpen=!0,window.innerWidth>=1024&&(this.isOpenDesktop=!0)}});document.addEventListener("alpine:init",()=>{let t=localStorage.getItem("theme")??getComputedStyle(document.documentElement).getPropertyValue("--default-theme-mode");window.Alpine.store("theme",t==="dark"||t==="system"&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.addEventListener("theme-changed",n=>{let i=n.detail;localStorage.setItem("theme",i),i==="system"&&(i=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.Alpine.store("theme",i)}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",n=>{localStorage.getItem("theme")==="system"&&window.Alpine.store("theme",n.matches?"dark":"light")}),window.Alpine.effect(()=>{window.Alpine.store("theme")==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")})});var U=window.history.replaceState,ce=window.history.pushState;window.history.replaceState=function(t,n,i){t?.url instanceof URL&&(t.url=t.url.toString());let l=i||t?.url||window.location.href,g=window.location.href;if(l!==g){U.call(window.history,t,n,i);return}try{let c=window.history.state;JSON.stringify(t)!==JSON.stringify(c)&&U.call(window.history,t,n,i)}catch{U.call(window.history,t,n,i)}};window.history.pushState=function(t,n,i){t?.url instanceof URL&&(t.url=t.url.toString()),ce.call(window.history,t,n,i)};document.addEventListener("DOMContentLoaded",()=>{setTimeout(()=>{let t=document.querySelector(".fi-main-sidebar .fi-sidebar-item.fi-active");if((!t||t.offsetParent===null)&&(t=document.querySelector(".fi-main-sidebar .fi-sidebar-group.fi-active")),!t||t.offsetParent===null)return;let n=document.querySelector(".fi-main-sidebar .fi-sidebar-nav");n&&n.scrollTo(0,t.offsetTop-window.innerHeight/2)},10)});window.setUpUnsavedDataChangesAlert=({body:t,livewireComponent:n,$wire:i})=>{window.addEventListener("beforeunload",l=>{window.jsMd5(JSON.stringify(i.data).replace(/\\/g,""))===i.savedDataHash||i?.__instance?.effects?.redirect||(l.preventDefault(),l.returnValue=!0)})};window.setUpSpaModeUnsavedDataChangesAlert=({body:t,resolveLivewireComponentUsing:n,$wire:i})=>{let l=()=>i?.__instance?.effects?.redirect?!1:window.jsMd5(JSON.stringify(i.data).replace(/\\/g,""))!==i.savedDataHash,g=()=>confirm(t);document.addEventListener("livewire:navigate",c=>{if(typeof n()<"u"){if(!l()||g())return;c.preventDefault()}}),window.addEventListener("beforeunload",c=>{l()&&(c.preventDefault(),c.returnValue=!0)})};window.setUpUnsavedActionChangesAlert=({resolveLivewireComponentUsing:t,$wire:n})=>{window.addEventListener("beforeunload",i=>{if(!(typeof t()>"u")&&(n.mountedActions?.length??0)&&!n?.__instance?.effects?.redirect){i.preventDefault(),i.returnValue=!0;return}})};document.addEventListener("alpine:init",()=>{window.Alpine.plugin(W),window.Alpine.store("sidebar",j())});})(); 2 | -------------------------------------------------------------------------------- /config/ide-helper.php: -------------------------------------------------------------------------------- 1 | '_ide_helper.php', 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Models filename 19 | |-------------------------------------------------------------------------- 20 | | 21 | | The default filename for the models helper file 22 | | 23 | */ 24 | 25 | 'models_filename' => '_ide_helper_models.php', 26 | 27 | /* 28 | |-------------------------------------------------------------------------- 29 | | Where to write the PhpStorm specific meta file 30 | |-------------------------------------------------------------------------- 31 | | 32 | | PhpStorm also supports the directory `.phpstorm.meta.php/` with arbitrary 33 | | files in it, should you need additional files for your project; e.g. 34 | | `.phpstorm.meta.php/laravel_ide_Helper.php'. 35 | | 36 | */ 37 | 'meta_filename' => '.phpstorm.meta.php', 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | Fluent helpers 42 | |-------------------------------------------------------------------------- 43 | | 44 | | Set to true to generate commonly used Fluent methods 45 | | 46 | */ 47 | 48 | 'include_fluent' => false, 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | Factory Builders 53 | |-------------------------------------------------------------------------- 54 | | 55 | | Set to true to generate factory generators for better factory() 56 | | method auto-completion. 57 | | 58 | | Deprecated for Laravel 8 or latest. 59 | | 60 | */ 61 | 62 | 'include_factory_builders' => false, 63 | 64 | /* 65 | |-------------------------------------------------------------------------- 66 | | Write Model Magic methods 67 | |-------------------------------------------------------------------------- 68 | | 69 | | Set to false to disable write magic methods of model 70 | | 71 | */ 72 | 73 | 'write_model_magic_where' => false, 74 | 75 | /* 76 | |-------------------------------------------------------------------------- 77 | | Write Model External Eloquent Builder methods 78 | |-------------------------------------------------------------------------- 79 | | 80 | | Set to false to disable write external eloquent builder methods 81 | | 82 | */ 83 | 84 | 'write_model_external_builder_methods' => true, 85 | 86 | /* 87 | |-------------------------------------------------------------------------- 88 | | Write Model relation count properties 89 | |-------------------------------------------------------------------------- 90 | | 91 | | Set to false to disable writing of relation count properties to model DocBlocks. 92 | | 93 | */ 94 | 95 | 'write_model_relation_count_properties' => true, 96 | 97 | /* 98 | |-------------------------------------------------------------------------- 99 | | Write Eloquent Model Mixins 100 | |-------------------------------------------------------------------------- 101 | | 102 | | This will add the necessary DocBlock mixins to the model class 103 | | contained in the Laravel Framework. This helps the IDE with 104 | | auto-completion. 105 | | 106 | | Please be aware that this setting changes a file within the /vendor directory. 107 | | 108 | */ 109 | 110 | 'write_eloquent_model_mixins' => false, 111 | 112 | /* 113 | |-------------------------------------------------------------------------- 114 | | Helper files to include 115 | |-------------------------------------------------------------------------- 116 | | 117 | | Include helper files. By default not included, but can be toggled with the 118 | | -- helpers (-H) option. Extra helper files can be included. 119 | | 120 | */ 121 | 122 | 'include_helpers' => false, 123 | 124 | 'helper_files' => [ 125 | base_path().'/vendor/laravel/framework/src/Illuminate/Support/helpers.php', 126 | ], 127 | 128 | /* 129 | |-------------------------------------------------------------------------- 130 | | Model locations to include 131 | |-------------------------------------------------------------------------- 132 | | 133 | | Define in which directories the ide-helper:models command should look 134 | | for models. 135 | | 136 | | glob patterns are supported to easier reach models in sub-directories, 137 | | e.g. `app/Services/* /Models` (without the space) 138 | | 139 | */ 140 | 141 | 'model_locations' => [ 142 | 'app', 143 | ], 144 | 145 | /* 146 | |-------------------------------------------------------------------------- 147 | | Models to ignore 148 | |-------------------------------------------------------------------------- 149 | | 150 | | Define which models should be ignored. 151 | | 152 | */ 153 | 154 | 'ignored_models' => [ 155 | 156 | ], 157 | 158 | /* 159 | |-------------------------------------------------------------------------- 160 | | Models hooks 161 | |-------------------------------------------------------------------------- 162 | | 163 | | Define which hook classes you want to run for models to add custom information 164 | | 165 | | Hooks should implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface. 166 | | 167 | */ 168 | 169 | 'model_hooks' => [ 170 | // App\Support\IdeHelper\MyModelHook::class 171 | ], 172 | 173 | /* 174 | |-------------------------------------------------------------------------- 175 | | Extra classes 176 | |-------------------------------------------------------------------------- 177 | | 178 | | These implementations are not really extended, but called with magic functions 179 | | 180 | */ 181 | 182 | 'extra' => [ 183 | 'Eloquent' => [\Illuminate\Database\Eloquent\Builder::class, \Illuminate\Database\Query\Builder::class], 184 | 'Session' => [\Illuminate\Session\Store::class], 185 | ], 186 | 187 | 'magic' => [], 188 | 189 | /* 190 | |-------------------------------------------------------------------------- 191 | | Interface implementations 192 | |-------------------------------------------------------------------------- 193 | | 194 | | These interfaces will be replaced with the implementing class. Some interfaces 195 | | are detected by the helpers, others can be listed below. 196 | | 197 | */ 198 | 199 | 'interfaces' => [ 200 | 201 | ], 202 | 203 | /* 204 | |-------------------------------------------------------------------------- 205 | | Support for camel cased models 206 | |-------------------------------------------------------------------------- 207 | | 208 | | There are some Laravel packages (such as Eloquence) that allow for accessing 209 | | Eloquent model properties via camel case, instead of snake case. 210 | | 211 | | Enabling this option will support these packages by saving all model 212 | | properties as camel case, instead of snake case. 213 | | 214 | | For example, normally you would see this: 215 | | 216 | | * @property \Illuminate\Support\Carbon $created_at 217 | | * @property \Illuminate\Support\Carbon $updated_at 218 | | 219 | | With this enabled, the properties will be this: 220 | | 221 | | * @property \Illuminate\Support\Carbon $createdAt 222 | | * @property \Illuminate\Support\Carbon $updatedAt 223 | | 224 | | Note, it is currently an all-or-nothing option. 225 | | 226 | */ 227 | 'model_camel_case_properties' => false, 228 | 229 | /* 230 | |-------------------------------------------------------------------------- 231 | | Property Casts 232 | |-------------------------------------------------------------------------- 233 | | 234 | | Cast the given "real type" to the given "type". 235 | | 236 | */ 237 | 'type_overrides' => [ 238 | 'integer' => 'int', 239 | 'boolean' => 'bool', 240 | ], 241 | 242 | /* 243 | |-------------------------------------------------------------------------- 244 | | Include DocBlocks from classes 245 | |-------------------------------------------------------------------------- 246 | | 247 | | Include DocBlocks from classes to allow additional code inspection for 248 | | magic methods and properties. 249 | | 250 | */ 251 | 'include_class_docblocks' => false, 252 | 253 | /* 254 | |-------------------------------------------------------------------------- 255 | | Force FQN usage 256 | |-------------------------------------------------------------------------- 257 | | 258 | | Use the fully qualified (class) name in docBlock, 259 | | event if class exists in a given file 260 | | or there is an import (use className) of a given class 261 | | 262 | */ 263 | 'force_fqn' => false, 264 | 265 | /* 266 | |-------------------------------------------------------------------------- 267 | | Use generics syntax 268 | |-------------------------------------------------------------------------- 269 | | 270 | | Use generics syntax within DocBlocks, 271 | | e.g. `Collection` instead of `Collection|User[]`. 272 | | 273 | */ 274 | 'use_generics_annotations' => true, 275 | 276 | /* 277 | |-------------------------------------------------------------------------- 278 | | Additional relation types 279 | |-------------------------------------------------------------------------- 280 | | 281 | | Sometimes it's needed to create custom relation types. The key of the array 282 | | is the Relationship Method name. The value of the array is the canonical class 283 | | name of the Relationship, e.g. `'relationName' => RelationShipClass::class`. 284 | | 285 | */ 286 | 'additional_relation_types' => [], 287 | 288 | /* 289 | |-------------------------------------------------------------------------- 290 | | Additional relation return types 291 | |-------------------------------------------------------------------------- 292 | | 293 | | When using custom relation types its possible for the class name to not contain 294 | | the proper return type of the relation. The key of the array is the relationship 295 | | method name. The value of the array is the return type of the relation ('many' 296 | | or 'morphTo'). 297 | | e.g. `'relationName' => 'many'`. 298 | | 299 | */ 300 | 'additional_relation_return_types' => [], 301 | 302 | /* 303 | |-------------------------------------------------------------------------- 304 | | Run artisan commands after migrations to generate model helpers 305 | |-------------------------------------------------------------------------- 306 | | 307 | | The specified commands should run after migrations are finished running. 308 | | 309 | */ 310 | 'post_migrate' => [ 311 | // 'ide-helper:models --nowrite', 312 | ], 313 | 314 | ]; 315 | -------------------------------------------------------------------------------- /public/js/filament/tables/tables.js: -------------------------------------------------------------------------------- 1 | (()=>{var M=Math.min,L=Math.max,B=Math.round,W=Math.floor,S=e=>({x:e,y:e});function J(e,t,i){return L(e,M(t,i))}function j(e,t){return typeof e=="function"?e(t):e}function H(e){return e.split("-")[0]}function Q(e){return e.split("-")[1]}function Z(e){return e==="x"?"y":"x"}function oe(e){return e==="y"?"height":"width"}var Pe=new Set(["top","bottom"]);function z(e){return Pe.has(H(e))?"y":"x"}function se(e){return Z(z(e))}function De(e){return{top:0,right:0,bottom:0,left:0,...e}}function re(e){return typeof e!="number"?De(e):{top:e,right:e,bottom:e,left:e}}function E(e){let{x:t,y:i,width:n,height:s}=e;return{width:n,height:s,top:i,left:t,right:t+n,bottom:i+s,x:t,y:i}}function le(e,t,i){let{reference:n,floating:s}=e,l=z(t),o=se(t),r=oe(o),c=H(t),a=l==="y",f=n.x+n.width/2-s.width/2,d=n.y+n.height/2-s.height/2,h=n[r]/2-s[r]/2,u;switch(c){case"top":u={x:f,y:n.y-s.height};break;case"bottom":u={x:f,y:n.y+n.height};break;case"right":u={x:n.x+n.width,y:d};break;case"left":u={x:n.x-s.width,y:d};break;default:u={x:n.x,y:n.y}}switch(Q(t)){case"start":u[o]-=h*(i&&a?-1:1);break;case"end":u[o]+=h*(i&&a?-1:1);break}return u}var ce=async(e,t,i)=>{let{placement:n="bottom",strategy:s="absolute",middleware:l=[],platform:o}=i,r=l.filter(Boolean),c=await(o.isRTL==null?void 0:o.isRTL(t)),a=await o.getElementRects({reference:e,floating:t,strategy:s}),{x:f,y:d}=le(a,n,c),h=n,u={},m=0;for(let p=0;p{let{x:g,y:x}=w;return{x:g,y:x}}},...c}=j(e,t),a={x:i,y:n},f=await ae(t,c),d=z(H(s)),h=Z(d),u=a[h],m=a[d];if(l){let w=h==="y"?"top":"left",g=h==="y"?"bottom":"right",x=u+f[w],y=u-f[g];u=J(x,u,y)}if(o){let w=d==="y"?"top":"left",g=d==="y"?"bottom":"right",x=m+f[w],y=m-f[g];m=J(x,m,y)}let p=r.fn({...t,[h]:u,[d]:m});return{...p,data:{x:p.x-i,y:p.y-n,enabled:{[h]:l,[d]:o}}}}}};function X(){return typeof window<"u"}function P(e){return he(e)?(e.nodeName||"").toLowerCase():"#document"}function v(e){var t;return(e==null||(t=e.ownerDocument)==null?void 0:t.defaultView)||window}function O(e){var t;return(t=(he(e)?e.ownerDocument:e.document)||window.document)==null?void 0:t.documentElement}function he(e){return X()?e instanceof Node||e instanceof v(e).Node:!1}function R(e){return X()?e instanceof Element||e instanceof v(e).Element:!1}function T(e){return X()?e instanceof HTMLElement||e instanceof v(e).HTMLElement:!1}function ue(e){return!X()||typeof ShadowRoot>"u"?!1:e instanceof ShadowRoot||e instanceof v(e).ShadowRoot}var $e=new Set(["inline","contents"]);function V(e){let{overflow:t,overflowX:i,overflowY:n,display:s}=C(e);return/auto|scroll|overlay|hidden|clip/.test(t+n+i)&&!$e.has(s)}var Ve=new Set(["table","td","th"]);function me(e){return Ve.has(P(e))}var Ne=[":popover-open",":modal"];function _(e){return Ne.some(t=>{try{return e.matches(t)}catch{return!1}})}var Be=["transform","translate","scale","rotate","perspective"],We=["transform","translate","scale","rotate","perspective","filter"],He=["paint","layout","strict","content"];function Y(e){let t=G(),i=R(e)?C(e):e;return Be.some(n=>i[n]?i[n]!=="none":!1)||(i.containerType?i.containerType!=="normal":!1)||!t&&(i.backdropFilter?i.backdropFilter!=="none":!1)||!t&&(i.filter?i.filter!=="none":!1)||We.some(n=>(i.willChange||"").includes(n))||He.some(n=>(i.contain||"").includes(n))}function ge(e){let t=k(e);for(;T(t)&&!D(t);){if(Y(t))return t;if(_(t))return null;t=k(t)}return null}function G(){return typeof CSS>"u"||!CSS.supports?!1:CSS.supports("-webkit-backdrop-filter","none")}var ze=new Set(["html","body","#document"]);function D(e){return ze.has(P(e))}function C(e){return v(e).getComputedStyle(e)}function I(e){return R(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function k(e){if(P(e)==="html")return e;let t=e.assignedSlot||e.parentNode||ue(e)&&e.host||O(e);return ue(t)?t.host:t}function pe(e){let t=k(e);return D(t)?e.ownerDocument?e.ownerDocument.body:e.body:T(t)&&V(t)?t:pe(t)}function $(e,t,i){var n;t===void 0&&(t=[]),i===void 0&&(i=!0);let s=pe(e),l=s===((n=e.ownerDocument)==null?void 0:n.body),o=v(s);if(l){let r=K(o);return t.concat(o,o.visualViewport||[],V(s)?s:[],r&&i?$(r):[])}return t.concat(s,$(s,[],i))}function K(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function be(e){let t=C(e),i=parseFloat(t.width)||0,n=parseFloat(t.height)||0,s=T(e),l=s?e.offsetWidth:i,o=s?e.offsetHeight:n,r=B(i)!==l||B(n)!==o;return r&&(i=l,n=o),{width:i,height:n,$:r}}function te(e){return R(e)?e:e.contextElement}function N(e){let t=te(e);if(!T(t))return S(1);let i=t.getBoundingClientRect(),{width:n,height:s,$:l}=be(t),o=(l?B(i.width):i.width)/n,r=(l?B(i.height):i.height)/s;return(!o||!Number.isFinite(o))&&(o=1),(!r||!Number.isFinite(r))&&(r=1),{x:o,y:r}}var _e=S(0);function ve(e){let t=v(e);return!G()||!t.visualViewport?_e:{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}}function Ie(e,t,i){return t===void 0&&(t=!1),!i||t&&i!==v(e)?!1:t}function F(e,t,i,n){t===void 0&&(t=!1),i===void 0&&(i=!1);let s=e.getBoundingClientRect(),l=te(e),o=S(1);t&&(n?R(n)&&(o=N(n)):o=N(e));let r=Ie(l,i,n)?ve(l):S(0),c=(s.left+r.x)/o.x,a=(s.top+r.y)/o.y,f=s.width/o.x,d=s.height/o.y;if(l){let h=v(l),u=n&&R(n)?v(n):n,m=h,p=K(m);for(;p&&n&&u!==m;){let w=N(p),g=p.getBoundingClientRect(),x=C(p),y=g.left+(p.clientLeft+parseFloat(x.paddingLeft))*w.x,A=g.top+(p.clientTop+parseFloat(x.paddingTop))*w.y;c*=w.x,a*=w.y,f*=w.x,d*=w.y,c+=y,a+=A,m=v(p),p=K(m)}}return E({width:f,height:d,x:c,y:a})}function q(e,t){let i=I(e).scrollLeft;return t?t.left+i:F(O(e)).left+i}function Re(e,t){let i=e.getBoundingClientRect(),n=i.left+t.scrollLeft-q(e,i),s=i.top+t.scrollTop;return{x:n,y:s}}function Ue(e){let{elements:t,rect:i,offsetParent:n,strategy:s}=e,l=s==="fixed",o=O(n),r=t?_(t.floating):!1;if(n===o||r&&l)return i;let c={scrollLeft:0,scrollTop:0},a=S(1),f=S(0),d=T(n);if((d||!d&&!l)&&((P(n)!=="body"||V(o))&&(c=I(n)),T(n))){let u=F(n);a=N(n),f.x=u.x+n.clientLeft,f.y=u.y+n.clientTop}let h=o&&!d&&!l?Re(o,c):S(0);return{width:i.width*a.x,height:i.height*a.y,x:i.x*a.x-c.scrollLeft*a.x+f.x+h.x,y:i.y*a.y-c.scrollTop*a.y+f.y+h.y}}function je(e){return Array.from(e.getClientRects())}function Xe(e){let t=O(e),i=I(e),n=e.ownerDocument.body,s=L(t.scrollWidth,t.clientWidth,n.scrollWidth,n.clientWidth),l=L(t.scrollHeight,t.clientHeight,n.scrollHeight,n.clientHeight),o=-i.scrollLeft+q(e),r=-i.scrollTop;return C(n).direction==="rtl"&&(o+=L(t.clientWidth,n.clientWidth)-s),{width:s,height:l,x:o,y:r}}var we=25;function Ye(e,t){let i=v(e),n=O(e),s=i.visualViewport,l=n.clientWidth,o=n.clientHeight,r=0,c=0;if(s){l=s.width,o=s.height;let f=G();(!f||f&&t==="fixed")&&(r=s.offsetLeft,c=s.offsetTop)}let a=q(n);if(a<=0){let f=n.ownerDocument,d=f.body,h=getComputedStyle(d),u=f.compatMode==="CSS1Compat"&&parseFloat(h.marginLeft)+parseFloat(h.marginRight)||0,m=Math.abs(n.clientWidth-d.clientWidth-u);m<=we&&(l-=m)}else a<=we&&(l+=a);return{width:l,height:o,x:r,y:c}}var Ge=new Set(["absolute","fixed"]);function Ke(e,t){let i=F(e,!0,t==="fixed"),n=i.top+e.clientTop,s=i.left+e.clientLeft,l=T(e)?N(e):S(1),o=e.clientWidth*l.x,r=e.clientHeight*l.y,c=s*l.x,a=n*l.y;return{width:o,height:r,x:c,y:a}}function xe(e,t,i){let n;if(t==="viewport")n=Ye(e,i);else if(t==="document")n=Xe(O(e));else if(R(t))n=Ke(t,i);else{let s=ve(e);n={x:t.x-s.x,y:t.y-s.y,width:t.width,height:t.height}}return E(n)}function Ce(e,t){let i=k(e);return i===t||!R(i)||D(i)?!1:C(i).position==="fixed"||Ce(i,t)}function qe(e,t){let i=t.get(e);if(i)return i;let n=$(e,[],!1).filter(r=>R(r)&&P(r)!=="body"),s=null,l=C(e).position==="fixed",o=l?k(e):e;for(;R(o)&&!D(o);){let r=C(o),c=Y(o);!c&&r.position==="fixed"&&(s=null),(l?!c&&!s:!c&&r.position==="static"&&!!s&&Ge.has(s.position)||V(o)&&!c&&Ce(e,o))?n=n.filter(f=>f!==o):s=r,o=k(o)}return t.set(e,n),n}function Je(e){let{element:t,boundary:i,rootBoundary:n,strategy:s}=e,o=[...i==="clippingAncestors"?_(t)?[]:qe(t,this._c):[].concat(i),n],r=o[0],c=o.reduce((a,f)=>{let d=xe(t,f,s);return a.top=L(d.top,a.top),a.right=M(d.right,a.right),a.bottom=M(d.bottom,a.bottom),a.left=L(d.left,a.left),a},xe(t,r,s));return{width:c.right-c.left,height:c.bottom-c.top,x:c.left,y:c.top}}function Qe(e){let{width:t,height:i}=be(e);return{width:t,height:i}}function Ze(e,t,i){let n=T(t),s=O(t),l=i==="fixed",o=F(e,!0,l,t),r={scrollLeft:0,scrollTop:0},c=S(0);function a(){c.x=q(s)}if(n||!n&&!l)if((P(t)!=="body"||V(s))&&(r=I(t)),n){let u=F(t,!0,l,t);c.x=u.x+t.clientLeft,c.y=u.y+t.clientTop}else s&&a();l&&!n&&s&&a();let f=s&&!n&&!l?Re(s,r):S(0),d=o.left+r.scrollLeft-c.x-f.x,h=o.top+r.scrollTop-c.y-f.y;return{x:d,y:h,width:o.width,height:o.height}}function ee(e){return C(e).position==="static"}function ye(e,t){if(!T(e)||C(e).position==="fixed")return null;if(t)return t(e);let i=e.offsetParent;return O(e)===i&&(i=i.ownerDocument.body),i}function Ae(e,t){let i=v(e);if(_(e))return i;if(!T(e)){let s=k(e);for(;s&&!D(s);){if(R(s)&&!ee(s))return s;s=k(s)}return i}let n=ye(e,t);for(;n&&me(n)&&ee(n);)n=ye(n,t);return n&&D(n)&&ee(n)&&!Y(n)?i:n||ge(e)||i}var et=async function(e){let t=this.getOffsetParent||Ae,i=this.getDimensions,n=await i(e.floating);return{reference:Ze(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:n.width,height:n.height}}};function tt(e){return C(e).direction==="rtl"}var nt={convertOffsetParentRelativeRectToViewportRelativeRect:Ue,getDocumentElement:O,getClippingRect:Je,getOffsetParent:Ae,getElementRects:et,getClientRects:je,getDimensions:Qe,getScale:N,isElement:R,isRTL:tt};function Se(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}function it(e,t){let i=null,n,s=O(e);function l(){var r;clearTimeout(n),(r=i)==null||r.disconnect(),i=null}function o(r,c){r===void 0&&(r=!1),c===void 0&&(c=1),l();let a=e.getBoundingClientRect(),{left:f,top:d,width:h,height:u}=a;if(r||t(),!h||!u)return;let m=W(d),p=W(s.clientWidth-(f+h)),w=W(s.clientHeight-(d+u)),g=W(f),y={rootMargin:-m+"px "+-p+"px "+-w+"px "+-g+"px",threshold:L(0,M(1,c))||1},A=!0;function b(ie){let U=ie[0].intersectionRatio;if(U!==c){if(!A)return o();U?o(!1,U):n=setTimeout(()=>{o(!1,1e-7)},1e3)}U===1&&!Se(a,e.getBoundingClientRect())&&o(),A=!1}try{i=new IntersectionObserver(b,{...y,root:s.ownerDocument})}catch{i=new IntersectionObserver(b,y)}i.observe(e)}return o(!0),l}function Oe(e,t,i,n){n===void 0&&(n={});let{ancestorScroll:s=!0,ancestorResize:l=!0,elementResize:o=typeof ResizeObserver=="function",layoutShift:r=typeof IntersectionObserver=="function",animationFrame:c=!1}=n,a=te(e),f=s||l?[...a?$(a):[],...$(t)]:[];f.forEach(g=>{s&&g.addEventListener("scroll",i,{passive:!0}),l&&g.addEventListener("resize",i)});let d=a&&r?it(a,i):null,h=-1,u=null;o&&(u=new ResizeObserver(g=>{let[x]=g;x&&x.target===a&&u&&(u.unobserve(t),cancelAnimationFrame(h),h=requestAnimationFrame(()=>{var y;(y=u)==null||y.observe(t)})),i()}),a&&!c&&u.observe(a),u.observe(t));let m,p=c?F(e):null;c&&w();function w(){let g=F(e);p&&!Se(p,g)&&i(),p=g,m=requestAnimationFrame(w)}return i(),()=>{var g;f.forEach(x=>{s&&x.removeEventListener("scroll",i),l&&x.removeEventListener("resize",i)}),d?.(),(g=u)==null||g.disconnect(),u=null,c&&cancelAnimationFrame(m)}}var Te=fe;var Le=de;var ke=(e,t,i)=>{let n=new Map,s={platform:nt,...i},l={...s.platform,_c:n};return ce(e,t,{...s,platform:l})};var Ee=({areGroupsCollapsedByDefault:e,canTrackDeselectedRecords:t,currentSelectionLivewireProperty:i,maxSelectableRecords:n,selectsCurrentPageOnly:s,$wire:l})=>({areFiltersOpen:!1,checkboxClickController:null,groupVisibility:[],isLoading:!1,selectedRecords:new Set,deselectedRecords:new Set,isTrackingDeselectedRecords:!1,shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,entangledSelectedRecords:i?l.$entangle(i):null,cleanUpFiltersDropdown:null,init(){this.livewireId=this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value,l.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),l.$on("scrollToTopOfTable",()=>this.$root.scrollIntoView({block:"start",inline:"nearest"})),i&&(n!==1?this.selectedRecords=new Set(this.entangledSelectedRecords):this.selectedRecords=new Set(this.entangledSelectedRecords?[this.entangledSelectedRecords]:[])),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:o})=>{o.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction(...o){l.set("isTrackingDeselectedTableRecords",this.isTrackingDeselectedRecords,!1),l.set("selectedTableRecords",[...this.selectedRecords],!1),l.set("deselectedTableRecords",[...this.deselectedRecords],!1),l.mountAction(...o)},toggleSelectRecordsOnPage(){let o=this.getRecordsOnPage();if(this.areRecordsSelected(o)){this.deselectRecords(o);return}this.selectRecords(o)},toggleSelectRecords(o){this.areRecordsSelected(o)?this.deselectRecords(o):this.selectRecords(o)},getSelectedRecordsCount(){return this.isTrackingDeselectedRecords?(this.$refs.allSelectableRecordsCount?.value??this.deselectedRecords.size)-this.deselectedRecords.size:this.selectedRecords.size},getRecordsOnPage(){let o=[];for(let r of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])o.push(r.value);return o},selectRecords(o){n===1&&(this.deselectAllRecords(),o=o.slice(0,1));for(let r of o)if(!this.isRecordSelected(r)){if(this.isTrackingDeselectedRecords){this.deselectedRecords.delete(r);continue}this.selectedRecords.add(r)}this.updatedSelectedRecords()},deselectRecords(o){for(let r of o){if(this.isTrackingDeselectedRecords){this.deselectedRecords.add(r);continue}this.selectedRecords.delete(r)}this.updatedSelectedRecords()},updatedSelectedRecords(){if(n!==1){this.entangledSelectedRecords=[...this.selectedRecords];return}this.entangledSelectedRecords=[...this.selectedRecords][0]??null},toggleSelectedRecord(o){if(this.isRecordSelected(o)){this.deselectRecords([o]);return}this.selectRecords([o])},async selectAllRecords(){if(!t||s){this.isLoading=!0,this.selectedRecords=new Set(await l.getAllSelectableTableRecordKeys()),this.updatedSelectedRecords(),this.isLoading=!1;return}this.isTrackingDeselectedRecords=!0,this.selectedRecords=new Set,this.deselectedRecords=new Set,this.updatedSelectedRecords()},canSelectAllRecords(){if(s){let c=this.getRecordsOnPage();return!this.areRecordsSelected(c)&&this.areRecordsToggleable(c)}let o=parseInt(this.$refs.allSelectableRecordsCount?.value);if(!o)return!1;let r=this.getSelectedRecordsCount();return o===r?!1:n===null||o<=n},deselectAllRecords(){this.isTrackingDeselectedRecords=!1,this.selectedRecords=new Set,this.deselectedRecords=new Set,this.updatedSelectedRecords()},isRecordSelected(o){return this.isTrackingDeselectedRecords?!this.deselectedRecords.has(o):this.selectedRecords.has(o)},areRecordsSelected(o){return o.every(r=>this.isRecordSelected(r))},areRecordsToggleable(o){if(n===null||n===1)return!0;let r=o.filter(c=>this.isRecordSelected(c));return r.length===o.length?!0:this.getSelectedRecordsCount()+(o.length-r.length)<=n},toggleCollapseGroup(o){this.isGroupCollapsed(o)?e?this.groupVisibility.push(o):this.groupVisibility.splice(this.groupVisibility.indexOf(o),1):e?this.groupVisibility.splice(this.groupVisibility.indexOf(o),1):this.groupVisibility.push(o)},isGroupCollapsed(o){return e?!this.groupVisibility.includes(o):this.groupVisibility.includes(o)},resetCollapsedGroups(){this.groupVisibility=[]},watchForCheckboxClicks(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:o}=this.checkboxClickController;this.$root?.addEventListener("click",r=>r.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(r,r.target),{signal:o})},handleCheckboxClick(o,r){if(!this.lastChecked){this.lastChecked=r;return}if(o.shiftKey){let c=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!c.includes(this.lastChecked)){this.lastChecked=r;return}let a=c.indexOf(this.lastChecked),f=c.indexOf(r),d=[a,f].sort((u,m)=>u-m),h=[];for(let u=d[0];u<=d[1];u++)h.push(c[u].value);if(r.checked){if(!this.areRecordsToggleable(h)){r.checked=!1,this.deselectRecords([r.value]);return}this.selectRecords(h)}else this.deselectRecords(h)}this.lastChecked=r},toggleFiltersDropdown(){if(this.areFiltersOpen=!this.areFiltersOpen,this.areFiltersOpen){let o=Oe(this.$refs.filtersTriggerActionContainer,this.$refs.filtersContentContainer,async()=>{let{x:a,y:f}=await ke(this.$refs.filtersTriggerActionContainer,this.$refs.filtersContentContainer,{placement:"bottom-end",middleware:[Te(8),Le({padding:8})]});Object.assign(this.$refs.filtersContentContainer.style,{left:`${a}px`,top:`${f}px`})}),r=a=>{let f=this.$refs.filtersTriggerActionContainer,d=this.$refs.filtersContentContainer;d&&d.contains(a.target)||f&&f.contains(a.target)||(this.areFiltersOpen=!1,this.cleanUpFiltersDropdown&&(this.cleanUpFiltersDropdown(),this.cleanUpFiltersDropdown=null))};document.addEventListener("mousedown",r),document.addEventListener("touchstart",r,{passive:!0});let c=a=>{a.key==="Escape"&&r(a)};document.addEventListener("keydown",c),this.cleanUpFiltersDropdown=()=>{o(),document.removeEventListener("mousedown",r),document.removeEventListener("touchstart",r,{passive:!0}),document.removeEventListener("keydown",c)}}else this.cleanUpFiltersDropdown&&(this.cleanUpFiltersDropdown(),this.cleanUpFiltersDropdown=null)}});function ne({columns:e,isLive:t}){return{error:void 0,isLoading:!1,deferredColumns:[],columns:e,isLive:t,hasReordered:!1,init(){if(!this.columns||this.columns.length===0){this.columns=[];return}this.deferredColumns=JSON.parse(JSON.stringify(this.columns))},get groupedColumns(){let i={};return this.deferredColumns.filter(n=>n.type==="group").forEach(n=>{i[n.name]=this.calculateGroupedColumns(n)}),i},calculateGroupedColumns(i){if((i?.columns?.filter(r=>!r.isHidden)??[]).length===0)return{hidden:!0,checked:!1,disabled:!1,indeterminate:!1};let s=i.columns.filter(r=>!r.isHidden&&r.isToggleable!==!1);if(s.length===0)return{checked:!0,disabled:!0,indeterminate:!1};let l=s.filter(r=>r.isToggled).length,o=i.columns.filter(r=>!r.isHidden&&r.isToggleable===!1);return l===0&&o.length>0?{checked:!0,disabled:!1,indeterminate:!0}:l===0?{checked:!1,disabled:!1,indeterminate:!1}:l===s.length?{checked:!0,disabled:!1,indeterminate:!1}:{checked:!0,disabled:!1,indeterminate:!0}},getColumn(i,n=null){return n?this.deferredColumns.find(l=>l.type==="group"&&l.name===n)?.columns?.find(l=>l.name===i):this.deferredColumns.find(s=>s.name===i)},toggleGroup(i){let n=this.deferredColumns.find(c=>c.type==="group"&&c.name===i);if(!n?.columns)return;let s=this.calculateGroupedColumns(n);if(s.disabled)return;let o=n.columns.filter(c=>c.isToggleable!==!1).some(c=>c.isToggled),r=s.indeterminate?!0:!o;n.columns.filter(c=>c.isToggleable!==!1).forEach(c=>{c.isToggled=r}),this.deferredColumns=[...this.deferredColumns],this.isLive&&this.applyTableColumnManager()},toggleColumn(i,n=null){let s=this.getColumn(i,n);!s||s.isToggleable===!1||(s.isToggled=!s.isToggled,this.deferredColumns=[...this.deferredColumns],this.isLive&&this.applyTableColumnManager())},reorderColumns(i){let n=i.map(s=>s.split("::"));this.reorderTopLevel(n),this.hasReordered=!0,this.isLive&&this.applyTableColumnManager()},reorderGroupColumns(i,n){let s=this.deferredColumns.find(r=>r.type==="group"&&r.name===n);if(!s)return;let l=i.map(r=>r.split("::")),o=[];l.forEach(([r,c])=>{let a=s.columns.find(f=>f.name===c);a&&o.push(a)}),s.columns=o,this.deferredColumns=[...this.deferredColumns],this.hasReordered=!0,this.isLive&&this.applyTableColumnManager()},reorderTopLevel(i){let n=this.deferredColumns,s=[];i.forEach(([l,o])=>{let r=n.find(c=>l==="group"?c.type==="group"&&c.name===o:l==="column"?c.type!=="group"&&c.name===o:!1);r&&s.push(r)}),this.deferredColumns=s},async applyTableColumnManager(){this.isLoading=!0;try{this.columns=JSON.parse(JSON.stringify(this.deferredColumns)),await this.$wire.call("applyTableColumnManager",this.columns,this.hasReordered),this.hasReordered=!1,this.error=void 0}catch(i){this.error="Failed to update column visibility",console.error("Table toggle columns error:",i)}finally{this.isLoading=!1}}}}document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentTable",Ee),window.Alpine.data("filamentTableColumnManager",ne)});})(); 2 | --------------------------------------------------------------------------------