├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── bootstrap └── app.php ├── composer.json ├── config └── laravel_csv.php ├── database └── migrations │ └── create_csv_imports_table.php.stub ├── mix-manifest.json ├── package-lock.json ├── package.json ├── pint.json ├── postcss.config.js ├── resources ├── assets │ ├── js │ │ └── app.js │ └── scss │ │ ├── tailwind.scss │ │ └── tailwindcss │ │ └── framework │ │ └── _tailwind.scss ├── dist │ ├── css │ │ └── tailwind.css │ └── js │ │ └── app.js ├── mix-manifest.json └── views │ ├── .gitkeep │ ├── components │ └── button.blade.php │ └── livewire │ └── tailwindcss │ ├── csv-importer.blade.php │ └── handle-imports.blade.php ├── src ├── Concerns │ ├── HasCsvImports.php │ ├── HasCsvProperties.php │ ├── InteractsWithColumns.php │ └── InteractsWithCsvFiles.php ├── Facades │ └── LaravelCsv.php ├── Http │ └── Livewire │ │ ├── CsvImporter.php │ │ └── HandleImports.php ├── Jobs │ └── ImportCsv.php ├── LaravelCsvDirectives.php ├── LaravelCsvManager.php ├── LaravelCsvServiceProvider.php ├── Models │ └── Import.php ├── Scopes │ └── ImportScope.php ├── Utilities │ └── ChunkIterator.php └── helpers.php ├── tailwind.config.js └── webpack.mix.js /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-csv` will be documented in this file. 4 | 5 | ## v1.1.1 - 2022-11-07 6 | 7 | ### What's Changed 8 | 9 | - Bump dependabot/fetch-metadata from 1.3.4 to 1.3.5 by @dependabot in https://github.com/coderflexx/laravel-csv/pull/13 10 | - Update csv-importer.blade.php by @Globerada in https://github.com/coderflexx/laravel-csv/pull/12 11 | 12 | ### New Contributors 13 | 14 | - @Globerada made their first contribution in https://github.com/coderflexx/laravel-csv/pull/12 15 | 16 | **Full Changelog**: https://github.com/coderflexx/laravel-csv/compare/v1.1.0...v1.1.1 17 | 18 | ## v1.1.0 - 2022-10-12 19 | 20 | ### What's Changed 21 | 22 | - Update README.md by @askdkc in https://github.com/coderflexx/laravel-csv/pull/9 23 | - Bump dependabot/fetch-metadata from 1.3.3 to 1.3.4 by @dependabot in https://github.com/coderflexx/laravel-csv/pull/10 24 | - Fixed vendor:publish tag name by @askdkc in https://github.com/coderflexx/laravel-csv/pull/11 25 | 26 | ### New Contributors 27 | 28 | - @dependabot made their first contribution in https://github.com/coderflexx/laravel-csv/pull/10 29 | 30 | **Full Changelog**: https://github.com/coderflexx/laravel-csv/compare/v1.0.2...v1.1.0 31 | 32 | ## v1.0.2 - 2022-09-29 33 | 34 | ### What's Changed 35 | 36 | - Bug Fix: package installation failure by @askdkc in https://github.com/coderflexx/laravel-csv/pull/5 37 | 38 | **Full Changelog**: https://github.com/coderflexx/laravel-csv/compare/v1.0.1...v1.0.2 39 | 40 | ## v1.0.1 - 2022-09-28 41 | 42 | ### What's Changed 43 | 44 | - Copy changes for README by @johnwesely in https://github.com/coderflexx/laravel-csv/pull/2 45 | - Fixed broken link by @askdkc in https://github.com/coderflexx/laravel-csv/pull/3 46 | 47 | ### New Contributors 48 | 49 | - @johnwesely made their first contribution in https://github.com/coderflexx/laravel-csv/pull/2 50 | - @askdkc made their first contribution in https://github.com/coderflexx/laravel-csv/pull/3 51 | 52 | **Full Changelog**: https://github.com/coderflexx/laravel-csv/compare/v1.0.0...v1.0.1 53 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Coderflex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Laravisit Logo 3 |

4 |

5 | 6 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/coderflex/laravel-csv.svg?style=flat-square)](https://packagist.org/packages/coderflex/laravel-csv) 7 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/coderflexx/laravel-csv/run-tests.yml?branch=main&label=tests)](https://github.com/coderflexx/laravel-csv/actions?query=workflow%3Arun-tests+branch%3Amain) 8 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/coderflexx/laravel-csv/phpstan.yml?branch=main&label=code%20style)](https://github.com/coderflexx/laravel-csv/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 9 | [![Total Downloads](https://img.shields.io/packagist/dt/coderflex/laravel-csv.svg?style=flat-square)](https://packagist.org/packages/coderflex/laravel-csv) 10 | 11 | 12 | - [Introduction](#introduction) 13 | - [Installation](#installation) 14 | - [Configuration](#configuration) 15 | - [Usage](#usage) 16 | - [CSV Importer Component](#csv-importer-component) 17 | - [Button Component](#button-component) 18 | - [In TALL stack project](#in-tall-stack-project) 19 | - [In none TALL Stack project](#in-none-tall-stack-project) 20 | - [Using Queues](#using-queues) 21 | - [Testing](#testing) 22 | - [Changelog](#changelog) 23 | - [Contributing](#contributing) 24 | - [Security Vulnerabilities](#security-vulnerabilities) 25 | - [Inspiration](#inspiration) 26 | - [Credits](#credits) 27 | - [License](#license) 28 | 29 | ## Introduction 30 | __Laravel CSV__ Package is a package created on top of Laravel [livewire](https://laravel-livewire.com) for easily handling imports with a simple API. 31 | 32 | ## Installation 33 | 34 | You can install the package via composer: 35 | 36 | ```bash 37 | composer require coderflex/laravel-csv 38 | ``` 39 | 40 | ## Configuration 41 | 42 | Publish and run the migrations with: 43 | 44 | ```bash 45 | php artisan vendor:publish --tag="csv-migrations" 46 | php artisan migrate 47 | ``` 48 | 49 | Add trait **HasCsvImports** to your User model. 50 | 51 | Publish the config file with: 52 | 53 | ```bash 54 | php artisan vendor:publish --tag="csv-config" 55 | ``` 56 | 57 | The following is the contents of the published config file: 58 | 59 | ```php 60 | 61 | return [ 62 | 63 | /* 64 | |-------------------------------------------------------------------------- 65 | | Default Layout 66 | |-------------------------------------------------------------------------- 67 | | 68 | | This package plans on supporting multiple CSS frameworks. 69 | | Currently, 'tailwindcss' is the default and only supported framework. 70 | | 71 | */ 72 | 'layout' => 'tailwindcss', 73 | 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | Max Upload File Size 77 | |-------------------------------------------------------------------------- 78 | | 79 | | The default maximumum file size that can be imported by this 80 | | package is 20MB. If you wish to increase/decrease this value, 81 | | change the value in KB below. 82 | | 83 | */ 84 | 'file_upload_size' => 20000, 85 | ]; 86 | ``` 87 | 88 | The `layout` option is for choosing which CSS framework you are using and currently supports only `tailwindcss`. We are working on other CSS frameworks to implement in the future. 89 | 90 | The `file_upload_size` is for validation rules, and it defines the maximum file size of uploaded files. You may also define this value from the [livewire config](https://github.com/livewire/livewire/blob/master/config/livewire.php#L100) file. 91 | 92 | Optionally, you can publish the views using 93 | 94 | ```bash 95 | php artisan vendor:publish --tag="laravel-csv-views" 96 | ``` 97 | 98 | > Before Using this command, please take a look at this [section](#in-tall-stack-project) below. 99 | 100 | ## Usage 101 | 102 | ### CSV Importer Component 103 | Using this package is a breeze. To implement the importer in your project, simply include the following component inside a Blade view. 104 | 105 | ```blade 106 | 115 | ``` 116 | 117 | | Props | Type | Description | 118 | |---|---|---| 119 | | model |`string` | Fully qualified name of the model you wish to import to | 120 | | columns-to-map |`array` | Column names in the target database table | 121 | | required-columns |`array` | Columns that are required by validation for import | 122 | | columns-label |`array` | Display labels for the required columns | 123 | 124 | ### Button Component 125 | The Component uses `alpinejs` under the hood. To display an import button, include the `x-csv-button` component. 126 | 127 | ```blade 128 | Import 129 | ``` 130 | 131 | To style the button, use the `class` attribute with Tailwind utility classes. 132 | 133 | ```blade 134 | 138 | {{ __('Import') }} 139 | 140 | ``` 141 | ### In TALL stack project 142 | If you are using this package in a [TALL Stack](https://tallstack.dev/) project, (Tailwindcss, Alpinejs, Laravel, Livewire) publish the vendor views to include Laravel-CSV in your project. 143 | 144 | ```bash 145 | php artisan vendor:publish --tag="csv-views" 146 | ``` 147 | Then compile your assets. 148 | ```bash 149 | npm run dev 150 | ``` 151 | 152 | ### In none TALL Stack project 153 | If you are not using the TALL Stack, use the `csv directives` to add the necessary styles/scripts. 154 | 155 | ```blade 156 | 157 | ... 158 | 159 | ... 160 | @csvStyles 161 | 162 | ... 163 |
164 | ... 165 | @csvScripts 166 |
167 | 168 | 169 | ``` 170 | ### Using Queues 171 | This package uses [queues](https://laravel.com/docs/9.x/queues#main-content) under the hood with [PHP Generators](https://www.php.net/manual/en/language.generators.overview.php) to make it fast and efficient. 172 | 173 | Create the `batches table` by running 174 | ```bash 175 | php artisan queue:batches-table 176 | ``` 177 | Then, run the migration. 178 | ``` 179 | php artisan migrate 180 | ``` 181 | 182 | After that, set up the queues' configuration. 183 | Head to [Laravel Queues Documentation](https://laravel.com/docs/9.x/queues#main-content) to learn more. 184 | 185 | 186 | ## Testing 187 | 188 | ```bash 189 | composer test 190 | ``` 191 | 192 | ## Changelog 193 | 194 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 195 | 196 | ## Contributing 197 | 198 | Please see [CONTRIBUTING](https://github.com/ousid/.github/blob/main/CONTRIBUTING.md) for details. 199 | 200 | ## Security Vulnerabilities 201 | 202 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 203 | 204 | ## Inspiration 205 | This Package Was Inspired by [codecourse](https://codecourse.com) video series. If you want to learn how this package was created, make sure to take a look at this [video series](https://codecourse.com/subjects/laravel-livewire) 206 | 207 | ## Credits 208 | 209 | - [ousid](https://github.com/ousid) 210 | - [All Contributors](../../contributors) 211 | 212 | ## License 213 | 214 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 215 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | 'tailwindcss', 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Max Upload File Size 19 | |-------------------------------------------------------------------------- 20 | | 21 | | The default maximumum file size that can be imported by this 22 | | package is 20MB. If you wish to increase/decrease this value, 23 | | change the value in KB below. 24 | | 25 | */ 26 | 'file_upload_size' => 20000, 27 | ]; 28 | -------------------------------------------------------------------------------- /database/migrations/create_csv_imports_table.php.stub: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('user_id')->constrained(); 14 | $table->string('model'); 15 | $table->string('file_path'); 16 | $table->string('file_name'); 17 | $table->unsignedBigInteger('total_rows'); 18 | $table->unsignedBigInteger('processed_rows')->default(0); 19 | $table->datetime('completed_at')->nullable(); 20 | $table->timestamps(); 21 | }); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/resources/dist/js/app.js": "/resources/dist/js/app.js", 3 | "/resources/dist/css/tailwind.css": "/resources/dist/css/tailwind.css" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel-csv", 3 | "author": "Oussama Sid", 4 | "License": "MIT", 5 | "scripts": { 6 | "dev": "npm run development", 7 | "development": "mix", 8 | "watch": "mix watch", 9 | "watch-poll": "mix watch -- --watch-options-poll=1000", 10 | "hot": "mix watch --hot", 11 | "prod": "npm run production", 12 | "production": "mix --production" 13 | }, 14 | "devDependencies": { 15 | "autoprefixer": "^10.4.8", 16 | "laravel-mix": "^6.0.49", 17 | "postcss": "^8.4.16", 18 | "resolve-url-loader": "^5.0.0", 19 | "sass": "^1.54.9", 20 | "sass-loader": "^12.6.0", 21 | "tailwindcss": "^3.1.8" 22 | }, 23 | "dependencies": { 24 | "alpinejs": "^3.10.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "build" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /resources/assets/js/app.js: -------------------------------------------------------------------------------- 1 | import Alpine from 'alpinejs' 2 | 3 | window.Alpine = Alpine 4 | 5 | Alpine.start() -------------------------------------------------------------------------------- /resources/assets/scss/tailwind.scss: -------------------------------------------------------------------------------- 1 | /** 2 | |------------------------------------------------------ 3 | | Import Tailwind CSS Utilities 4 | |------------------------------------------------------ 5 | **/ 6 | 7 | @import './tailwindcss/framework/tailwind'; 8 | -------------------------------------------------------------------------------- /resources/assets/scss/tailwindcss/framework/_tailwind.scss: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | 5 | [x-cloak] { 6 | display: none !important; 7 | } 8 | -------------------------------------------------------------------------------- /resources/dist/css/tailwind.css: -------------------------------------------------------------------------------- 1 | /*! tailwindcss v3.1.8 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::-webkit-backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{left:0;right:0}.inset-0,.inset-y-0{bottom:0;top:0}.right-0{right:0}.z-10{z-index:10}.col-span-1{grid-column:span 1/span 1}.mx-auto{margin-left:auto;margin-right:auto}.ml-3{margin-left:.75rem}.mt-2{margin-top:.5rem}.mt-8{margin-top:2rem}.mt-4{margin-top:1rem}.mt-1{margin-top:.25rem}.ml-4{margin-left:1rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-full{height:100%}.h-7{height:1.75rem}.h-6{height:1.5rem}.h-12{height:3rem}.h-2{height:.5rem}.w-screen{width:100vw}.w-6{width:1.5rem}.w-12{width:3rem}.w-full{width:100%}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-lg{max-width:32rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.25rem*var(--tw-space-y-reverse));margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-md{border-radius:.375rem}.rounded{border-radius:.25rem}.border-2{border-width:2px}.border{border-width:1px}.border-dashed{border-style:dashed}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}.border-transparent{border-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-indigo-100{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity))}.bg-indigo-500{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity))}.p-4{padding:1rem}.p-6{padding:1.5rem}.py-6{padding-bottom:1.5rem;padding-top:1.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-4{padding-bottom:1rem;padding-top:1rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.pl-10{padding-left:2.5rem}.pt-5{padding-top:1.25rem}.pb-6{padding-bottom:1.5rem}.pl-1{padding-left:.25rem}.text-center{text-align:center}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.text-indigo-200{--tw-text-opacity:1;color:rgb(199 210 254/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.shadow-sm,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}[x-cloak]{display:none!important}.focus-within\:outline-none:focus-within{outline:2px solid transparent;outline-offset:2px}.focus-within\:ring-2:focus-within{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-within\:ring-indigo-500:focus-within{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))}.focus-within\:ring-offset-2:focus-within{--tw-ring-offset-width:2px}.hover\:bg-indigo-700:hover{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity))}.hover\:text-indigo-300:hover{--tw-text-opacity:1;color:rgb(165 180 252/var(--tw-text-opacity))}.hover\:text-indigo-500:hover{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity))}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-white:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity))}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width:640px){.sm\:col-span-3{grid-column:span 3/span 3}.sm\:mt-px{margin-top:1px}.sm\:mt-0{margin-top:0}.sm\:max-w-xs{max-width:20rem}.sm\:p-6{padding:1.5rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pl-16{padding-left:4rem}.sm\:pt-2{padding-top:.5rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}} 2 | -------------------------------------------------------------------------------- /resources/dist/js/app.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var e,t={764:()=>{var e,t,n,r,i=!1,o=!1,a=[];function s(e){!function(e){a.includes(e)||a.push(e);o||i||(i=!0,queueMicrotask(c))}(e)}function l(e){let t=a.indexOf(e);-1!==t&&a.splice(t,1)}function c(){i=!1,o=!0;for(let e=0;e{(void 0===t||t.includes(n))&&(r.forEach((e=>e())),delete e._x_attributeCleanups[n])}))}var m=new MutationObserver(A),x=!1;function g(){m.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),x=!0}function y(){(b=b.concat(m.takeRecords())).length&&!w&&(w=!0,queueMicrotask((()=>{A(b),b.length=0,w=!1}))),m.disconnect(),x=!1}var b=[],w=!1;function E(e){if(!x)return e();y();let t=e();return g(),t}var O=!1,k=[];function A(e){if(O)return void(k=k.concat(e));let t=[],n=[],r=new Map,i=new Map;for(let o=0;o1===e.nodeType&&t.push(e))),e[o].removedNodes.forEach((e=>1===e.nodeType&&n.push(e)))),"attributes"===e[o].type)){let t=e[o].target,n=e[o].attributeName,a=e[o].oldValue,s=()=>{r.has(t)||r.set(t,[]),r.get(t).push({name:n,value:t.getAttribute(n)})},l=()=>{i.has(t)||i.set(t,[]),i.get(t).push(n)};t.hasAttribute(n)&&null===a?s():t.hasAttribute(n)?(l(),s()):l()}i.forEach(((e,t)=>{v(t,e)})),r.forEach(((e,t)=>{d.forEach((n=>n(t,e)))}));for(let e of n)if(!t.includes(e)&&(p.forEach((t=>t(e))),e._x_cleanups))for(;e._x_cleanups.length;)e._x_cleanups.pop()();t.forEach((e=>{e._x_ignoreSelf=!0,e._x_ignore=!0}));for(let e of t)n.includes(e)||e.isConnected&&(delete e._x_ignoreSelf,delete e._x_ignore,_.forEach((t=>t(e))),e._x_ignore=!0,e._x_ignoreSelf=!0);t.forEach((e=>{delete e._x_ignoreSelf,delete e._x_ignore})),t=null,n=null,r=null,i=null}function S(e){return M(j(e))}function C(e,t,n){return e._x_dataStack=[t,...j(n||e)],()=>{e._x_dataStack=e._x_dataStack.filter((e=>e!==t))}}function $(e,t){let n=e._x_dataStack[0];Object.entries(t).forEach((([e,t])=>{n[e]=t}))}function j(e){return e._x_dataStack?e._x_dataStack:"function"==typeof ShadowRoot&&e instanceof ShadowRoot?j(e.host):e.parentNode?j(e.parentNode):[]}function M(e){let t=new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap((e=>Object.keys(e))))),has:(t,n)=>e.some((e=>e.hasOwnProperty(n))),get:(n,r)=>(e.find((e=>{if(e.hasOwnProperty(r)){let n=Object.getOwnPropertyDescriptor(e,r);if(n.get&&n.get._x_alreadyBound||n.set&&n.set._x_alreadyBound)return!0;if((n.get||n.set)&&n.enumerable){let i=n.get,o=n.set,a=n;i=i&&i.bind(t),o=o&&o.bind(t),i&&(i._x_alreadyBound=!0),o&&(o._x_alreadyBound=!0),Object.defineProperty(e,r,{...a,get:i,set:o})}return!0}return!1}))||{})[r],set:(t,n,r)=>{let i=e.find((e=>e.hasOwnProperty(n)));return i?i[n]=r:e[e.length-1][n]=r,!0}});return t}function L(e){let t=(n,r="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach((([i,{value:o,enumerable:a}])=>{if(!1===a||void 0===o)return;let s=""===r?i:`${r}.${i}`;var l;"object"==typeof o&&null!==o&&o._x_interceptor?n[i]=o.initialize(e,s,i):"object"!=typeof(l=o)||Array.isArray(l)||null===l||o===n||o instanceof Element||t(o,s)}))};return t(e)}function P(e,t=(()=>{})){let n={initialValue:void 0,_x_interceptor:!0,initialize(t,n,r){return e(this.initialValue,(()=>function(e,t){return t.split(".").reduce(((e,t)=>e[t]),e)}(t,n)),(e=>N(t,n,e)),n,r)}};return t(n),e=>{if("object"==typeof e&&null!==e&&e._x_interceptor){let t=n.initialize.bind(n);n.initialize=(r,i,o)=>{let a=e.initialize(r,i,o);return n.initialValue=a,t(r,i,o)}}else n.initialValue=e;return n}}function N(e,t,n){if("string"==typeof t&&(t=t.split(".")),1!==t.length){if(0===t.length)throw error;return e[t[0]]||(e[t[0]]={}),N(e[t[0]],t.slice(1),n)}e[t[0]]=n}var R={};function T(e,t){R[e]=t}function z(e,t){return Object.entries(R).forEach((([n,r])=>{Object.defineProperty(e,`$${n}`,{get(){let[e,n]=ne(t);return e={interceptor:P,...e},h(t,n),r(t,e)},enumerable:!1})})),e}function I(e,t,n,...r){try{return n(...r)}catch(n){D(n,e,t)}}function D(e,t,n){Object.assign(e,{el:t,expression:n}),console.warn(`Alpine Expression Error: ${e.message}\n\n${n?'Expression: "'+n+'"\n\n':""}`,t),setTimeout((()=>{throw e}),0)}var q=!0;function W(e,t,n={}){let r;return B(e,t)((e=>r=e),n),r}function B(...e){return F(...e)}var F=V;function V(e,t){let n={};z(n,e);let r=[n,...j(e)];if("function"==typeof t)return function(e,t){return(n=(()=>{}),{scope:r={},params:i=[]}={})=>{U(n,t.apply(M([r,...e]),i))}}(r,t);let i=function(e,t,n){let r=function(e,t){if(K[e])return K[e];let n=Object.getPrototypeOf((async function(){})).constructor,r=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(() => { ${e} })()`:e;let i=(()=>{try{return new n(["__self","scope"],`with (scope) { __self.result = ${r} }; __self.finished = true; return __self.result;`)}catch(n){return D(n,t,e),Promise.resolve()}})();return K[e]=i,i}(t,n);return(i=(()=>{}),{scope:o={},params:a=[]}={})=>{r.result=void 0,r.finished=!1;let s=M([o,...e]);if("function"==typeof r){let e=r(r,s).catch((e=>D(e,n,t)));r.finished?(U(i,r.result,s,a,n),r.result=void 0):e.then((e=>{U(i,e,s,a,n)})).catch((e=>D(e,n,t))).finally((()=>r.result=void 0))}}}(r,t,e);return I.bind(null,e,t,i)}var K={};function U(e,t,n,r,i){if(q&&"function"==typeof t){let o=t.apply(n,r);o instanceof Promise?o.then((t=>U(e,t,n,r))).catch((e=>D(e,i,t))):e(o)}else e(t)}var H="x-";function Z(e=""){return H+e}var Y={};function J(e,t){Y[e]=t}function G(e,t,n){if(t=Array.from(t),e._x_virtualDirectives){let n=Object.entries(e._x_virtualDirectives).map((([e,t])=>({name:e,value:t}))),r=Q(n);n=n.map((e=>r.find((t=>t.name===e.name))?{name:`x-bind:${e.name}`,value:`"${e.value}"`}:e)),t=t.concat(n)}let r={},i=t.map(ie(((e,t)=>r[e]=t))).filter(se).map(function(e,t){return({name:n,value:r})=>{let i=n.match(le()),o=n.match(/:([a-zA-Z0-9\-:]+)/),a=n.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],s=t||e[n]||n;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:a.map((e=>e.replace(".",""))),expression:r,original:s}}}(r,n)).sort(fe);return i.map((t=>function(e,t){let n=()=>{},r=Y[t.type]||n,[i,o]=ne(e);!function(e,t,n){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(n)}(e,t.original,o);let a=()=>{e._x_ignore||e._x_ignoreSelf||(r.inline&&r.inline(e,t,i),r=r.bind(r,e,t,i),X?ee.get(te).push(r):r())};return a.runCleanups=o,a}(e,t)))}function Q(e){return Array.from(e).map(ie()).filter((e=>!se(e)))}var X=!1,ee=new Map,te=Symbol();function ne(e){let r=[],[i,o]=function(e){let r=()=>{};return[i=>{let o=t(i);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach((e=>e()))}),e._x_effects.add(o),r=()=>{void 0!==o&&(e._x_effects.delete(o),n(o))},o},()=>{r()}]}(e);r.push(o);return[{Alpine:Ze,effect:i,cleanup:e=>r.push(e),evaluateLater:B.bind(B,e),evaluate:W.bind(W,e)},()=>r.forEach((e=>e()))]}var re=(e,t)=>({name:n,value:r})=>(n.startsWith(e)&&(n=n.replace(e,t)),{name:n,value:r});function ie(e=(()=>{})){return({name:t,value:n})=>{let{name:r,value:i}=oe.reduce(((e,t)=>t(e)),{name:t,value:n});return r!==t&&e(r,t),{name:r,value:i}}}var oe=[];function ae(e){oe.push(e)}function se({name:e}){return le().test(e)}var le=()=>new RegExp(`^${H}([^:^.]+)\\b`);var ce="DEFAULT",ue=["ignore","ref","data","id","bind","init","for","mask","model","modelable","transition","show","if",ce,"teleport"];function fe(e,t){let n=-1===ue.indexOf(e.type)?ce:e.type,r=-1===ue.indexOf(t.type)?ce:t.type;return ue.indexOf(n)-ue.indexOf(r)}function de(e,t,n={}){e.dispatchEvent(new CustomEvent(t,{detail:n,bubbles:!0,composed:!0,cancelable:!0}))}var pe=[],_e=!1;function he(e=(()=>{})){return queueMicrotask((()=>{_e||setTimeout((()=>{ve()}))})),new Promise((t=>{pe.push((()=>{e(),t()}))}))}function ve(){for(_e=!1;pe.length;)pe.shift()()}function me(e,t){if("function"==typeof ShadowRoot&&e instanceof ShadowRoot)return void Array.from(e.children).forEach((e=>me(e,t)));let n=!1;if(t(e,(()=>n=!0)),n)return;let r=e.firstElementChild;for(;r;)me(r,t),r=r.nextElementSibling}function xe(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var ge=[],ye=[];function be(){return ge.map((e=>e()))}function we(){return ge.concat(ye).map((e=>e()))}function Ee(e){ge.push(e)}function Oe(e){ye.push(e)}function ke(e,t=!1){return Ae(e,(e=>{if((t?we():be()).some((t=>e.matches(t))))return!0}))}function Ae(e,t){if(e){if(t(e))return e;if(e._x_teleportBack&&(e=e._x_teleportBack),e.parentElement)return Ae(e.parentElement,t)}}function Se(e,t=me){!function(e){X=!0;let t=Symbol();te=t,ee.set(t,[]);let n=()=>{for(;ee.get(t).length;)ee.get(t).shift()();ee.delete(t)};e(n),X=!1,n()}((()=>{t(e,((e,t)=>{G(e,e.attributes).forEach((e=>e())),e._x_ignore&&t()}))}))}function Ce(e,t){return Array.isArray(t)?$e(e,t.join(" ")):"object"==typeof t&&null!==t?function(e,t){let n=e=>e.split(" ").filter(Boolean),r=Object.entries(t).flatMap((([e,t])=>!!t&&n(e))).filter(Boolean),i=Object.entries(t).flatMap((([e,t])=>!t&&n(e))).filter(Boolean),o=[],a=[];return i.forEach((t=>{e.classList.contains(t)&&(e.classList.remove(t),a.push(t))})),r.forEach((t=>{e.classList.contains(t)||(e.classList.add(t),o.push(t))})),()=>{a.forEach((t=>e.classList.add(t))),o.forEach((t=>e.classList.remove(t)))}}(e,t):"function"==typeof t?Ce(e,t()):$e(e,t)}function $e(e,t){return t=!0===t?t="":t||"",n=t.split(" ").filter((t=>!e.classList.contains(t))).filter(Boolean),e.classList.add(...n),()=>{e.classList.remove(...n)};var n}function je(e,t){return"object"==typeof t&&null!==t?function(e,t){let n={};return Object.entries(t).forEach((([t,r])=>{n[t]=e.style[t],t.startsWith("--")||(t=t.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()),e.style.setProperty(t,r)})),setTimeout((()=>{0===e.style.length&&e.removeAttribute("style")})),()=>{je(e,n)}}(e,t):function(e,t){let n=e.getAttribute("style",t);return e.setAttribute("style",t),()=>{e.setAttribute("style",n||"")}}(e,t)}function Me(e,t=(()=>{})){let n=!1;return function(){n?t.apply(this,arguments):(n=!0,e.apply(this,arguments))}}function Le(e,t,n={}){e._x_transition||(e._x_transition={enter:{during:n,start:n,end:n},leave:{during:n,start:n,end:n},in(n=(()=>{}),r=(()=>{})){Ne(e,t,{during:this.enter.during,start:this.enter.start,end:this.enter.end},n,r)},out(n=(()=>{}),r=(()=>{})){Ne(e,t,{during:this.leave.during,start:this.leave.start,end:this.leave.end},n,r)}})}function Pe(e){let t=e.parentNode;if(t)return t._x_hidePromise?t:Pe(t)}function Ne(e,t,{during:n,start:r,end:i}={},o=(()=>{}),a=(()=>{})){if(e._x_transitioning&&e._x_transitioning.cancel(),0===Object.keys(n).length&&0===Object.keys(r).length&&0===Object.keys(i).length)return o(),void a();let s,l,c;!function(e,t){let n,r,i,o=Me((()=>{E((()=>{n=!0,r||t.before(),i||(t.end(),ve()),t.after(),e.isConnected&&t.cleanup(),delete e._x_transitioning}))}));e._x_transitioning={beforeCancels:[],beforeCancel(e){this.beforeCancels.push(e)},cancel:Me((function(){for(;this.beforeCancels.length;)this.beforeCancels.shift()();o()})),finish:o},E((()=>{t.start(),t.during()})),_e=!0,requestAnimationFrame((()=>{if(n)return;let o=1e3*Number(getComputedStyle(e).transitionDuration.replace(/,.*/,"").replace("s","")),a=1e3*Number(getComputedStyle(e).transitionDelay.replace(/,.*/,"").replace("s",""));0===o&&(o=1e3*Number(getComputedStyle(e).animationDuration.replace("s",""))),E((()=>{t.before()})),r=!0,requestAnimationFrame((()=>{n||(E((()=>{t.end()})),ve(),setTimeout(e._x_transitioning.finish,o+a),i=!0)}))}))}(e,{start(){s=t(e,r)},during(){l=t(e,n)},before:o,end(){s(),c=t(e,i)},after:a,cleanup(){l(),c()}})}function Re(e,t,n){if(-1===e.indexOf(t))return n;const r=e[e.indexOf(t)+1];if(!r)return n;if("scale"===t&&isNaN(r))return n;if("duration"===t){let e=r.match(/([0-9]+)ms/);if(e)return e[1]}return"origin"===t&&["top","right","left","center","bottom"].includes(e[e.indexOf(t)+2])?[r,e[e.indexOf(t)+2]].join(" "):r}J("transition",((e,{value:t,modifiers:n,expression:r},{evaluate:i})=>{"function"==typeof r&&(r=i(r)),r?function(e,t,n){Le(e,Ce,""),{enter:t=>{e._x_transition.enter.during=t},"enter-start":t=>{e._x_transition.enter.start=t},"enter-end":t=>{e._x_transition.enter.end=t},leave:t=>{e._x_transition.leave.during=t},"leave-start":t=>{e._x_transition.leave.start=t},"leave-end":t=>{e._x_transition.leave.end=t}}[n](t)}(e,r,t):function(e,t,n){Le(e,je);let r=!t.includes("in")&&!t.includes("out")&&!n,i=r||t.includes("in")||["enter"].includes(n),o=r||t.includes("out")||["leave"].includes(n);t.includes("in")&&!r&&(t=t.filter(((e,n)=>nn>t.indexOf("out"))));let a=!t.includes("opacity")&&!t.includes("scale"),s=a||t.includes("opacity"),l=a||t.includes("scale"),c=s?0:1,u=l?Re(t,"scale",95)/100:1,f=Re(t,"delay",0),d=Re(t,"origin","center"),p="opacity, transform",_=Re(t,"duration",150)/1e3,h=Re(t,"duration",75)/1e3,v="cubic-bezier(0.4, 0.0, 0.2, 1)";i&&(e._x_transition.enter.during={transformOrigin:d,transitionDelay:f,transitionProperty:p,transitionDuration:`${_}s`,transitionTimingFunction:v},e._x_transition.enter.start={opacity:c,transform:`scale(${u})`},e._x_transition.enter.end={opacity:1,transform:"scale(1)"});o&&(e._x_transition.leave.during={transformOrigin:d,transitionDelay:f,transitionProperty:p,transitionDuration:`${h}s`,transitionTimingFunction:v},e._x_transition.leave.start={opacity:1,transform:"scale(1)"},e._x_transition.leave.end={opacity:c,transform:`scale(${u})`})}(e,n,t)})),window.Element.prototype._x_toggleAndCascadeWithTransitions=function(e,t,n,r){const i="visible"===document.visibilityState?requestAnimationFrame:setTimeout;let o=()=>i(n);t?e._x_transition&&(e._x_transition.enter||e._x_transition.leave)?e._x_transition.enter&&(Object.entries(e._x_transition.enter.during).length||Object.entries(e._x_transition.enter.start).length||Object.entries(e._x_transition.enter.end).length)?e._x_transition.in(n):o():e._x_transition?e._x_transition.in(n):o():(e._x_hidePromise=e._x_transition?new Promise(((t,n)=>{e._x_transition.out((()=>{}),(()=>t(r))),e._x_transitioning.beforeCancel((()=>n({isFromCancelledTransition:!0})))})):Promise.resolve(r),queueMicrotask((()=>{let t=Pe(e);t?(t._x_hideChildren||(t._x_hideChildren=[]),t._x_hideChildren.push(e)):i((()=>{let t=e=>{let n=Promise.all([e._x_hidePromise,...(e._x_hideChildren||[]).map(t)]).then((([e])=>e()));return delete e._x_hidePromise,delete e._x_hideChildren,n};t(e).catch((e=>{if(!e.isFromCancelledTransition)throw e}))}))})))};var Te=!1;function ze(e,t=(()=>{})){return(...n)=>Te?t(...n):e(...n)}function Ie(t,n,r,i=[]){switch(t._x_bindings||(t._x_bindings=e({})),t._x_bindings[n]=r,n=i.includes("camel")?n.toLowerCase().replace(/-(\w)/g,((e,t)=>t.toUpperCase())):n){case"value":!function(e,t){if("radio"===e.type)void 0===e.attributes.value&&(e.value=t),window.fromModel&&(e.checked=De(e.value,t));else if("checkbox"===e.type)Number.isInteger(t)?e.value=t:Number.isInteger(t)||Array.isArray(t)||"boolean"==typeof t||[null,void 0].includes(t)?Array.isArray(t)?e.checked=t.some((t=>De(t,e.value))):e.checked=!!t:e.value=String(t);else if("SELECT"===e.tagName)!function(e,t){const n=[].concat(t).map((e=>e+""));Array.from(e.options).forEach((e=>{e.selected=n.includes(e.value)}))}(e,t);else{if(e.value===t)return;e.value=t}}(t,r);break;case"style":!function(e,t){e._x_undoAddedStyles&&e._x_undoAddedStyles();e._x_undoAddedStyles=je(e,t)}(t,r);break;case"class":!function(e,t){e._x_undoAddedClasses&&e._x_undoAddedClasses();e._x_undoAddedClasses=Ce(e,t)}(t,r);break;default:!function(e,t,n){[null,void 0,!1].includes(n)&&function(e){return!["aria-pressed","aria-checked","aria-expanded","aria-selected"].includes(e)}(t)?e.removeAttribute(t):(qe(t)&&(n=t),function(e,t,n){e.getAttribute(t)!=n&&e.setAttribute(t,n)}(e,t,n))}(t,n,r)}}function De(e,t){return e==t}function qe(e){return["disabled","checked","required","readonly","hidden","open","selected","autofocus","itemscope","multiple","novalidate","allowfullscreen","allowpaymentrequest","formnovalidate","autoplay","controls","loop","muted","playsinline","default","ismap","reversed","async","defer","nomodule"].includes(e)}function We(e,t){var n;return function(){var r=this,i=arguments,o=function(){n=null,e.apply(r,i)};clearTimeout(n),n=setTimeout(o,t)}}function Be(e,t){let n;return function(){let r=this,i=arguments;n||(e.apply(r,i),n=!0,setTimeout((()=>n=!1),t))}}var Fe={},Ve=!1;var Ke={};function Ue(e,t,n){let r=[];for(;r.length;)r.pop()();let i=Object.entries(t).map((([e,t])=>({name:e,value:t}))),o=Q(i);i=i.map((e=>o.find((t=>t.name===e.name))?{name:`x-bind:${e.name}`,value:`"${e.value}"`}:e)),G(e,i,n).map((e=>{r.push(e.runCleanups),e()}))}var He={};var Ze={get reactive(){return e},get release(){return n},get effect(){return t},get raw(){return r},version:"3.10.3",flushAndStopDeferringMutations:function(){O=!1,A(k),k=[]},dontAutoEvaluateFunctions:function(e){let t=q;q=!1,e(),q=t},disableEffectScheduling:function(e){u=!1,e(),u=!0},setReactivityEngine:function(i){e=i.reactive,n=i.release,t=e=>i.effect(e,{scheduler:e=>{u?s(e):e()}}),r=i.raw},closestDataStack:j,skipDuringClone:ze,addRootSelector:Ee,addInitSelector:Oe,addScopeToNode:C,deferMutations:function(){O=!0},mapAttributes:ae,evaluateLater:B,setEvaluator:function(e){F=e},mergeProxies:M,findClosest:Ae,closestRoot:ke,interceptor:P,transition:Ne,setStyles:je,mutateDom:E,directive:J,throttle:Be,debounce:We,evaluate:W,initTree:Se,nextTick:he,prefixed:Z,prefix:function(e){H=e},plugin:function(e){e(Ze)},magic:T,store:function(t,n){if(Ve||(Fe=e(Fe),Ve=!0),void 0===n)return Fe[t];Fe[t]=n,"object"==typeof n&&null!==n&&n.hasOwnProperty("init")&&"function"==typeof n.init&&Fe[t].init(),L(Fe[t])},start:function(){var e;document.body||xe("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's ` 28 | HTML; 29 | } 30 | 31 | /** 32 | * Get Tailwind Style Path 33 | */ 34 | protected static function getTailwindStyle(): string 35 | { 36 | return <<<'HTML' 37 | 38 | HTML; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/LaravelCsvManager.php: -------------------------------------------------------------------------------- 1 | name('laravel-csv') 24 | ->hasConfigFile('laravel_csv') 25 | ->hasAssets() 26 | ->hasViews('laravel-csv') 27 | ->hasMigration('create_csv_imports_table'); 28 | } 29 | 30 | public function bootingPackage() 31 | { 32 | $this->registerLivewireComponents(); 33 | 34 | $this->configureComponents(); 35 | 36 | $this->registerBladeDirectives(); 37 | } 38 | 39 | public function registeringPackage() 40 | { 41 | $this->app->bind('laravel-csv', fn () => new LaravelCsvManager); 42 | } 43 | 44 | /** 45 | * Configure Laravel CSV Blade components 46 | */ 47 | protected function configureComponents(): void 48 | { 49 | $this->callAfterResolving(BladeCompiler::class, function () { 50 | $this->registerComponent('button'); 51 | }); 52 | } 53 | 54 | /** 55 | * Register livewire components 56 | */ 57 | protected function registerLivewireComponents(): void 58 | { 59 | /** @phpstan-ignore-next-line */ 60 | Livewire::component('csv-importer', CsvImporter::class); 61 | 62 | /** @phpstan-ignore-next-line */ 63 | Livewire::component('handle-imports', HandleImports::class); 64 | } 65 | 66 | /** 67 | * Register given component. 68 | */ 69 | protected function registerComponent(string $component): void 70 | { 71 | Blade::component('laravel-csv::components.'.$component, 'csv-'.$component); 72 | } 73 | 74 | /** 75 | * Register laravel CSV blade directives 76 | * 77 | * @return void 78 | */ 79 | protected function registerBladeDirectives() 80 | { 81 | Blade::directive('csvStyles', [LaravelCsvDirectives::class, 'csvStyles']); 82 | Blade::directive('csvScripts', [LaravelCsvDirectives::class, 'csvScripts']); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Models/Import.php: -------------------------------------------------------------------------------- 1 | |bool 37 | */ 38 | protected $guarded = []; 39 | } 40 | -------------------------------------------------------------------------------- /src/Scopes/ImportScope.php: -------------------------------------------------------------------------------- 1 | whereNotNull('completed_at'); 16 | } 17 | 18 | /** 19 | * Not Completed Status Scope 20 | */ 21 | public function scopeUnCompleted(Builder $builder): Builder 22 | { 23 | return $builder->whereNull('completed_at'); 24 | } 25 | 26 | /** 27 | * Get the percentage of the model completion 28 | */ 29 | public function percentageComplete(): int|float 30 | { 31 | return floor(($this->processed_rows / $this->total_rows) * 100); 32 | } 33 | 34 | /** 35 | * Fetch imports based on the given model 36 | */ 37 | public function scopeForModel(Builder $builder, string $model): Builder 38 | { 39 | return $builder->where('model', $model); 40 | } 41 | 42 | /** 43 | * Fetch imports on the user id 44 | */ 45 | public function scopeForUser(Builder $builder, int $user): Builder 46 | { 47 | return $builder->where('user_id', $user); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Utilities/ChunkIterator.php: -------------------------------------------------------------------------------- 1 | iterator = $iterator; 28 | $this->chunkSize = $chunkSize; 29 | } 30 | 31 | /** 32 | * Chunk the given data 33 | */ 34 | public function get(): Generator 35 | { 36 | $chunk = []; 37 | 38 | for ($i = 0; $this->iterator->valid(); $i++) { 39 | // store the current record into the $chunk array 40 | $chunk[] = $this->iterator->current(); 41 | 42 | // move on to the next record 43 | $this->iterator->next(); 44 | 45 | // if the number of element on the $chunk variable 46 | // met the chunk size, we yield the result and start 47 | // over, to the next elements 48 | if (count($chunk) == $this->chunkSize) { 49 | yield $chunk; 50 | $chunk = []; 51 | } 52 | } 53 | 54 | // if the chunk size is positive, we yield the results 55 | if (count($chunk) > 0) { 56 | yield $chunk; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 |