diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..b0ee5d8 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +Please read and understand the contribution guide before creating an issue or pull request. + +## Etiquette + +This project is open source, and as such, the maintainers give their free time to build and maintain the source code +held within. They make the code freely available in the hope that it will be of use to other developers. It would be +extremely unfair for them to suffer abuse or anger for their hard work. + +Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the +world that developers are civilized and selfless people. + +It's the duty of the maintainer to ensure that all submissions to the project are of sufficient +quality to benefit the project. Many developers have different skills, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. + +## Viability + +When requesting or submitting new features, first consider whether it might be useful to others. Open +source projects are used by many developers, who may have entirely different needs to your own. Think about +whether or not your feature is likely to be used by other users of the project. + +## Procedure + +Before filing an issue: + +- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. +- Check to make sure your feature suggestion isn't already present within the project. +- Check the pull requests tab to ensure that the bug doesn't have a fix in progress. +- Check the pull requests tab to ensure that the feature isn't already in progress. + +Before submitting a pull request: + +- Check the codebase to ensure that your feature doesn't already exist. +- Check the pull requests to ensure that another person hasn't already submitted the feature or fix. + +## Requirements + +If the project maintainer has any additional requirements, you will find them listed here. + +- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer). + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. + +- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. + +**Happy coding**! diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..892ba05 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [3x1io] diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..8fa85ce --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,66 @@ +name: Bug Report +description: Report an Issue or Bug with the Package +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + We're sorry to hear you have a problem. Can you help us solve it by providing the following details. + - type: textarea + id: what-happened + attributes: + label: What happened? + description: What did you expect to happen? + placeholder: I cannot currently do X thing because when I do, it breaks X thing. + validations: + required: true + - type: textarea + id: how-to-reproduce + attributes: + label: How to reproduce the bug + description: How did this occur, please add any config values used and provide a set of reliable steps if possible. + placeholder: When I do X I see Y. + validations: + required: true + - type: input + id: package-version + attributes: + label: Package Version + description: What version of our Package are you running? Please be as specific as possible + placeholder: 2.0.0 + validations: + required: true + - type: input + id: php-version + attributes: + label: PHP Version + description: What version of PHP are you running? Please be as specific as possible + placeholder: 8.2.0 + validations: + required: true + - type: input + id: laravel-version + attributes: + label: Laravel Version + description: What version of Laravel are you running? Please be as specific as possible + placeholder: 9.0.0 + validations: + required: true + - type: dropdown + id: operating-systems + attributes: + label: Which operating systems does with happen with? + description: You may select more than one. + multiple: true + options: + - macOS + - Windows + - Linux + - type: textarea + id: notes + attributes: + label: Notes + description: Use this field to provide any other notes that you feel might be relevant to the issue. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 0000000..cb37ffd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Ask a question + url: https://github.com/tomatophp/filament-form-builder/discussions/new?category=q-a + about: Ask the community for help + - name: Request a feature + url: https://github.com/tomatophp/filament-form-builder/discussions/new?category=ideas + about: Share ideas for new features + - name: Report a security issue + url: https://github.com/tomatophp/filament-form-builder/security/policy + about: Learn how to notify us for sensitive bugs diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..b2490a9 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,3 @@ +# Security Policy + +If you discover any security related issues, please email info@3x1.io instead of using the issue tracker. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0bc378d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + labels: + - "dependencies" diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..27c23a4 --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,32 @@ +name: dependabot-auto-merge +on: pull_request_target + +permissions: + pull-requests: write + contents: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2.2.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Auto-merge Dependabot PRs for semver-minor updates + if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}} + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Auto-merge Dependabot PRs for semver-patch updates + if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}} + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/fix-php-code-styling.yml b/.github/workflows/fix-php-code-styling.yml new file mode 100644 index 0000000..e71024d --- /dev/null +++ b/.github/workflows/fix-php-code-styling.yml @@ -0,0 +1,30 @@ +name: 'PHP Code Styling' + +on: + workflow_dispatch: + push: + branches: + - master + paths: + - '**.php' + +permissions: + contents: write + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + + - name: Fix PHP code style issues + uses: aglipanci/laravel-pint-action@v2 + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "Format Code" + commit_user_name: 'GitHub Actions' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..14a3ed3 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,68 @@ +name: "Tests" + +on: + workflow_dispatch: + push: + branches: + - master + paths: + - '**.php' + pull_request: + types: + - opened + - synchronize + branches: + - master + paths: + - '**.php' + - '.github/workflows/tests.yml' + - 'phpunit.xml.dist' + - 'composer.json' + - 'composer.lock' + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: [ubuntu-latest] + php: [8.3, 8.2] + laravel: [11.*] + stability: [prefer-stable] + include: + - laravel: 11.* + testbench: 9.* + carbon: 3.* + collision: 8.* + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Cache Dependencies + uses: actions/cache@v4 + with: + path: ~/.composer/cache/files + key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo + coverage: none + + - name: Install Dependencies + run: | + echo "::add-matcher::${{ runner.tool_cache }}/php.json" + echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Install Dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nesbot/carbon:${{ matrix.carbon }}" "nunomaduro/collision:${{ matrix.collision }}" --no-interaction --no-update + composer update --${{ matrix.stability }} --prefer-dist --no-interaction + composer db + + - name: Execute tests + run: vendor/bin/pest diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..4123157 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,37 @@ +notPath('bootstrap/*') + ->notPath('storage/*') + ->notPath('resources/view/mail/*') + ->in([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->name('*.php') + ->notName('*.blade.php') + ->ignoreDotFiles(true) + ->ignoreVCS(true); + +return (new PhpCsFixer\Config()) + ->setRules([ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'ordered_imports' => ['sort_algorithm' => 'alpha'], + 'no_unused_imports' => true, + 'not_operator_with_successor_space' => true, + 'trailing_comma_in_multiline' => true, + 'phpdoc_scalar' => true, + 'unary_operator_spaces' => true, + 'binary_operator_spaces' => true, + 'blank_line_before_statement' => [ + 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], + ], + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_var_without_name' => true, + 'method_argument_space' => [ + 'on_multiline' => 'ensure_fully_multiline', + 'keep_multiple_spaces_after_comma' => true, + ] + ]) + ->setFinder($finder); diff --git a/3x1io-tomato-form-builder.md b/3x1io-tomato-form-builder.md new file mode 100644 index 0000000..0c4cb8a --- /dev/null +++ b/3x1io-tomato-form-builder.md @@ -0,0 +1,14 @@ +--- +name: Form Builder +slug: 3x1io-tomato-form-builder +author_slug: 3x1io +categories: [developer-tools] +description: Manage your forms using database and drop/drag component to build the form with Livewire component support for FilamentPHP +discord_url: +docs_url: https://raw.githubusercontent.com/tomatophp/filament-form-builder/master/README.md +github_repository: tomatophp/filament-form-builder +has_dark_theme: true +has_translations: true +versions: [3] +publish_date: 2024-11-27 +--- diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e0a5086 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# V1.0.0 + +First release of the package diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..18c9147 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..e66364e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4059786 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +![Screenshot](https://raw.githubusercontent.com/tomatophp/filament-form-builder/master/art/screenshot.jpg) + +# Filament form builder + +[![Latest Stable Version](https://poser.pugx.org/tomatophp/filament-form-builder/version.svg)](https://packagist.org/packages/tomatophp/filament-form-builder) +[![License](https://poser.pugx.org/tomatophp/filament-form-builder/license.svg)](https://packagist.org/packages/tomatophp/filament-form-builder) +[![Downloads](https://poser.pugx.org/tomatophp/filament-form-builder/d/total.svg)](https://packagist.org/packages/tomatophp/filament-form-builder) + +Manage your forms using database and drop/drag component to build the form with Livewire component support for FilamentPHP + +## Installation + +```bash +composer require tomatophp/filament-form-builder +``` +after install your package please run this command + +```bash +php artisan filament-form-builder:install +``` + +finally register the plugin on `/app/Providers/Filament/AdminPanelProvider.php` + +```php +->plugin(\TomatoPHP\FilamentFormBuilder\FilamentFormBuilderPlugin::make()) +``` + + +## Publish Assets + +you can publish config file by use this command + +```bash +php artisan vendor:publish --tag="filament-form-builder-config" +``` + +you can publish views file by use this command + +```bash +php artisan vendor:publish --tag="filament-form-builder-views" +``` + +you can publish languages file by use this command + +```bash +php artisan vendor:publish --tag="filament-form-builder-lang" +``` + +you can publish migrations file by use this command + +```bash +php artisan vendor:publish --tag="filament-form-builder-migrations" +``` + +## Changelog + +Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. + +## Security + +Please see [SECURITY](SECURITY.md) for more information about security. + +## Credits + +- [Fady Mondy](mailto:info@3x1.io) + +## License + +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..b2490a9 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,3 @@ +# Security Policy + +If you discover any security related issues, please email info@3x1.io instead of using the issue tracker. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8788718 --- /dev/null +++ b/composer.json @@ -0,0 +1,68 @@ +{ + "name": "tomatophp/filament-form-builder", + "type": "library", + "description": "Manage your forms using database and drop/drag component to build the form with Livewire component support for FilamentPHP", + "keywords": [ + "php", + "laravel", + "template" + ], + "license": "MIT", + "autoload": { + "psr-4": { + "TomatoPHP\\FilamentFormBuilder\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "TomatoPHP\\FilamentFormBuilder\\Tests\\": "tests/src/", + "TomatoPHP\\FilamentFormBuilder\\Tests\\Database\\Factories\\": "tests/database/factories" + } + }, + "extra": { + "laravel": { + "providers": [ + "TomatoPHP\\FilamentFormBuilder\\FilamentFormBuilderServiceProvider" + ] + } + }, + "authors": [ + { + "name": "Fady Mondy", + "email": "info@3x1.io" + } + ], + "config": { + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true, + "phpstan/extension-installer": true + } + }, + "scripts": { + "testbench": "vendor/bin/testbench package:discover --ansi", + "db": "vendor/bin/testbench package:create-sqlite-db && vendor/bin/testbench migrate", + "analyse": "vendor/bin/phpstan analyse src tests", + "test": "vendor/bin/pest", + "test-coverage": "vendor/bin/pest --coverage", + "format": "vendor/bin/pint" + }, + "require": { + "php": "^8.1|^8.2", + "tomatophp/console-helpers": "^1.1", + "filament/filament": "^3.2" + }, + "require-dev": { + "laravel/pint": "^1.18", + "livewire/livewire": "^2.10|^3.0", + "nunomaduro/larastan": "^2.9", + "orchestra/testbench": "^9.5", + "pestphp/pest": "^2.36", + "pestphp/pest-plugin-laravel": "^2.4", + "pestphp/pest-plugin-livewire": "^2.1", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan-deprecation-rules": "^1.2", + "phpstan/phpstan-phpunit": "^1.4" + }, + "version": "v1.0.0" +} diff --git a/config/.gitkeep b/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/config/filament-form-builder.php b/config/filament-form-builder.php new file mode 100644 index 0000000..f5bf951 --- /dev/null +++ b/config/filament-form-builder.php @@ -0,0 +1,5 @@ +id(); + + //Set Type from page/modal/slideover + $table->string('type')->default('page')->nullable(); + + //Set Name And Key + $table->json('title')->nullable(); + $table->json('description')->nullable(); + $table->string('key')->unique()->index(); + + //Set Form Action + $table->string('endpoint')->default('/')->nullable(); + $table->string('method')->default('POST')->nullable(); + + //Form Control + $table->boolean('is_active')->default(0)->nullable(); + + $table->timestamps(); + $table->softDeletes(); + }); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('forms'); + } +}; diff --git a/database/migrations/2023_01_19_115147_create_form_options_table.php b/database/migrations/2023_01_19_115147_create_form_options_table.php new file mode 100644 index 0000000..f3eaed3 --- /dev/null +++ b/database/migrations/2023_01_19_115147_create_form_options_table.php @@ -0,0 +1,79 @@ +id(); + + $table->foreignId('form_id')->constrained('forms')->onDelete('cascade'); + + //Set type of filed like text, email, number, select, checkbox, radio, textarea, file, date, time, datetime, password + $table->string('type')->default('text')->nullable(); + + //Set Name and Key for this filed + $table->json('label')->nullable(); + $table->json('placeholder')->nullable(); + $table->json('hint')->nullable(); + $table->string('name')->index(); + $table->string('group')->nullable(); + + //Set Default value for this field + $table->json('default')->nullable(); + + $table->integer('order')->default(0)->nullable(); + + //Is Filed Required? + $table->boolean('is_required')->default(0)->nullable(); + $table->boolean('is_multi')->default(0)->nullable(); + $table->json('required_message')->nullable(); + + //Is Field Reactive? + $table->boolean('is_reactive')->default(0)->nullable(); + $table->string('reactive_field')->nullable(); + $table->string('reactive_where')->nullable(); + + //Is Table Select? + $table->boolean('is_relation')->default(0)->nullable(); + $table->string('relation_name')->nullable(); + $table->string('relation_column')->nullable(); + + //Check if Field is Has options like Select + $table->boolean('has_options')->default(0)->nullable(); + $table->json('options')->nullable(); + + //Valdations + $table->boolean('has_validation')->default(0)->nullable(); + $table->json('validation')->nullable(); + + //For Meta Injection + $table->json('meta')->nullable(); + + $table->timestamps(); + }); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::disableForeignKeyConstraints(); + Schema::dropIfExists('form_options'); + Schema::enableForeignKeyConstraints(); + } +}; diff --git a/database/migrations/2023_01_19_115148_create_form_requests_table.php b/database/migrations/2023_01_19_115148_create_form_requests_table.php new file mode 100644 index 0000000..541a54a --- /dev/null +++ b/database/migrations/2023_01_19_115148_create_form_requests_table.php @@ -0,0 +1,48 @@ +id(); + + //Morph + $table->string('model_type')->nullable(); + $table->unsignedBigInteger('model_id')->nullable(); + + //Morph Service + $table->string('service_type')->nullable(); + $table->unsignedBigInteger('service_id')->nullable(); + + $table->foreignId('form_id')->constrained('forms'); + $table->string('status')->default('pending')->nullable(); + $table->json('payload')->nullable(); + + $table->text('description')->nullable(); + $table->date('date')->nullable(); + $table->time('time')->nullable(); + + $table->timestamps(); + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::disableForeignKeyConstraints(); + Schema::dropIfExists('form_requests'); + Schema::enableForeignKeyConstraints(); + } +}; diff --git a/database/migrations/2023_01_19_115148_update_form_options_table.php b/database/migrations/2023_01_19_115148_update_form_options_table.php new file mode 100644 index 0000000..357fdc8 --- /dev/null +++ b/database/migrations/2023_01_19_115148_update_form_options_table.php @@ -0,0 +1,37 @@ +foreignId('sub_form')->nullable()->constrained('forms'); + }); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + if(config('filament-cms.features.forms')) { + Schema::table('form_options', function (Blueprint $table) { + $table->dropForeign(['sub_form']); + $table->dropColumn('sub_form'); + }); + } + } +}; diff --git a/database/migrations/2023_01_19_115149_create_form_request_metas_table.php b/database/migrations/2023_01_19_115149_create_form_request_metas_table.php new file mode 100644 index 0000000..7e1a0d0 --- /dev/null +++ b/database/migrations/2023_01_19_115149_create_form_request_metas_table.php @@ -0,0 +1,38 @@ +id(); + + $table->unsignedBigInteger('model_id')->nullable(); + $table->string('model_type')->nullable(); + + $table->foreignId('form_request_id')->references('id')->on('form_requests')->onDelete('cascade'); + $table->string('key')->index(); + $table->json('value')->nullable(); + $table->timestamps(); + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::disableForeignKeyConstraints(); + Schema::dropIfExists('form_request_metas'); + Schema::enableForeignKeyConstraints(); + } +}; diff --git a/module.json b/module.json new file mode 100644 index 0000000..4de0b70 --- /dev/null +++ b/module.json @@ -0,0 +1,29 @@ +{ + "name": "FilamentFormBuilder", + "alias": "filament-form-builder", + "description": { + "ar": "Manage your forms using database and drop/drag component to build the form with Livewire component support for FilamentPHP", + "en": "Manage your forms using database and drop/drag component to build the form with Livewire component support for FilamentPHP", + "gr": "Manage your forms using database and drop/drag component to build the form with Livewire component support for FilamentPHP", + "sp": "Manage your forms using database and drop/drag component to build the form with Livewire component support for FilamentPHP" + }, + "keywords": [], + "priority": 0, + "providers": [ + "TomatoPHP\\FilamentFormBuilder\\FilamentFormBuilderServiceProvider" + ], + "files": [], + "title": { + "ar": "Filament form builder", + "en": "Filament form builder", + "gr": "Filament form builder", + "sp": "Filament form builder" + }, + "color": "#cc1448", + "icon": "heroicon-c-users", + "placeholder": "https://raw.githubusercontent.com/tomatophp/filament-form-builder/master/art/screenshot.jpg", + "type": "plugin", + "version": "v1.0.0", + "github" : "https://github.com/tomatophp/filament-form-builder", + "docs" : "https://github.com/tomatophp/filament-form-builder" +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..e542661 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,26 @@ + + + + + ./tests/ + + + + + ./src + + + + + + + diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..c6ddb49 --- /dev/null +++ b/pint.json @@ -0,0 +1,14 @@ +{ + "preset": "laravel", + "rules": { + "blank_line_before_statement": true, + "concat_space": { + "spacing": "one" + }, + "method_argument_space": true, + "single_trait_insert_per_statement": true, + "types_spaces": { + "space": "single" + } + } +} diff --git a/resources/lang/.gitkeep b/resources/lang/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/resources/lang/ar/messages.php b/resources/lang/ar/messages.php new file mode 100644 index 0000000..4b480a2 --- /dev/null +++ b/resources/lang/ar/messages.php @@ -0,0 +1,73 @@ + [ + "section" => [ + "information" => "تفاصيل النموذج" + ], + "title" => "منشيء النماذج", + "single" => "نموذج", + "columns" => [ + "type" => "النوع", + "method" => "الطريقة", + "title" => "العنوان", + "key" => "المفتاح", + "description" => "الوصف", + "endpoint" => "الرابط", + "is_active" => "نشط", + ], + "fields" => [ + "title" => "الحقول", + "single" => "حقل", + "columns" => [ + "type" => "النوع", + "name" => "الاسم", + "group" => "المجموعة", + "default" => "القيمة الافتراضية", + "is_relation" => "علاقة", + "relation_name" => "اسم العلاقة", + "relation_column" => "عمود العلاقة", + "sub_form" => "نموذج فرعي", + "is_multi" => "متعدد", + "has_options" => "لديه خيارات", + "options" => "الخيارات", + "label" => "التسمية", + "value"=> "القيمة", + "placeholder" => "النص البديل", + "hint" => "تلميح", + "is_required" => "مطلوب", + "required_message" => "رسالة الخطأ", + "has_validation" => "لديه تحقق", + "validation" => "التحقق", + "rule" => "القاعدة", + "message" => "الرسالة", + ], + "tabs" => [ + "general" => "عام", + "options" => "الخيارات", + "validation" => "التحقق", + "relation" => "العلاقة", + "labels" => "التسميات", + ], + "actions" => [ + "preview" => "معاينة", + ] + ], + "requests" => [ + "title" => "طلبات النموذج", + "single" => "طلب", + "columns" => [ + "status" => "الحالة", + "description" => "الوصف", + "time" => "الوقت", + "date" => "التاريخ", + "payload" => "الرسالة", + "pending" => "قيد الانتظار", + "processing" => "جاري المعالجة", + "completed" => "تم الانتهاء", + "cancelled" => "تم الإلغاء", + + ] + ] + ], +]; diff --git a/resources/lang/en/messages.php b/resources/lang/en/messages.php new file mode 100644 index 0000000..51248be --- /dev/null +++ b/resources/lang/en/messages.php @@ -0,0 +1,73 @@ + [ + "section" => [ + "information" => "Form Information" + ], + "title" => "Form Builder", + "single" => "Form", + "columns" => [ + "type" => "Type", + "method" => "Method", + "title" => "Title", + "key" => "Key", + "description" => "Description", + "endpoint" => "Endpoint", + "is_active" => "Is Active", + ], + "fields" => [ + "title" => "Fields", + "single" => "Field", + "columns" => [ + "type" => "Type", + "name" => "Name", + "group" => "Group", + "default" => "Default", + "is_relation" => "Is Relation", + "relation_name" => "Relation Name", + "relation_column" => "Relation Column", + "sub_form" => "Sub Form", + "is_multi" => "Is Multi", + "has_options" => "Has Options", + "options" => "Options", + "label" => "Label", + "value"=> "Value", + "placeholder" => "Placeholder", + "hint" => "Hint", + "is_required" => "Is Required", + "required_message" => "Required Message", + "has_validation" => "Has Validation", + "validation" => "Validation", + "rule" => "Rule", + "message" => "Message" + ], + "tabs" => [ + "general" => "General", + "options" => "Options", + "validation" => "Validation", + "relation" => "Relation", + "labels" => "Labels", + ], + "actions" => [ + "preview" => "Preview", + ] + ], + "requests" => [ + "title" => "Form Requests", + "single" => "Request", + "columns" => [ + "status" => "Status", + "description" => "Description", + "time" => "Time", + "date" => "Date", + "payload" => "Payload", + "pending" => "Pending", + "processing" => "Processing", + "completed" => "Completed", + "cancelled" => "Cancelled", + + ] + ] + ], +]; diff --git a/src/Console/.gitkeep b/src/Console/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Console/FilamentFormBuilderInstall.php b/src/Console/FilamentFormBuilderInstall.php new file mode 100644 index 0000000..9b6ba4d --- /dev/null +++ b/src/Console/FilamentFormBuilderInstall.php @@ -0,0 +1,44 @@ +info('Publish Vendor Assets'); + $this->artisanCommand(["migrate"]); + $this->artisanCommand(["optimize:clear"]); + $this->info('Filament form builder installed successfully.'); + } +} diff --git a/src/Console/FilamentFormGenerator.php b/src/Console/FilamentFormGenerator.php new file mode 100644 index 0000000..4e7e73b --- /dev/null +++ b/src/Console/FilamentFormGenerator.php @@ -0,0 +1,197 @@ +ask('please input your form title'); + $key = $this->ask('please input your form key', Str::slug($name)); + $checkIfFormExists = Form::where('key', $key??Str::slug($name))->first(); + $key= $key??Str::slug($name); + while ($checkIfFormExists){ + $this->error('Form already exists'); + $key = $this->ask('please input your form key [unique]', Str::slug($name)); + $checkIfFormExists = Form::where('key', $key??Str::slug($name))->first(); + $key= $key??Str::slug($name); + } + $endpoint = $this->ask('please input your form endpoint', '/'); + $method = $this->ask('please input your form method', 'POST'); + $description = $this->ask('please input your form description'); + $type = $this->ask('please input your form type', 'page'); + + $fields = []; + $add = true; + while ($add){ + $fieldKey = $this->ask('please input your first field key'); + $checkIfFieldExists = Field::where('key', $fieldKey)->first(); + if(!$checkIfFieldExists){ + $ask = $this->ask('Field not exits exists do you went to create new? [yes/no]', 'no'); + if($ask === 'yes' || $ask === 'y'){ + $type = $this->ask('type', 'text'); + $label = $this->ask('label'); + $fieldKey = $this->ask('key [unique]', Str::slug($label)); + $placeholder = $this->ask('placeholder'); + $required = $this->ask('Is Required? [yes/no]', 'yes'); + $hasOptions = $this->ask('Has Options? [yes/no]', 'no'); + + $buildFieldsArray->push(" ["); + $buildFieldsArray->push(" 'label'=>'".$label. "',"); + $buildFieldsArray->push(" 'key'=>'".$fieldKey ?? Str::slug($fieldKey). "',"); + $buildFieldsArray->push(" 'type'=>'".$type. "',"); + $buildFieldsArray->push(" 'placeholder'=>'".$placeholder. "',"); + if($required || $required === 'n' || $required === 'no'){ + $buildFieldsArray->push(" 'is_required'=>false,"); + } + else { + $buildFieldsArray->push(" 'is_required'=>true,"); + } + if($hasOptions || $hasOptions === 'y' || $hasOptions === 'yes'){ + $buildFieldsArray->push(" 'has_options'=>true,"); + } + else { + $buildFieldsArray->push(" 'has_options'=>false,"); + } + + + if($hasOptions === 'y' || $hasOptions === 'yes'){ + $options = $this->ask('options LIKE: male, female'); + $buildFieldsArray->push(" 'options'=>".$options. ","); + + $options = explode(',', $options); + $options = array_map(function ($option){ + return trim($option); + }, $options); + } + + $buildFieldsArray->push(" ],"); + + $createNewField = new Field(); + $createNewField->label = $label; + $createNewField->key = $fieldKey ?? Str::slug($fieldKey); + $createNewField->type = $type; + $createNewField->placeholder = $placeholder; + $createNewField->is_required = ($required || $required === 'n' || $required === 'no') ? false : true; + $createNewField->has_options = ($hasOptions === 'y' || $hasOptions === 'yes') ? true : false; + + $createNewField->save(); + + if($hasOptions === 'y' || $hasOptions === 'yes'){ + foreach ($options as $option){ + $createNewField->options()->create([ + 'type' => 'text', + 'label' => $option, + 'value' => $option, + ]); + } + } + + $fields[] = $createNewField->id; + } + + } + else { + $buildFieldsArray->push(" ["); + $buildFieldsArray->push(" 'label'=>'".$checkIfFieldExists->label. "',"); + $buildFieldsArray->push(" 'key'=>'".$checkIfFieldExists->key. "',"); + $buildFieldsArray->push(" 'type'=>'".$checkIfFieldExists->type. "',"); + $buildFieldsArray->push(" 'placeholder'=>'".$checkIfFieldExists->placeholder. "',"); + if($checkIfFieldExists->is_required){ + $buildFieldsArray->push(" 'is_required'=>true,"); + } + else { + $buildFieldsArray->push(" 'is_required'=>false,"); + } + if($checkIfFieldExists->has_options){ + $buildFieldsArray->push(" 'has_options'=>true,"); + } + else { + $buildFieldsArray->push(" 'has_options'=>false,"); + } + if($checkIfFieldExists->has_options){ + $getOptions = ""; + foreach($checkIfFieldExists->options as $optionKey=>$option){ + $getOptions .= $option->value; + if($optionKey !== count($option)-1){ + $getOptions.= ","; + } + } + $buildFieldsArray->push(" 'options'=>".$getOptions. ","); + } + $buildFieldsArray->push(" ],"); + + $fields[] = $checkIfFieldExists->id; + } + + $addMore = $this->ask('Do you want to add more fields? [yes/no]', 'no'); + $add = $addMore === 'yes' || $addMore === 'y' ? true : false; + } + + $createNewForm = new Form(); + $createNewForm->name = $title; + $createNewForm->key = $key; + $createNewForm->endpoint = $endpoint ?? '/'; + $createNewForm->method = $method ?? 'POST'; + $createNewForm->description = $description ?? null; + $createNewForm->type = $type; + $createNewForm->save(); + + $createNewForm->fields()->attach($fields); + + $this->generateStubs( + __DIR__ . '/../../stubs/migration.stub', + database_path('migrations/'.date('Y_m_d_His').'_fill_form_for_'.Str::lower($key).'.php'), + [ + 'name' => Str::ucfirst(Str::camel($key)), + 'key' => $key, + 'fields' => $buildFieldsArray->implode("\n"), + 'formName' => $title, + 'formEndpoint' => $endpoint ?? '/', + 'formMethod' => $method ?? 'POST', + 'formDescription' => $description ?? '', + 'formType' => $type, + ] + ); + + $this->info('Form created successfully'); + } +} diff --git a/src/Filament/Resources/.gitkeep b/src/Filament/Resources/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Filament/Resources/FormOptionResource.php b/src/Filament/Resources/FormOptionResource.php new file mode 100644 index 0000000..8be067e --- /dev/null +++ b/src/Filament/Resources/FormOptionResource.php @@ -0,0 +1,138 @@ +schema([ + Forms\Components\TextInput::make('form_id') + ->required() + ->numeric(), + Forms\Components\TextInput::make('type') + ->maxLength(255) + ->default('text'), + Forms\Components\TextInput::make('label'), + Forms\Components\TextInput::make('placeholder'), + Forms\Components\TextInput::make('hint'), + Forms\Components\TextInput::make('name') + ->required() + ->maxLength(255), + Forms\Components\TextInput::make('group') + ->maxLength(255), + Forms\Components\TextInput::make('default'), + Forms\Components\TextInput::make('order') + ->numeric() + ->default(0), + Forms\Components\Toggle::make('is_required'), + Forms\Components\Toggle::make('is_multi'), + Forms\Components\TextInput::make('required_message'), + Forms\Components\Toggle::make('is_reactive'), + Forms\Components\TextInput::make('reactive_field') + ->maxLength(255), + Forms\Components\TextInput::make('reactive_where') + ->maxLength(255), + Forms\Components\Toggle::make('is_relation'), + Forms\Components\TextInput::make('relation_name') + ->maxLength(255), + Forms\Components\TextInput::make('relation_column') + ->maxLength(255), + Forms\Components\Toggle::make('has_options'), + Forms\Components\TextInput::make('options'), + Forms\Components\Toggle::make('has_validation'), + Forms\Components\TextInput::make('validation'), + Forms\Components\TextInput::make('meta'), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('form_id') + ->numeric() + ->sortable(), + Tables\Columns\TextColumn::make('type') + ->searchable(), + Tables\Columns\TextColumn::make('name') + ->searchable(), + Tables\Columns\TextColumn::make('group') + ->searchable(), + Tables\Columns\TextColumn::make('order') + ->numeric() + ->sortable(), + Tables\Columns\IconColumn::make('is_required') + ->boolean(), + Tables\Columns\IconColumn::make('is_multi') + ->boolean(), + Tables\Columns\IconColumn::make('is_reactive') + ->boolean(), + Tables\Columns\TextColumn::make('reactive_field') + ->searchable(), + Tables\Columns\TextColumn::make('reactive_where') + ->searchable(), + Tables\Columns\IconColumn::make('is_relation') + ->boolean(), + Tables\Columns\TextColumn::make('relation_name') + ->searchable(), + Tables\Columns\TextColumn::make('relation_column') + ->searchable(), + Tables\Columns\IconColumn::make('has_options') + ->boolean(), + Tables\Columns\IconColumn::make('has_validation') + ->boolean(), + Tables\Columns\TextColumn::make('created_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('updated_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListFormOptions::route('/'), + 'create' => Pages\CreateFormOption::route('/create'), + 'edit' => Pages\EditFormOption::route('/{record}/edit'), + ]; + } +} diff --git a/src/Filament/Resources/FormOptionResource/Pages/CreateFormOption.php b/src/Filament/Resources/FormOptionResource/Pages/CreateFormOption.php new file mode 100644 index 0000000..51aabc9 --- /dev/null +++ b/src/Filament/Resources/FormOptionResource/Pages/CreateFormOption.php @@ -0,0 +1,12 @@ +schema([ + Forms\Components\TextInput::make('model_id') + ->numeric(), + Forms\Components\TextInput::make('model_type') + ->maxLength(255), + Forms\Components\TextInput::make('form_request_id') + ->required() + ->numeric(), + Forms\Components\TextInput::make('key') + ->required() + ->maxLength(255), + Forms\Components\TextInput::make('value'), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('model_id') + ->numeric() + ->sortable(), + Tables\Columns\TextColumn::make('model_type') + ->searchable(), + Tables\Columns\TextColumn::make('form_request_id') + ->numeric() + ->sortable(), + Tables\Columns\TextColumn::make('key') + ->searchable(), + Tables\Columns\TextColumn::make('created_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('updated_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListFormRequestMetas::route('/'), + 'create' => Pages\CreateFormRequestMeta::route('/create'), + 'edit' => Pages\EditFormRequestMeta::route('/{record}/edit'), + ]; + } +} diff --git a/src/Filament/Resources/FormRequestMetaResource/Pages/CreateFormRequestMeta.php b/src/Filament/Resources/FormRequestMetaResource/Pages/CreateFormRequestMeta.php new file mode 100644 index 0000000..329bf4d --- /dev/null +++ b/src/Filament/Resources/FormRequestMetaResource/Pages/CreateFormRequestMeta.php @@ -0,0 +1,12 @@ +schema([ + Forms\Components\TextInput::make('model_type') + ->maxLength(255), + Forms\Components\TextInput::make('model_id') + ->numeric(), + Forms\Components\TextInput::make('service_type') + ->maxLength(255), + Forms\Components\TextInput::make('service_id') + ->numeric(), + Forms\Components\TextInput::make('form_id') + ->required() + ->numeric(), + Forms\Components\TextInput::make('status') + ->maxLength(255) + ->default('pending'), + Forms\Components\TextInput::make('payload'), + Forms\Components\Textarea::make('description') + ->columnSpanFull(), + Forms\Components\DatePicker::make('date'), + Forms\Components\TextInput::make('time'), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('model_type') + ->searchable(), + Tables\Columns\TextColumn::make('model_id') + ->numeric() + ->sortable(), + Tables\Columns\TextColumn::make('service_type') + ->searchable(), + Tables\Columns\TextColumn::make('service_id') + ->numeric() + ->sortable(), + Tables\Columns\TextColumn::make('form_id') + ->numeric() + ->sortable(), + Tables\Columns\TextColumn::make('status') + ->searchable(), + Tables\Columns\TextColumn::make('date') + ->date() + ->sortable(), + Tables\Columns\TextColumn::make('time'), + Tables\Columns\TextColumn::make('created_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('updated_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListFormRequests::route('/'), + 'create' => Pages\CreateFormRequest::route('/create'), + 'edit' => Pages\EditFormRequest::route('/{record}/edit'), + ]; + } +} diff --git a/src/Filament/Resources/FormRequestResource/Pages/CreateFormRequest.php b/src/Filament/Resources/FormRequestResource/Pages/CreateFormRequest.php new file mode 100644 index 0000000..abf8126 --- /dev/null +++ b/src/Filament/Resources/FormRequestResource/Pages/CreateFormRequest.php @@ -0,0 +1,12 @@ +label(trans('filament-cms::messages.forms.columns.type')) + ->searchable() + ->options([ + "page" => "Page", + "modal" => "Modal", + "slideover" => "Slideover", + ]) + ->default('page'), + Forms\Components\Select::make('method') + ->label(trans('filament-cms::messages.forms.columns.method')) + ->searchable() + ->options([ + "POST" => "POST", + "GET" => "GET", + "PUT" => "PUT", + "DELETE" => "DELETE", + "PATCH" => "PATCH", + ]) + ->default('POST'), + Forms\Components\TextInput::make('title') + ->label(trans('filament-cms::messages.forms.columns.title')), + Forms\Components\TextInput::make('key') + ->label(trans('filament-cms::messages.forms.columns.key')) + ->default(Str::random(6)) + ->unique(ignoreRecord: true) + ->required() + ->maxLength(255), + Forms\Components\Textarea::make('description') + ->label(trans('filament-cms::messages.forms.columns.description')) + ->columnSpanFull(), + Forms\Components\TextInput::make('endpoint') + ->label(trans('filament-cms::messages.forms.columns.endpoint')) + ->columnSpanFull() + ->maxLength(255) + ->default('/'), + Forms\Components\Toggle::make('is_active') + ->label(trans('filament-cms::messages.forms.columns.is_active')), + ]; + return $form + ->schema(fn($record) => $record ? [ + Forms\Components\Section::make(trans('filament-cms::messages.forms.section.information')) + ->collapsible() + ->collapsed(fn($record) => $record) + ->schema($formSchema), + ] : $formSchema); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('type') + ->label(trans('filament-cms::messages.forms.columns.type')) + ->searchable(), + Tables\Columns\TextColumn::make('title') + ->label(trans('filament-cms::messages.forms.columns.title')) + ->searchable(), + Tables\Columns\TextColumn::make('key') + ->label(trans('filament-cms::messages.forms.columns.key')) + ->searchable(), + Tables\Columns\TextColumn::make('endpoint') + ->label(trans('filament-cms::messages.forms.columns.endpoint')) + ->searchable(), + Tables\Columns\TextColumn::make('method') + ->label(trans('filament-cms::messages.forms.columns.method')) + ->searchable(), + Tables\Columns\IconColumn::make('is_active') + ->label(trans('filament-cms::messages.forms.columns.is_active')) + ->boolean(), + Tables\Columns\TextColumn::make('created_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('updated_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + RelationManagers\FormFieldsRelation::class, + RelationManagers\FormRequestsRelation::class, + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListForms::route('/'), + 'edit' => Pages\EditForm::route('/{record}/edit'), + ]; + } +} diff --git a/src/Filament/Resources/FormResource/Pages/CreateForm.php b/src/Filament/Resources/FormResource/Pages/CreateForm.php new file mode 100644 index 0000000..bd6d7bc --- /dev/null +++ b/src/Filament/Resources/FormResource/Pages/CreateForm.php @@ -0,0 +1,12 @@ +schema([ + Forms\Components\Tabs::make() + ->schema([ + Forms\Components\Tabs\Tab::make(trans('filament-cms::messages.forms.fields.tabs.general')) + ->icon('heroicon-s-information-circle') + ->schema([ + Forms\Components\Select::make('type') + ->label(trans('filament-cms::messages.forms.fields.columns.type')) + ->searchable() + ->options(FilamentCMSFormFields::getOptions()->pluck('label', 'name')->toArray()) + ->default('text'), + Forms\Components\TextInput::make('name') + ->label(trans('filament-cms::messages.forms.fields.columns.name')) + ->live() + ->afterStateUpdated(function (Forms\Get $get, Forms\Set $set, $state){ + if(str($state)->contains('email')){ + $set('type', 'email'); + } + if(str($state)->contains('phone')){ + $set('type', 'tel'); + } + if(str($state)->contains(['is_', 'has_'])){ + $set('type', 'toggle'); + } + if(str($state)->contains(['at', 'date'])){ + $set('type', 'date'); + } + if(str($state)->contains('password')){ + $set('type', 'password'); + } + if(str($state)->contains(['description', 'message'])){ + $set('type', 'textarea'); + } + if(str($state)->contains(['body', 'about'])){ + $set('type', 'rich'); + } + if(str($state)->contains('price')){ + $set('type', 'number'); + } + + }) + ->required() + ->maxLength(255), + Forms\Components\TextInput::make('group') + ->label(trans('filament-cms::messages.forms.fields.columns.group')) + ->maxLength(255), + Forms\Components\TextInput::make('default') + ->label(trans('filament-cms::messages.forms.fields.columns.default')), + ])->columns(2), +// Forms\Components\Tabs\Tab::make('Reactive') +// ->schema([ +// Forms\Components\Toggle::make('is_reactive') +// ->live(), +// Forms\Components\Select::make('reactive_field') +// ->hidden(fn(Forms\Get $get) => !$get('is_reactive')) +// ->searchable() +// ->options(function(){ +// return FormOption::query()->where('form_id', $this->getOwnerRecord()->id)->pluck('name', 'name')->toArray(); +// }), +// Forms\Components\Repeater::make('reactive_where') +// ->hidden(fn(Forms\Get $get) => !$get('is_reactive')) +// ->schema([ +// Forms\Components\Select::make('field') +// ->searchable() +// ->options(function(){ +// return FormOption::query()->where('form_id', $this->getOwnerRecord()->id)->pluck('name', 'name')->toArray(); +// }), +// Forms\Components\Select::make('operator') +// ->options([ +// '=' => '=', +// '!=' => '!=', +// '>' => '>', +// '<' => '<', +// '>=' => '>=', +// '<=' => '<=' +// ]), +// Forms\Components\TextInput::make('value') +// ->maxLength(255) +// ])->columns(3), +// ]), + Forms\Components\Tabs\Tab::make(trans('filament-cms::messages.forms.fields.tabs.relation')) + ->icon('heroicon-s-squares-plus') + ->schema([ + Forms\Components\Toggle::make('is_relation') + ->label(trans('filament-cms::messages.forms.fields.columns.is_relation')) + ->columnSpanFull() + ->live(), + Forms\Components\TextInput::make('relation_name') + ->label(trans('filament-cms::messages.forms.fields.columns.relation_name')) + ->hidden(fn(Forms\Get $get) => !$get('is_relation')) + ->maxLength(255), + Forms\Components\TextInput::make('relation_column') + ->label(trans('filament-cms::messages.forms.fields.columns.relation_column')) + ->hidden(fn(Forms\Get $get) => !$get('is_relation')) + ->maxLength(255), + ])->columns(2), + Forms\Components\Tabs\Tab::make(trans('filament-cms::messages.forms.fields.tabs.options')) + ->icon('heroicon-s-rectangle-group') + ->schema([ + Forms\Components\Select::make('sub_form') + ->label(trans('filament-cms::messages.forms.fields.columns.sub_form')) + ->searchable() + ->options(\TomatoPHP\FilamentFormBuilder\Models\Form::query()->where('id', '!=', $this->getOwnerRecord()->id)->pluck('key', 'id')->toArray()), + Forms\Components\Toggle::make('is_multi') + ->label(trans('filament-cms::messages.forms.fields.columns.is_multi')), + Forms\Components\Toggle::make('has_options') + ->label(trans('filament-cms::messages.forms.fields.columns.has_options')) + ->live(), + Forms\Components\Repeater::make('options') + ->label(trans('filament-cms::messages.forms.fields.columns.options')) + ->schema([ + Translation::make('label')->label(trans('filament-cms::messages.forms.fields.columns.label')), + Forms\Components\TextInput::make('value')->label(trans('filament-cms::messages.forms.fields.columns.value')), + ]) + ->hidden(fn(Forms\Get $get) => !$get('has_options')), + ]), + Forms\Components\Tabs\Tab::make(trans('filament-cms::messages.forms.fields.tabs.labels')) + ->icon('heroicon-s-language') + ->schema([ + Translation::make('label') + ->label(trans('filament-cms::messages.forms.fields.columns.label')), + Translation::make('placeholder') + ->label(trans('filament-cms::messages.forms.fields.columns.placeholder')), + Translation::make('hint') + ->label(trans('filament-cms::messages.forms.fields.columns.hint')), + ]), + Forms\Components\Tabs\Tab::make(trans('filament-cms::messages.forms.fields.tabs.validation')) + ->icon('heroicon-s-variable') + ->schema([ + Forms\Components\Toggle::make('is_required') + ->label(trans('filament-cms::messages.forms.fields.columns.is_required')) + ->live(), + Translation::make('required_message') + ->label(trans('filament-cms::messages.forms.fields.columns.required_message')) + ->hidden(fn(Forms\Get $get) => !$get('is_required')), + Forms\Components\Toggle::make('has_validation') + ->label(trans('filament-cms::messages.forms.fields.columns.has_validation')) + ->live(), + Forms\Components\Repeater::make('validation') + ->label(trans('filament-cms::messages.forms.fields.columns.validation')) + ->schema([ + Forms\Components\TextInput::make('rule')->label(trans('filament-cms::messages.forms.fields.columns.rule')), + Translation::make('message')->label(trans('filament-cms::messages.forms.fields.columns.message')), + ]) + ->hidden(fn(Forms\Get $get) => !$get('has_validation')), + ]) + ]) + ])->columns(1); + } + + public function table(Table $table): Table + { + return $table + ->headerActions([ + Tables\Actions\CreateAction::make() + ->icon('heroicon-s-plus-circle') + ->after(function(array $data, $record){ + $record->name = Str::of($record->name)->replace(' ', '_')->lower()->toString(); + $record->save(); + }), + Tables\Actions\Action::make('preview') + ->label(trans('filament-cms::messages.forms.fields.actions.preview')) + ->icon('heroicon-s-eye') + ->color('info') + ->form(function (){ + return FilamentCMSFormBuilder::make($this->getOwnerRecord()->key)->build(); + })->action(function (array $data){ + FilamentCMSFormBuilder::make($this->getOwnerRecord()->key)->send($data); + }) + ]) + ->actions([ + Tables\Actions\EditAction::make()->after(function(array $data, $record){ + $record->name = Str::of($record->name)->replace(' ', '_')->lower()->toString(); + $record->save(); + }), + Tables\Actions\DeleteAction::make() + ]) + ->columns([ + Tables\Columns\TextColumn::make('type') + ->label(trans('filament-cms::messages.forms.fields.columns.type')) + ->badge() + ->icon(fn($record) => FilamentCMSFormFields::getOptions()->where('name', $record->type)->first()->icon) + ->color(fn($record) => FilamentCMSFormFields::getOptions()->where('name', $record->type)->first()->color) + ->state(fn($record) => FilamentCMSFormFields::getOptions()->where('name', $record->type)->first()->label) + ->searchable(), + Tables\Columns\TextColumn::make('name') + ->label(trans('filament-cms::messages.forms.fields.columns.name')) + ->searchable(), + Tables\Columns\ToggleColumn::make('is_required') + ->label(trans('filament-cms::messages.forms.fields.columns.is_required')) + ->searchable(), + Tables\Columns\TextColumn::make('created_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('updated_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->groups([ + Tables\Grouping\Group::make('group') + ]) + ->defaultSort('created_at') + ->bulkActions([ + Tables\Actions\DeleteBulkAction::make() + ]) + ->reorderable('order'); + } +} diff --git a/src/Filament/Resources/FormResource/RelationManagers/FormRequestsRelation.php b/src/Filament/Resources/FormResource/RelationManagers/FormRequestsRelation.php new file mode 100644 index 0000000..fef1793 --- /dev/null +++ b/src/Filament/Resources/FormResource/RelationManagers/FormRequestsRelation.php @@ -0,0 +1,173 @@ +schema([ + Forms\Components\Select::make('status') + ->label(trans('filament-cms::messages.forms.requests.columns.status')) + ->searchable() + ->options([ + "pending" => trans('filament-cms::messages.forms.requests.columns.pending'), + "processing" => trans('filament-cms::messages.forms.requests.columns.processing'), + "completed" => trans('filament-cms::messages.forms.requests.columns.completed'), + "cancelled" => trans('filament-cms::messages.forms.requests.columns.cancelled'), + ]) + ->columnSpanFull() + ->default('pending'), + ]); + } + + public function infolist(Infolist $infolist): Infolist + { + + return $infolist->schema([ + TextEntry::make('description') + ->label(trans('filament-cms::messages.forms.requests.columns.description')) + ->columnSpanFull(), + TextEntry::make('time') + ->label(trans('filament-cms::messages.forms.requests.columns.time')), + TextEntry::make('date') + ->label(trans('filament-cms::messages.forms.requests.columns.date')), + KeyValueEntry::make('payload') + ->label(trans('filament-cms::messages.forms.requests.columns.payload')) + ->columnSpanFull() + ->schema(function(FormRequest $record){ + $getEntryText = []; + foreach ($record->payload as $key=>$value){ + $field = $record->form->fields->where('key', $key)->first(); + $getEntryText[] = TextEntry::make($key) + ->label($field->label ?? str($key)->title()) + ->default($value) + ->columnSpanFull(); + } + + return $getEntryText; + }) + ->columns(2) + ]); + } + + public function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('status') + ->label(trans('filament-cms::messages.forms.requests.columns.status')) + ->badge() + ->state(fn($record) => match($record->status) { + "pending" => trans('filament-cms::messages.forms.requests.columns.pending'), + "processing" => trans('filament-cms::messages.forms.requests.columns.processing'), + "completed" => trans('filament-cms::messages.forms.requests.columns.completed'), + "cancelled" => trans('filament-cms::messages.forms.requests.columns.cancelled'), + default => $record->status, + }) + ->icon(fn($record) => match($record->status) { + 'pending' => 'heroicon-s-rectangle-stack', + 'processing' => 'heroicon-s-arrow-path', + 'completed' => 'heroicon-s-check-circle', + 'cancelled' => 'heroicon-s-x-circle', + default => 'heroicon-s-x-circle', + }) + ->color(fn($record) => match($record->status) { + 'pending' => 'info', + 'processing' => 'warning', + 'completed' => 'success', + 'cancelled' => 'danger', + default => 'secondary', + }) + ->searchable(), + Tables\Columns\TextColumn::make('description') + ->label(trans('filament-cms::messages.forms.requests.columns.description')), + Tables\Columns\TextColumn::make('date') + ->label(trans('filament-cms::messages.forms.requests.columns.date')) + ->date() + ->sortable(), + Tables\Columns\TextColumn::make('time') + ->label(trans('filament-cms::messages.forms.requests.columns.time')), + Tables\Columns\TextColumn::make('created_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('updated_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + Tables\Filters\SelectFilter::make('status') + ->label(trans('filament-cms::messages.forms.requests.columns.status')) + ->searchable() + ->options([ + "pending" => trans('filament-cms::messages.forms.requests.columns.pending'), + "processing" => trans('filament-cms::messages.forms.requests.columns.processing'), + "completed" => trans('filament-cms::messages.forms.requests.columns.completed'), + "cancelled" => trans('filament-cms::messages.forms.requests.columns.cancelled'), + ]) + ->columnSpanFull(), + ]) + ->defaultSort('created_at', 'desc') + ->groups([ + Tables\Grouping\Group::make('status') + ]) + ->actions([ + Tables\Actions\ViewAction::make(), + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + +} diff --git a/src/FilamentFormBuilderPlugin.php b/src/FilamentFormBuilderPlugin.php new file mode 100644 index 0000000..6a48388 --- /dev/null +++ b/src/FilamentFormBuilderPlugin.php @@ -0,0 +1,30 @@ +commands([ + \TomatoPHP\FilamentFormBuilder\Console\FilamentFormBuilderInstall::class, + ]); + + //Register Config file + $this->mergeConfigFrom(__DIR__.'/../config/filament-form-builder.php', 'filament-form-builder'); + + //Publish Config + $this->publishes([ + __DIR__.'/../config/filament-form-builder.php' => config_path('filament-form-builder.php'), + ], 'filament-form-builder-config'); + + //Register Migrations + $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); + + //Publish Migrations + $this->publishes([ + __DIR__.'/../database/migrations' => database_path('migrations'), + ], 'filament-form-builder-migrations'); + //Register views + $this->loadViewsFrom(__DIR__.'/../resources/views', 'filament-form-builder'); + + //Publish Views + $this->publishes([ + __DIR__.'/../resources/views' => resource_path('views/vendor/filament-form-builder'), + ], 'filament-form-builder-views'); + + //Register Langs + $this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'filament-form-builder'); + + //Publish Lang + $this->publishes([ + __DIR__.'/../resources/lang' => base_path('lang/vendor/filament-form-builder'), + ], 'filament-form-builder-lang'); + + //Register Routes + $this->loadRoutesFrom(__DIR__.'/../routes/web.php'); + + } + + public function boot(): void + { + FilamentCMSFormFields::register([ + CmsFormFieldType::make('text') + ->label('Text'), + CmsFormFieldType::make('textarea') + ->className(Textarea::class) + ->color('warning') + ->icon('heroicon-s-document-text') + ->label('Textarea'), + CmsFormFieldType::make('select') + ->className(Select::class) + ->color('info') + ->icon('heroicon-s-squares-plus') + ->label('Select'), + CmsFormFieldType::make('checkbox') + ->className(Checkbox::class) + ->color('danger') + ->icon('heroicon-s-check') + ->label('Checkbox'), + CmsFormFieldType::make('radio') + ->className(Radio::class) + ->color('success') + ->icon('heroicon-s-check-circle') + ->label('Radio'), + CmsFormFieldType::make('file') + ->className(FileUpload::class) + ->color('info') + ->icon('heroicon-s-document-arrow-up') + ->label('File'), + CmsFormFieldType::make('date') + ->className(DatePicker::class) + ->color('success') + ->icon('heroicon-s-calendar') + ->label('Date'), + CmsFormFieldType::make('time') + ->className(TimePicker::class) + ->color('info') + ->icon('heroicon-s-clock') + ->label('Time'), + CmsFormFieldType::make('datetime') + ->className(DateTimePicker::class) + ->color('warning') + ->icon('heroicon-s-calendar-days') + ->label('DateTime'), + CmsFormFieldType::make('color') + ->className(ColorPicker::class) + ->color('success') + ->icon('heroicon-s-swatch') + ->label('Color'), + CmsFormFieldType::make('icon') + ->className(IconPicker::class) + ->color('info') + ->icon('heroicon-s-heart') + ->label('Icon'), + CmsFormFieldType::make('toggle') + ->className(Toggle::class) + ->color('success') + ->icon('heroicon-s-adjustments-horizontal') + ->label('Toggle'), + CmsFormFieldType::make('password') + ->color('danger') + ->icon('heroicon-s-lock-closed') + ->label('Password'), + CmsFormFieldType::make('email') + ->color('info') + ->icon('heroicon-s-envelope') + ->label('Email'), + CmsFormFieldType::make('number') + ->color('success') + ->icon('heroicon-s-minus-circle') + ->label('Number'), + CmsFormFieldType::make('url') + ->color('primary') + ->icon('heroicon-s-globe-alt') + ->label('URL'), + CmsFormFieldType::make('tel') + ->color('warning') + ->icon('heroicon-s-phone') + ->label('Tel'), + CmsFormFieldType::make('markdown') + ->className(MarkdownEditor::class) + ->color('warning') + ->icon('heroicon-s-hashtag') + ->label('Markdown'), + CmsFormFieldType::make('rich') + ->className(RichEditor::class) + ->color('info') + ->icon('heroicon-s-document-text') + ->label('RichText'), + CmsFormFieldType::make('keyValue') + ->className(KeyValue::class) + ->color('danger') + ->icon('heroicon-s-key') + ->label('Key/Value'), + CmsFormFieldType::make('repeater') + ->className(Repeater::class) + ->icon('heroicon-s-rectangle-group') + ->label('Repeater'), + ]); + } +} diff --git a/src/Models/.gitkeep b/src/Models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Models/Form.php b/src/Models/Form.php new file mode 100644 index 0000000..201e2a3 --- /dev/null +++ b/src/Models/Form.php @@ -0,0 +1,57 @@ + 'boolean', + ]; + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function fields() + { + return $this->hasMany(FormOption::class, 'form_id', 'id')->orderBy('order', 'asc'); + } + + public function requests() + { + return $this->hasMany(FormRequest::class, 'form_id', 'id'); + } +} diff --git a/src/Models/FormOption.php b/src/Models/FormOption.php new file mode 100644 index 0000000..d77344c --- /dev/null +++ b/src/Models/FormOption.php @@ -0,0 +1,71 @@ + "json", + 'options' => "array", + 'validation' => "array", + 'lable' => "array", + 'hint'=> "array", + 'placeholder'=> "array", + 'required_message'=> "array", + 'reactive_where'=> "array", + 'has_options' => "boolean", + 'has_validation' => "boolean", + 'is_required'=> "boolean", + 'is_multi'=> "boolean", + 'is_reactive'=> "boolean", + 'is_from_table'=> "boolean" + ]; + + + public function subForm() + { + return $this->belongsTo(Form::class, 'sub_form', 'id'); + } + +} diff --git a/src/Models/FormRequest.php b/src/Models/FormRequest.php new file mode 100644 index 0000000..5139602 --- /dev/null +++ b/src/Models/FormRequest.php @@ -0,0 +1,82 @@ + "array" + ]; + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function form() + { + return $this->belongsTo('TomatoPHP\FilamentFormBuilder\Models\Form'); + } + + public function formRequestsMetas(){ + return $this->hasMany(FormRequestMeta::class, 'form_request_id'); + } + + public function meta(string $key, string|null $value=null): Model|string|null + { + if($value){ + return $this->formRequestsMetas()->updateOrCreate(['key' => $key], ['value' => $value]); + } + else { + return $this->formRequestsMetas()->where('key', $key)->first()?->value; + } + } + + public function modelable() + { + return $this->morphTo(); + } + + public function serviceable() + { + return $this->morphTo(); + } +} diff --git a/src/Models/FormRequestMeta.php b/src/Models/FormRequestMeta.php new file mode 100644 index 0000000..ff16090 --- /dev/null +++ b/src/Models/FormRequestMeta.php @@ -0,0 +1,36 @@ + 'array', + ]; + + public function formRequest(){ + return $this->belongsTo('TomatoPHP\FilamentFormBuilder\Models\FormRequest'); + } +} diff --git a/src/Services/Contracts/CmsFormFieldType.php b/src/Services/Contracts/CmsFormFieldType.php new file mode 100644 index 0000000..12262e6 --- /dev/null +++ b/src/Services/Contracts/CmsFormFieldType.php @@ -0,0 +1,102 @@ +name($name); + } + + /** + * @param string $name + * @return $this + */ + public function name(string $name): self + { + $this->name = $name; + return $this; + } + + /** + * @param string $label + * @return $this + */ + public function label(string $label): self + { + $this->label = $label; + return $this; + } + + /** + * @param string $color + * @return $this + */ + public function color(string $color): self + { + $this->color = $color; + return $this; + } + + /** + * @param string $icon + * @return $this + */ + public function icon(string $icon): self + { + $this->icon = $icon; + return $this; + } + + /** + * @param string $className + * @return $this + */ + public function className(string $className): self + { + $this->className = $className; + return $this; + } + + /** + * @return array + */ + public function toArray(): array + { + return [ + 'name' => $this->name, + 'label' => $this->label, + 'color' => $this->color, + 'icon' => $this->icon, + 'className' => $this->className, + ]; + } +} diff --git a/src/Services/Contracts/Form.php b/src/Services/Contracts/Form.php new file mode 100644 index 0000000..e1306c4 --- /dev/null +++ b/src/Services/Contracts/Form.php @@ -0,0 +1,97 @@ +setLocale($lang->id ?? config('app.locale')); + }catch (\Exception $exception) {} + } + + public function toArray(): array + { + return [ + 'title' => $this->title, + 'description' => $this->description, + 'name' => $this->name, + 'key' => $this->key, + 'type' => $this->type, + 'method' => $this->method, + 'endpoint' => $this->endpoint, + 'inputs' => $this->inputs, + ]; + } + + /** + * @return static + */ + public static function make(): static + { + return (new static); + } + + public function title(string $title): static + { + $this->title = $title; + return $this; + } + + public function description(string $description): static + { + $this->description = $description; + return $this; + } + + public function key(string $key): static + { + $this->key = $key; + return $this; + } + + public function name(string $name): static + { + $this->name = $name; + return $this; + } + + public function method(string $method): static + { + $this->method = $method; + return $this; + } + + public function type(string $type): static + { + $this->type = $type; + return $this; + } + + public function endpoint(string $endpoint): static + { + $this->endpoint = $endpoint; + return $this; + } + + public function inputs(array $inputs): static + { + $this->inputs = $inputs; + return $this; + } +} diff --git a/src/Services/Contracts/FormInput.php b/src/Services/Contracts/FormInput.php new file mode 100644 index 0000000..cb3e1c7 --- /dev/null +++ b/src/Services/Contracts/FormInput.php @@ -0,0 +1,238 @@ +setLocale($lang->id ?? config('app.locale')); + }catch (\Exception $exception) {} + } + + public function toArray(){ + return [ + 'label' => $this->label, + 'placeholder' => $this->placeholder, + 'hint' => $this->hint, + 'type' => $this->type, + 'name' => $this->name, + 'group' => $this->group, + 'default' => $this->default, + 'order' => $this->order, + 'required_message' => $this->required_message, + 'reactive_field' => $this->reactive_field, + 'reactive_where' => $this->reactive_where, + 'table_name' => $this->table_name, + 'options' => $this->options, + 'has_valdation' => $this->has_valdation, + 'is_requred' => $this->is_requred, + 'is_reavtive' => $this->is_reavtive, + 'is_from_table' => $this->is_from_table, + 'is_multi' => $this->is_multi, + 'has_options' => $this->has_options, + 'validation' => [ + 'max' => $this->max, + 'min' => $this->min, + 'type' => $this->input_type, + 'option_root' => $this->option_root, + 'option_value' => $this->option_value, + 'option_label' => $this->option_label, + ] + ]; + } + + + /** + * @return static + */ + public static function make(): static + { + return (new static); + } + + public function label(string $label): static + { + $this->label = $label; + return $this; + } + + public function input_type(string $input_type): static + { + $this->input_type = $input_type; + return $this; + } + + public function placeholder(string $placeholder): static + { + $this->placeholder = $placeholder; + return $this; + } + + public function hint(string $hint): static + { + $this->hint = $hint; + return $this; + } + + public function name(string $name): static + { + $this->name = $name; + return $this; + } + + public function group(string $group): static + { + $this->group = $group; + return $this; + } + + public function default(string $default): static + { + $this->default = $default; + return $this; + } + + public function order(int $order): static + { + $this->order = $order; + return $this; + } + + public function required_message(string $required_message): static + { + $this->required_message = $required_message; + return $this; + } + + public function reactive_field(string $reactive_field): static + { + $this->reactive_field = $reactive_field; + return $this; + } + + public function reactive_where(string $reactive_where): static + { + $this->reactive_where = $reactive_where; + return $this; + } + + public function table_name(string $table_name): static + { + $this->table_name = $table_name; + return $this; + } + + public function options(array $options): static + { + $this->options = $options; + return $this; + } + + public function has_valdation(bool $has_valdation): static + { + $this->has_valdation = $has_valdation; + return $this; + } + + public function is_requred(bool $is_requred): static + { + $this->is_requred = $is_requred; + return $this; + } + + public function is_reavtive(bool $is_reavtive): static + { + $this->is_reavtive = $is_reavtive; + return $this; + } + + public function is_from_table(bool $is_from_table): static + { + $this->is_from_table = $is_from_table; + return $this; + } + + public function is_multi(bool $is_multi): static + { + $this->is_multi = $is_multi; + return $this; + } + + public function has_options(bool $has_options): static + { + $this->has_options = $has_options; + return $this; + } + + public function max(int $max): static + { + $this->max = $max; + return $this; + } + + public function min(int $min): static + { + $this->min = $min; + return $this; + } + + public function type(string $type): static + { + $this->type = $type; + return $this; + } + + public function option_label(string $option_label): static + { + $this->option_label = $option_label; + return $this; + } + + public function option_value(string $option_value): static + { + $this->option_value = $option_value; + return $this; + } + + public function option_root(string $option_root): static + { + $this->option_root = $option_root; + return $this; + } + + + +} diff --git a/src/Services/Contracts/FormInputOption.php b/src/Services/Contracts/FormInputOption.php new file mode 100644 index 0000000..248f25f --- /dev/null +++ b/src/Services/Contracts/FormInputOption.php @@ -0,0 +1,58 @@ +setLocale($lang->id ?? config('app.locale')); + }catch (\Exception $exception) {} + } + + public function toArray():array + { + return [ + 'value' => $this->value, + 'label_ar' => $this->label_ar, + 'label_en' => $this->label_en, + ]; + } + + + /** + * @return static + */ + public static function make(): static + { + return (new static); + } + + public function value(string $value): static + { + $this->value = $value; + return $this; + } + + public function label_ar(string $label_ar): static + { + $this->label_ar = $label_ar; + return $this; + } + + public function label_en(string $label_en): static + { + $this->label_en = $label_en; + return $this; + } +} diff --git a/src/Services/FilamentCMSFormBuilder.php b/src/Services/FilamentCMSFormBuilder.php new file mode 100644 index 0000000..ae6ac30 --- /dev/null +++ b/src/Services/FilamentCMSFormBuilder.php @@ -0,0 +1,142 @@ +key($key); + } + + public function key(string $key): static + { + $this->key = $key; + $this->form = Form::query()->where('key', $this->key)->orWhere('id', (int)$this->key)->first(); + return $this; + } + + public function build(): array + { + $schema = []; + $form = $this->form; + if($form){ + $fields = $form->fields()->orderBy('order')->get(); + + foreach ($fields as $key=>$field){ + $getFiledBuilder = FilamentCMSFormFields::getOptions()->where('name', $field->type)->first(); + if($getFiledBuilder){ + $messages = []; + $title = Str::of($field->name)->title()->toString(); + $fieldBuild = $getFiledBuilder->className::make($field->name); + if($field->label){ + $fieldBuild->label($field->label); + } + if($field->hint){ + $fieldBuild->hint($field->hint); + } + if($field->placeholder){ + $fieldBuild->placeholder($field->placeholder); + } + if($field->is_required){ + $fieldBuild->required(); + $messages['required'] = $field->required_message[app()->getLocale()]??null; + } + if($field->default){ + $fieldBuild->default($field->default); + } + if($field->is_multi){ + $fieldBuild->multiple(); + } + if($field->type === 'number'){ + $fieldBuild->numeric(); + } + if($field->type === 'email'){ + $fieldBuild->email(); + } + if($field->type === 'tel'){ + $fieldBuild->tel(); + } + if($field->type === 'url'){ + $fieldBuild->url(); + } + if($field->type === 'password'){ + $fieldBuild->password(); + } +// if($field->is_reactive){ +// $fieldBuild->live(); +// } + if($field->has_options){ + $fieldBuild->options(collect($field->options)->map(function ($item){ + $item['label'] = $item['label'][app()->getLocale()]??null; + return $item; + })->pluck('label', 'value')->toArray()); + } + if($field->has_validation){ + $rules = []; + foreach ($field->validation as $rule){ + $messages[$rule['rule']] = $rule['message'][app()->getLocale()]??null; + $rules[] = $rule['rule']; + } + $fieldBuild->rules($rules); + } + if(count($messages)){ + $fieldBuild->validationMessages($messages); + } + if($field->sub_form){ + $fieldBuild->schema(static::make($field->sub_form)->build()); + } + if($field->is_relation){ + $fieldBuild->searchable(); + if(str($field->relation_name)->contains('\\')){ + $fieldBuild->options($field->relation_name::all()->pluck($field->relation_column, 'id')->toArray()); + } + else { + $fieldBuild->relationship($field->relation_name, $field->relation_column); + } + } + $schema[] = $fieldBuild; + } + + } + } + + return $schema; + } + + public function send(array $data): void + { + if(count($data)){ + $formRequest = new FormRequest(); + $formRequest->form_id = $this->form->id; + $formRequest->status = 'pending'; + $formRequest->payload = $data; + $formRequest->description = "Created From Form Preview"; + $formRequest->date = Carbon::now()->toDateString(); + $formRequest->time = Carbon::now()->toTimeString(); + $formRequest->save(); + + Notification::make() + ->title('Form Preview') + ->body('Form Preview has been created successfully') + ->success() + ->send(); + } + else { + Notification::make() + ->title('Form Preview') + ->body('Form Empty!') + ->danger() + ->send(); + } + } +} diff --git a/src/Services/FilamentCMSFormFields.php b/src/Services/FilamentCMSFormFields.php new file mode 100644 index 0000000..116c222 --- /dev/null +++ b/src/Services/FilamentCMSFormFields.php @@ -0,0 +1,27 @@ +forms[] = $form; + } + + public function getForms(): array + { + return $this->forms; + } + + public function build(): void + { + foreach ($this->forms as $form){ + $checkIfFormExists = \TomatoPHP\FilamentFormBuilder\Models\Form::where('key', $form->key)->first(); + if(!$checkIfFormExists){ + $newForm = \TomatoPHP\FilamentFormBuilder\Models\Form::create($form->toArray()); + $newForm->fields()->createMany($form->inputs); + } + } + } +} diff --git a/testbench.yaml b/testbench.yaml new file mode 100644 index 0000000..b930aa7 --- /dev/null +++ b/testbench.yaml @@ -0,0 +1,24 @@ +providers: + - BladeUI\Icons\BladeIconsServiceProvider + - BladeUI\Heroicons\BladeHeroiconsServiceProvider + - Filament\Actions\ActionsServiceProvider + - Filament\FilamentServiceProvider + - Filament\Forms\FormsServiceProvider + - Filament\Infolists\InfolistsServiceProvider + - Filament\Notifications\NotificationsServiceProvider + - Filament\Support\SupportServiceProvider + - Filament\Tables\TablesServiceProvider + - Filament\Widgets\WidgetsServiceProvider + - RyanChandler\BladeCaptureDirective\BladeCaptureDirectiveServiceProvider + - TomatoPHP\FilamentFormBuilder\FilamentFormBuilderServiceProvider + - TomatoPHP\FilamentFormBuilder\Tests\AdminPanelProvider +workbench: + welcome: true + install: true + start: / + guard: testing + discovers: + web: true + api: false + commands: false + views: true diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..56c7a28 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,5 @@ +in(__DIR__); diff --git a/tests/database/database.sqlite b/tests/database/database.sqlite new file mode 100644 index 0000000..cb9c456 Binary files /dev/null and b/tests/database/database.sqlite differ diff --git a/tests/database/factories/.gitkeep b/tests/database/factories/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/database/factories/UserFactory.php b/tests/database/factories/UserFactory.php new file mode 100644 index 0000000..fb3ea56 --- /dev/null +++ b/tests/database/factories/UserFactory.php @@ -0,0 +1,23 @@ + $this->faker->name(), + 'email' => $this->faker->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password + 'remember_token' => Str::random(10), + ]; + } +} diff --git a/tests/src/AdminPanelProvider.php b/tests/src/AdminPanelProvider.php new file mode 100644 index 0000000..e0ad8de --- /dev/null +++ b/tests/src/AdminPanelProvider.php @@ -0,0 +1,50 @@ +default() + ->id('admin') + ->path('admin') + ->login() + ->pages([ + Pages\Dashboard::class, + ]) + ->plugin( + FilamentFormBuilderPlugin::make() + ) + ->middleware([ + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + AuthenticateSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + DisableBladeIconComponents::class, + DispatchServingFilamentEvent::class, + ]) + ->authMiddleware([ + Authenticate::class, + ]); + } +} diff --git a/tests/src/DebugTest.php b/tests/src/DebugTest.php new file mode 100644 index 0000000..91bf6cd --- /dev/null +++ b/tests/src/DebugTest.php @@ -0,0 +1,5 @@ +each->not->toBeUsed(); +}); diff --git a/tests/src/Models/.gitkeep b/tests/src/Models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/src/Models/User.php b/tests/src/Models/User.php new file mode 100644 index 0000000..356810d --- /dev/null +++ b/tests/src/Models/User.php @@ -0,0 +1,34 @@ +set('database.default', 'sqlite'); + $app['config']->set('database.connections.sqlite.database', __DIR__ . '/../database/database.sqlite'); + + $app['config']->set('view.paths', [ + ...$app['config']->get('view.paths'), + __DIR__ . '/../resources/views', + ]); + } +}