mirror of
https://github.com/filamentphp/filament.git
synced 2026-09-24 15:42:09 +08:00
Merge branch '3.x' into tnylea/3.x
This commit is contained in:
@@ -3,4 +3,5 @@
|
||||
/.github export-ignore
|
||||
/.gitignore export-ignore
|
||||
/phpunit.xml.dist export-ignore
|
||||
/testbench.yaml export-ignore
|
||||
/tests export-ignore
|
||||
|
||||
@@ -11,11 +11,16 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.3, 8.2, 8.1]
|
||||
laravel: [10.*]
|
||||
laravel: [11.*, 10.*]
|
||||
dependency-version: [prefer-stable]
|
||||
include:
|
||||
- laravel: 11.*
|
||||
testbench: 9.*
|
||||
- laravel: 10.*
|
||||
testbench: 8.*
|
||||
exclude:
|
||||
- laravel: 11.*
|
||||
php: 8.1
|
||||
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -11,11 +11,16 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.3, 8.2, 8.1]
|
||||
laravel: [10.*]
|
||||
laravel: [11.*, 10.*]
|
||||
dependency-version: [prefer-stable]
|
||||
include:
|
||||
- laravel: 11.*
|
||||
testbench: 9.*
|
||||
- laravel: 10.*
|
||||
testbench: 8.*
|
||||
exclude:
|
||||
- laravel: 11.*
|
||||
php: 8.1
|
||||
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
+3
-6
@@ -10,22 +10,19 @@
|
||||
"require-dev": {
|
||||
"anourvalar/eloquent-serialize": "^1.2",
|
||||
"danharrin/livewire-rate-limiting": "^0.3|^1.0",
|
||||
"doctrine/dbal": "^3.2",
|
||||
"filament/support": "*",
|
||||
"kirschbaum-development/eloquent-power-joins": "^3.0",
|
||||
"larastan/larastan": "^2.2",
|
||||
"laravel/cashier": "^14.2",
|
||||
"laravel/pint": "^1.0",
|
||||
"league/csv": "^9.14",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
"nunomaduro/termwind": "^1.0",
|
||||
"nunomaduro/termwind": "^1.0|^2.0",
|
||||
"openspout/openspout": "^4.23",
|
||||
"orchestra/testbench": "^8.0",
|
||||
"orchestra/testbench": "^8.20|^9.0",
|
||||
"pestphp/pest": "^2.0",
|
||||
"pestphp/pest-plugin-laravel": "^2.0",
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpstan": "v1.10.54",
|
||||
"rector/rector": "^0.17",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"spatie/laravel-medialibrary": "^10.0|^11.0",
|
||||
"spatie/laravel-ray": "^1.29",
|
||||
"spatie/laravel-tags": "^4.2",
|
||||
|
||||
Generated
+648
-1319
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Generated
+201
-155
@@ -14,7 +14,7 @@
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"alpine-lazy-load-assets": "^1.2.1",
|
||||
"alpinejs": "^3.10.4",
|
||||
"async-alpine": "^1.2.1",
|
||||
"async-alpine": "v1.2.1",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"body-scroll-lock": "^4.0.0-beta.0",
|
||||
"chart.js": "^3.6.0",
|
||||
@@ -44,7 +44,7 @@
|
||||
"postcss": "^8.4.4",
|
||||
"postcss-nesting": "^12.0.1",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-blade": "^2.0.0",
|
||||
"prettier-plugin-blade": "v2.0.0",
|
||||
"prettier-plugin-tailwindcss": "^0.5.10",
|
||||
"pusher-js": "^7.4.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
@@ -2446,9 +2446,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
|
||||
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
@@ -2694,9 +2694,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "8.56.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz",
|
||||
"integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==",
|
||||
"version": "8.56.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.3.tgz",
|
||||
"integrity": "sha512-PvSf1wfv2wJpVIFUMSb+i4PvqNYkB9Rkp9ZDO3oaWzq4SKhsQk4mrMBr3ZH06I0hKrVGLBacmgl8JM4WVjb9dg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
@@ -2850,9 +2850,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz",
|
||||
"integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==",
|
||||
"version": "20.11.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz",
|
||||
"integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
@@ -3483,10 +3483,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz",
|
||||
"integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"possible-typed-array-names": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -3821,9 +3824,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.22.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz",
|
||||
"integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==",
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
|
||||
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3840,8 +3843,8 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001580",
|
||||
"electron-to-chromium": "^1.4.648",
|
||||
"caniuse-lite": "^1.0.30001587",
|
||||
"electron-to-chromium": "^1.4.668",
|
||||
"node-releases": "^2.0.14",
|
||||
"update-browserslist-db": "^1.0.13"
|
||||
},
|
||||
@@ -3891,15 +3894,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz",
|
||||
"integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.3",
|
||||
"set-function-length": "^1.2.0"
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -3949,9 +3953,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001585",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz",
|
||||
"integrity": "sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==",
|
||||
"version": "1.0.30001589",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz",
|
||||
"integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4332,12 +4336,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/core-js-compat": {
|
||||
"version": "3.35.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz",
|
||||
"integrity": "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==",
|
||||
"version": "3.36.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz",
|
||||
"integrity": "sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"browserslist": "^4.22.2"
|
||||
"browserslist": "^4.22.3"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -4730,18 +4734,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.2.tgz",
|
||||
"integrity": "sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==",
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.2",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.1"
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/define-lazy-prop": {
|
||||
@@ -5004,9 +5010,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.659",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.659.tgz",
|
||||
"integrity": "sha512-sRJ3nV3HowrYpBtPF9bASQV7OW49IgZC01Xiq43WfSE3RTCkK0/JidoCmR73Hyc1mN+l/H4Yqx0eNiomvExFZg==",
|
||||
"version": "1.4.681",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.681.tgz",
|
||||
"integrity": "sha512-1PpuqJUFWoXZ1E54m8bsLPVYwIVCRzvaL+n5cjigGga4z854abDnFRc+cTa2th4S79kyGqya/1xoR7h+Y5G5lg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/elliptic": {
|
||||
@@ -5098,50 +5104,52 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-abstract": {
|
||||
"version": "1.22.3",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz",
|
||||
"integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==",
|
||||
"version": "1.22.4",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz",
|
||||
"integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"array-buffer-byte-length": "^1.0.0",
|
||||
"arraybuffer.prototype.slice": "^1.0.2",
|
||||
"available-typed-arrays": "^1.0.5",
|
||||
"call-bind": "^1.0.5",
|
||||
"es-set-tostringtag": "^2.0.1",
|
||||
"array-buffer-byte-length": "^1.0.1",
|
||||
"arraybuffer.prototype.slice": "^1.0.3",
|
||||
"available-typed-arrays": "^1.0.6",
|
||||
"call-bind": "^1.0.7",
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-set-tostringtag": "^2.0.2",
|
||||
"es-to-primitive": "^1.2.1",
|
||||
"function.prototype.name": "^1.1.6",
|
||||
"get-intrinsic": "^1.2.2",
|
||||
"get-symbol-description": "^1.0.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"get-symbol-description": "^1.0.2",
|
||||
"globalthis": "^1.0.3",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.0",
|
||||
"has-property-descriptors": "^1.0.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0",
|
||||
"internal-slot": "^1.0.5",
|
||||
"is-array-buffer": "^3.0.2",
|
||||
"hasown": "^2.0.1",
|
||||
"internal-slot": "^1.0.7",
|
||||
"is-array-buffer": "^3.0.4",
|
||||
"is-callable": "^1.2.7",
|
||||
"is-negative-zero": "^2.0.2",
|
||||
"is-regex": "^1.1.4",
|
||||
"is-shared-array-buffer": "^1.0.2",
|
||||
"is-string": "^1.0.7",
|
||||
"is-typed-array": "^1.1.12",
|
||||
"is-typed-array": "^1.1.13",
|
||||
"is-weakref": "^1.0.2",
|
||||
"object-inspect": "^1.13.1",
|
||||
"object-keys": "^1.1.1",
|
||||
"object.assign": "^4.1.4",
|
||||
"regexp.prototype.flags": "^1.5.1",
|
||||
"safe-array-concat": "^1.0.1",
|
||||
"safe-regex-test": "^1.0.0",
|
||||
"object.assign": "^4.1.5",
|
||||
"regexp.prototype.flags": "^1.5.2",
|
||||
"safe-array-concat": "^1.1.0",
|
||||
"safe-regex-test": "^1.0.3",
|
||||
"string.prototype.trim": "^1.2.8",
|
||||
"string.prototype.trimend": "^1.0.7",
|
||||
"string.prototype.trimstart": "^1.0.7",
|
||||
"typed-array-buffer": "^1.0.0",
|
||||
"typed-array-buffer": "^1.0.1",
|
||||
"typed-array-byte-length": "^1.0.0",
|
||||
"typed-array-byte-offset": "^1.0.0",
|
||||
"typed-array-length": "^1.0.4",
|
||||
"unbox-primitive": "^1.0.2",
|
||||
"which-typed-array": "^1.1.13"
|
||||
"which-typed-array": "^1.1.14"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -5150,6 +5158,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
@@ -5166,14 +5186,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
|
||||
"integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
|
||||
"integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.2",
|
||||
"has-tostringtag": "^1.0.0",
|
||||
"hasown": "^2.0.0"
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -5631,9 +5651,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filepond-plugin-file-validate-type": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/filepond-plugin-file-validate-type/-/filepond-plugin-file-validate-type-1.2.8.tgz",
|
||||
"integrity": "sha512-UBTqIWbk5+5R0GBELI8svu01MHWjFSLfc9DfCMvFtHKXLdJMxY1p37ChC4YKQjhfODaTuvLnNVRsukMGMFZBBg==",
|
||||
"version": "1.2.9",
|
||||
"resolved": "https://registry.npmjs.org/filepond-plugin-file-validate-type/-/filepond-plugin-file-validate-type-1.2.9.tgz",
|
||||
"integrity": "sha512-Tzv07aNdZvjUXDRA3XL16QMEvh6llDrXlcZ6W0eTHQ+taHaVg/JKJTFs/AViO+6ZcpPCcQStbhYEL2HoS+vldw==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"filepond": ">=1.x <5.x"
|
||||
@@ -6068,13 +6088,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-symbol-description": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.1.tgz",
|
||||
"integrity": "sha512-KmuibvwbWaM4BHcBRYwJfZ1JxyJeBwB8ct9YYu67SvYdbEIlcQ2e56dHxfbobqW38GXo8/zDFqJeGtHiVbWyQw==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
|
||||
"integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.5",
|
||||
"es-errors": "^1.3.0"
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -6213,21 +6234,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.2"
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -6308,9 +6329,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
|
||||
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
|
||||
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
@@ -6919,9 +6940,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-negative-zero": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
|
||||
"integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
|
||||
"integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -6995,12 +7016,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-shared-array-buffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
|
||||
"integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
|
||||
"integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2"
|
||||
"call-bind": "^1.0.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -7258,9 +7282,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/laravel-echo": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.15.3.tgz",
|
||||
"integrity": "sha512-SRXzccaat6w4qKgZ4/rjFKr3nJfVxB+ly4V0MEJNIF1/TpERNXepo3uk7NnOjBGsiV/np1fl2XitAzW4Sa1s/w==",
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.16.0.tgz",
|
||||
"integrity": "sha512-BJGUa4tcKvYmTkzTmcBGMHiO2tq+k7Do5wPmLbRswWfzKwyfZEUR+J5iwBTPEfLLwNPZlA9Kjo6R/NV6pmyIpg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -8533,10 +8557,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
||||
"integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.34",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.34.tgz",
|
||||
"integrity": "sha512-4eLTO36woPSocqZ1zIrFD2K1v6wH7pY1uBh0JIM2KKfrVtGvPFiAku6aNOP0W1Wr9qwnaCsF0Z+CrVnryB2A8Q==",
|
||||
"version": "8.4.35",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
|
||||
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -8933,9 +8966,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-nesting": {
|
||||
"version": "12.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.2.tgz",
|
||||
"integrity": "sha512-63PpJHSeNs93S3ZUIyi+7kKx4JqOIEJ6QYtG3x+0qA4J03+4n0iwsyA1GAHyWxsHYljQS4/4ZK1o2sMi70b5wQ==",
|
||||
"version": "12.0.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.3.tgz",
|
||||
"integrity": "sha512-yrtMRPFNkfZMv9ikBvZ/Eh3RxhpMBKQ3KzD7LCY8+jYVlgju/Mdcxi4JY8bW2Y7ISXw8GTLuF/o+kFtp+yaVfQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -8948,7 +8981,7 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@csstools/selector-specificity": "^3.0.1",
|
||||
"@csstools/selector-specificity": "^3.0.2",
|
||||
"postcss-selector-parser": "^6.0.13"
|
||||
},
|
||||
"engines": {
|
||||
@@ -8959,9 +8992,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.1.tgz",
|
||||
"integrity": "sha512-NPljRHkq4a14YzZ3YD406uaxh7s0g6eAq3L9aLOWywoqe8PkYamAvtsh7KNX6c++ihDrJ0RiU+/z7rGnhlZ5ww==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.2.tgz",
|
||||
"integrity": "sha512-RpHaZ1h9LE7aALeQXmXrJkRG84ZxIsctEN2biEUmFyKpzFM3zZ35eUMcIzZFsw/2olQE6v69+esEqU2f1MKycg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -9646,14 +9679,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
|
||||
"integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==",
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
|
||||
"integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.2.0",
|
||||
"set-function-name": "^2.0.0"
|
||||
"call-bind": "^1.0.6",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"set-function-name": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -10158,14 +10192,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/set-function-name": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz",
|
||||
"integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
|
||||
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.0.1",
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"functions-have-names": "^1.2.3",
|
||||
"has-property-descriptors": "^1.0.0"
|
||||
"has-property-descriptors": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -10339,9 +10374,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-exceptions": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.4.0.tgz",
|
||||
"integrity": "sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
|
||||
"integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/spdx-expression-parse": {
|
||||
@@ -10355,9 +10390,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-license-ids": {
|
||||
"version": "3.0.16",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz",
|
||||
"integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==",
|
||||
"version": "3.0.17",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz",
|
||||
"integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/spdy": {
|
||||
@@ -10853,12 +10888,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz",
|
||||
"integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==",
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
|
||||
"integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antonk52"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/postcss-selector-parser": {
|
||||
@@ -10893,9 +10931,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz",
|
||||
"integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==",
|
||||
"version": "5.28.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.28.1.tgz",
|
||||
"integrity": "sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
@@ -11096,12 +11134,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typed-array-buffer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz",
|
||||
"integrity": "sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz",
|
||||
"integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.6",
|
||||
"call-bind": "^1.0.7",
|
||||
"es-errors": "^1.3.0",
|
||||
"is-typed-array": "^1.1.13"
|
||||
},
|
||||
@@ -11110,15 +11148,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typed-array-byte-length": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz",
|
||||
"integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz",
|
||||
"integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"call-bind": "^1.0.7",
|
||||
"for-each": "^0.3.3",
|
||||
"has-proto": "^1.0.1",
|
||||
"is-typed-array": "^1.1.10"
|
||||
"gopd": "^1.0.1",
|
||||
"has-proto": "^1.0.3",
|
||||
"is-typed-array": "^1.1.13"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -11128,16 +11167,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typed-array-byte-offset": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz",
|
||||
"integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz",
|
||||
"integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"available-typed-arrays": "^1.0.5",
|
||||
"call-bind": "^1.0.2",
|
||||
"available-typed-arrays": "^1.0.7",
|
||||
"call-bind": "^1.0.7",
|
||||
"for-each": "^0.3.3",
|
||||
"has-proto": "^1.0.1",
|
||||
"is-typed-array": "^1.1.10"
|
||||
"gopd": "^1.0.1",
|
||||
"has-proto": "^1.0.3",
|
||||
"is-typed-array": "^1.1.13"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -11147,23 +11187,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typed-array-length": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
|
||||
"integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz",
|
||||
"integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"call-bind": "^1.0.7",
|
||||
"for-each": "^0.3.3",
|
||||
"is-typed-array": "^1.1.9"
|
||||
"gopd": "^1.0.1",
|
||||
"has-proto": "^1.0.3",
|
||||
"is-typed-array": "^1.1.13",
|
||||
"possible-typed-array-names": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/typo-js": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.3.tgz",
|
||||
"integrity": "sha512-67Hyl94beZX8gmTap7IDPrG5hy2cHftgsCAcGvE1tzuxGT+kRB+zSBin0wIMwysYw8RUCBCvv9UfQl8TNM75dA==",
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.4.tgz",
|
||||
"integrity": "sha512-Oy/k+tFle5NAA3J/yrrYGfvEnPVrDZ8s8/WCwjUE75k331QyKIsFss7byQ/PzBmXLY6h1moRnZbnaxWBe3I3CA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
@@ -11439,9 +11485,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.90.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz",
|
||||
"integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==",
|
||||
"version": "5.90.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz",
|
||||
"integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"alpine-lazy-load-assets": "^1.2.1",
|
||||
"alpinejs": "^3.10.4",
|
||||
"async-alpine": "^1.2.1",
|
||||
"async-alpine": "v1.2.1",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"body-scroll-lock": "^4.0.0-beta.0",
|
||||
"chart.js": "^3.6.0",
|
||||
@@ -54,7 +54,7 @@
|
||||
"postcss": "^8.4.4",
|
||||
"postcss-nesting": "^12.0.1",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-blade": "^2.0.0",
|
||||
"prettier-plugin-blade": "v2.0.0",
|
||||
"prettier-plugin-tailwindcss": "^0.5.10",
|
||||
"pusher-js": "^7.4.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
"filament/infolists": "self.version",
|
||||
"filament/notifications": "self.version",
|
||||
"filament/support": "self.version",
|
||||
"illuminate/contracts": "^10.0",
|
||||
"illuminate/database": "^10.0",
|
||||
"illuminate/support": "^10.0",
|
||||
"illuminate/contracts": "^10.45|^11.0",
|
||||
"illuminate/database": "^10.45|^11.0",
|
||||
"illuminate/support": "^10.45|^11.0",
|
||||
"league/csv": "^9.14",
|
||||
"openspout/openspout": "^4.23",
|
||||
"spatie/laravel-package-tools": "^1.9"
|
||||
|
||||
@@ -293,7 +293,7 @@ Instead of opening in the center of the screen, the modal content will now slide
|
||||
|
||||
## Making the modal header sticky
|
||||
|
||||
The header of a modal scrolls out of view with the modal content when it overflows the modal size. However, slide-overs have a sticky modal that's always visible. You may control this behavior using `stickyModalHeader()`:
|
||||
The header of a modal scrolls out of view with the modal content when it overflows the modal size. However, slide-overs have a sticky header that's always visible. You may control this behavior using `stickyModalHeader()`:
|
||||
|
||||
```php
|
||||
Action::make('updateAuthor')
|
||||
|
||||
@@ -67,8 +67,6 @@ If you'd like to save time, Filament can automatically generate the [columns](#d
|
||||
php artisan make:filament-importer Product --generate
|
||||
```
|
||||
|
||||
> If your table contains ENUM columns, the `doctrine/dbal` package we use is unable to scan your table and will crash. Hence, Filament is unable to generate the columns for your importer if it contains an ENUM column. Read more about this issue [here](https://github.com/doctrine/dbal/issues/3819#issuecomment-573419808).
|
||||
|
||||
## Defining importer columns
|
||||
|
||||
To define the columns that can be imported, you need to override the `getColumns()` method on your importer class, returning an array of `ImportColumn` objects:
|
||||
@@ -320,6 +318,18 @@ ImportColumn::make('sku')
|
||||
})
|
||||
```
|
||||
|
||||
### Adding helper text below the import column
|
||||
|
||||
Sometimes, you may wish to provide extra information for the user before validation. You can do this by adding `helperText()` to a column, which gets displayed below the mapping select:
|
||||
|
||||
```php
|
||||
use Filament\Forms\Components\TextInput;
|
||||
|
||||
ImportColumn::make('skus')
|
||||
->array(',')
|
||||
->helperText('A comma-separated list of SKUs.')
|
||||
```
|
||||
|
||||
## Updating existing records when importing
|
||||
|
||||
When generating an importer class, you will see this `resolveRecord()` method:
|
||||
|
||||
@@ -82,8 +82,6 @@ If you'd like to save time, Filament can automatically generate the [columns](#d
|
||||
php artisan make:filament-exporter Product --generate
|
||||
```
|
||||
|
||||
> If your table contains ENUM columns, the `doctrine/dbal` package we use is unable to scan your table and will crash. Hence, Filament is unable to generate the columns for your exporter if it contains an ENUM column. Read more about this issue [here](https://github.com/doctrine/dbal/issues/3819#issuecomment-573419808).
|
||||
|
||||
## Defining exporter columns
|
||||
|
||||
To define the columns that can be exported, you need to override the `getColumns()` method on your exporter class, returning an array of `ExportColumn` objects:
|
||||
@@ -124,6 +122,18 @@ ExportColumn::make('description')
|
||||
->enabledByDefault(false)
|
||||
```
|
||||
|
||||
### Disabling column selection
|
||||
|
||||
By default, user will be asked which columns they would like to export. You can disable this functionality using `columnMapping(false)`:
|
||||
|
||||
```php
|
||||
use App\Filament\Exports\ProductExporter;
|
||||
|
||||
ExportAction::make()
|
||||
->exporter(ProductExporter::class)
|
||||
->columnMapping(false)
|
||||
```
|
||||
|
||||
### Calculated export column state
|
||||
|
||||
Sometimes you need to calculate the state of a column, instead of directly reading it from a database column.
|
||||
@@ -495,6 +505,43 @@ public static function getCsvDelimiter(): string
|
||||
|
||||
You can only specify a single character, otherwise an exception will be thrown.
|
||||
|
||||
## Styling XLSX cells
|
||||
|
||||
If you want to style the cells of the XLSX file, you may override the `getXlsxCellStyle()` method on the exporter class, returning an [OpenSpout `Style` object](https://github.com/openspout/openspout/blob/4.x/docs/documentation.md#styling):
|
||||
|
||||
```php
|
||||
use OpenSpout\Common\Entity\Style\Style;
|
||||
|
||||
public function getXlsxCellStyle(): ?Style
|
||||
{
|
||||
return (new Style())
|
||||
->setFontSize(12)
|
||||
->setFontName('Consolas');
|
||||
}
|
||||
```
|
||||
|
||||
If you want to use a different style for the header cells of the XLSX file only, you may override the `getXlsxHeaderCellStyle()` method on the exporter class, returning an [OpenSpout `Style` object](https://github.com/openspout/openspout/blob/4.x/docs/documentation.md#styling):
|
||||
|
||||
```php
|
||||
use OpenSpout\Common\Entity\Style\CellAlignment;
|
||||
use OpenSpout\Common\Entity\Style\CellVerticalAlignment;
|
||||
use OpenSpout\Common\Entity\Style\Color;
|
||||
use OpenSpout\Common\Entity\Style\Style;
|
||||
|
||||
public function getXlsxHeaderCellStyle(): ?Style
|
||||
{
|
||||
return (new Style())
|
||||
->setFontBold()
|
||||
->setFontItalic()
|
||||
->setFontSize(14)
|
||||
->setFontName('Consolas')
|
||||
->setFontColor(Color::rgb(255, 255, 77))
|
||||
->setBackgroundColor(Color::rgb(0, 0, 0))
|
||||
->setCellAlignment(CellAlignment::CENTER)
|
||||
->setCellVerticalAlignment(CellVerticalAlignment::CENTER);
|
||||
}
|
||||
```
|
||||
|
||||
## Customizing the export job
|
||||
|
||||
The default job for processing exports is `Filament\Actions\Exports\Jobs\PrepareCsvExport`. If you want to extend this class and override any of its methods, you may replace the original class in the `register()` method of a service provider:
|
||||
|
||||
@@ -25,7 +25,7 @@ return [
|
||||
],
|
||||
|
||||
'attach_another' => [
|
||||
'label' => 'İlişdir ve başqasına başla',
|
||||
'label' => 'İlişdir və başqasına başla',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -17,7 +17,7 @@ return [
|
||||
],
|
||||
|
||||
'create_another' => [
|
||||
'label' => 'Yarat ve başqasını yarat',
|
||||
'label' => 'Yarat və başqasını yarat',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'İxrac :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'İxrac :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'label' => 'Sütunlar',
|
||||
|
||||
'form' => [
|
||||
|
||||
'is_enabled' => [
|
||||
'label' => ':column aktiv',
|
||||
],
|
||||
|
||||
'label' => [
|
||||
'label' => ':column etiketi',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'export' => [
|
||||
'label' => 'İxrac',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'İxrac tamamlandı',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_csv' => [
|
||||
'label' => '.csv olaraq endir',
|
||||
],
|
||||
|
||||
'download_xlsx' => [
|
||||
'label' => '.xlsx olaraq endir',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'İxrac çox böyükdür',
|
||||
'body' => 'Bir dəfədə 1 sətirdən çox ixrac edə bilməzsiniz.| Bir dəfədə :count sətirdən çox ixrac edə bilməzsiniz.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'İxrac başladı',
|
||||
'body' => 'İxrac başladı və 1 sətir arxa planda işlənəcək.|İxrac başladı və :count sətir arxa planda işlənəcək.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'file_name' => 'export-:export_id-:model',
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'İdxal: :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'İdxal: :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'file' => [
|
||||
'label' => 'Fayl',
|
||||
'placeholder' => 'Bir CSV faylı seçin',
|
||||
],
|
||||
|
||||
'columns' => [
|
||||
'label' => 'Sütunlar',
|
||||
'placeholder' => 'Sütunları uyğunlaşdırın',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_example' => [
|
||||
'label' => 'Nümunə CSV faylını endir',
|
||||
],
|
||||
|
||||
'import' => [
|
||||
'label' => 'İdxal',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'İdxal tamamlandı',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_failed_rows_csv' => [
|
||||
'label' => 'Uğursuz sətir haqqında məlumatları endir|Uğursuz sətirlər haqqında məlumatları endir',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'Seçilən fayl çox böyükdür',
|
||||
'body' => 'Bir dəfədə 1-dən çox sətiri olan faylı idxal edə bilməzsiniz.|Bir dəfədə :count dən çox sətiri olan faylı idxal edə bilməzsiniz.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'İdxal başladı',
|
||||
'body' => 'İdxal başladı və 1 sətir arxa planda işlənəcək.|İdxal başladı və :count sətir arxa planda işlənəcək',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'example_csv' => [
|
||||
'file_name' => ':importer-example',
|
||||
],
|
||||
|
||||
'failure_csv' => [
|
||||
'file_name' => 'import-:import_id-:csv_name-failed-rows',
|
||||
'error_header' => 'xəta',
|
||||
'system_error' => 'Sistem xətası, texniki dəstək ilə əlaqə saxlayın',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'Exporter :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Exporter :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'label' => 'Colonnes',
|
||||
|
||||
'form' => [
|
||||
|
||||
'is_enabled' => [
|
||||
'label' => ':column sélectionnée',
|
||||
],
|
||||
|
||||
'label' => [
|
||||
'label' => ':column label',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'export' => [
|
||||
'label' => 'Exporter',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'Exportation terminée',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_csv' => [
|
||||
'label' => 'Télécharger .csv',
|
||||
],
|
||||
|
||||
'download_xlsx' => [
|
||||
'label' => 'Télécharger .xlsx',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'L\'exportation est trop volumineuse',
|
||||
'body' => 'Vous ne pouvez pas exporter plus d’une ligne à la fois.|Vous ne pouvez pas exporter plus de :count lignes à la fois',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'L’exportation a commencé',
|
||||
'body' => 'Votre exportation a commencé et 1 ligne sera traitée en arrière-plan.|Votre exportation a commencé et :count lignes seront traitées en arrière-plan.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'file_name' => 'export-:export_id-:model',
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'Exportar :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Exportar :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'label' => 'Colunas',
|
||||
|
||||
'form' => [
|
||||
|
||||
'is_enabled' => [
|
||||
'label' => ':column ativa',
|
||||
],
|
||||
|
||||
'label' => [
|
||||
'label' => 'Rótulo para :column',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'export' => [
|
||||
'label' => 'Exportar',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'Exportação completa',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_csv' => [
|
||||
'label' => 'Baixar .csv',
|
||||
],
|
||||
|
||||
'download_xlsx' => [
|
||||
'label' => 'Baixar .xlsx',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'A exportação é muito grande',
|
||||
'body' => 'Não é possível exportar mais de 1 linha de uma vez.|Não é possível exportar mais de :count linhas de uma vez.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'Exportação iniciada',
|
||||
'body' => 'A exportação foi iniciada e 1 linha será processada em segundo plano.|A exportação foi iniciada e :count linhas serão processadas em segundo plano.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'file_name' => 'export-:export_id-:model',
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'Importar :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Importar :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'file' => [
|
||||
'label' => 'Arquivo',
|
||||
'placeholder' => 'Enviar um arquivo CSV',
|
||||
],
|
||||
|
||||
'columns' => [
|
||||
'label' => 'Colunas',
|
||||
'placeholder' => 'Selecione uma coluna',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_example' => [
|
||||
'label' => 'Baixar um arquivo CSV de exemplo',
|
||||
],
|
||||
|
||||
'import' => [
|
||||
'label' => 'Importar',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'Importação completa',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_failed_rows_csv' => [
|
||||
'label' => 'Baixar informações sobre a linha que falhou|Baixar informações sobre as linhas que falharam',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'O arquivo CSV enviado é muito grande',
|
||||
'body' => 'Não é possível importar mais de 1 linha de uma vez.|Não é possível importar mais de :count linhas de uma vez.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'Importação iniciada',
|
||||
'body' => 'A importação foi iniciada e 1 linha será processada em segundo plano.|A importação foi iniciada e :count linhas serão processadas em segundo plano.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'example_csv' => [
|
||||
'file_name' => ':importer-example',
|
||||
],
|
||||
|
||||
'failure_csv' => [
|
||||
'file_name' => 'import-:import_id-:csv_name-failed-rows',
|
||||
'error_header' => 'erro',
|
||||
'system_error' => 'Erro de sistema, por favor, entre em contato com o suporte.',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'Importă :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Importă :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'file' => [
|
||||
'label' => 'Fișier',
|
||||
'placeholder' => 'Încarcă un fișier CSV',
|
||||
],
|
||||
|
||||
'columns' => [
|
||||
'label' => 'Coloane',
|
||||
'placeholder' => 'Alege Coloanele',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_example' => [
|
||||
'label' => 'Descarcă exemplu în format CSV',
|
||||
],
|
||||
|
||||
'import' => [
|
||||
'label' => 'Importă',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'Import finalizat',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_failed_rows_csv' => [
|
||||
'label' => 'Descarcă informațiiler despre câmpul ce a avut eroaro|Descarcă informații despre câmpurile ce au auvt erori',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'Fișierul CSV file este prea mare',
|
||||
'body' => 'Nu este permis importul a mai mult de o linile într-un singur fișier.|Nu este permis imporutl a mai mult de :count linii într-un singur fișier.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'Importul a început',
|
||||
'body' => 'Importul dumneavostră a început, iar rândul va fi procesa în fundal.|Importul dumneavoastră a început, iar cele :count rânduri vor fi procesate în fundal.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'example_csv' => [
|
||||
'file_name' => ':importer-example',
|
||||
],
|
||||
|
||||
'failure_csv' => [
|
||||
'file_name' => 'import-:import_id-:csv_name-câmpuri-gresite',
|
||||
'error_header' => 'error',
|
||||
'system_error' => 'Eroare de sistem, vă rugăm contactați echipa de suport.',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -6,8 +6,8 @@ use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/filament/exports/{export}/download', DownloadExport::class)
|
||||
->name('filament.exports.download')
|
||||
->middleware(['web', 'auth']);
|
||||
->middleware('filament.actions');
|
||||
|
||||
Route::get('/filament/imports/{import}/failed-rows/download', DownloadImportFailureCsv::class)
|
||||
->name('filament.imports.failed-rows.download')
|
||||
->middleware(['web', 'auth']);
|
||||
->middleware('filament.actions');
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Filament\Actions;
|
||||
|
||||
use Filament\Actions\Testing\TestsActions;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Routing\Router;
|
||||
use Livewire\Features\SupportTesting\Testable;
|
||||
use Spatie\LaravelPackageTools\Package;
|
||||
use Spatie\LaravelPackageTools\PackageServiceProvider;
|
||||
@@ -25,6 +26,11 @@ class ActionsServiceProvider extends PackageServiceProvider
|
||||
->hasViews();
|
||||
}
|
||||
|
||||
public function packageRegistered(): void
|
||||
{
|
||||
app(Router::class)->middlewareGroup('filament.actions', ['web', 'auth']);
|
||||
}
|
||||
|
||||
public function packageBooted(): void
|
||||
{
|
||||
if ($this->app->runningInConsole()) {
|
||||
|
||||
@@ -14,17 +14,12 @@ trait CanGenerateExporterColumns
|
||||
return '//';
|
||||
}
|
||||
|
||||
$schema = $this->getModelSchema($model);
|
||||
$table = $this->getModelTable($model);
|
||||
|
||||
if (blank($table)) {
|
||||
return '//';
|
||||
}
|
||||
|
||||
$columns = [];
|
||||
|
||||
foreach ($table->getColumns() as $column) {
|
||||
$columnName = $column->getName();
|
||||
|
||||
foreach ($schema->getColumnListing($table) as $columnName) {
|
||||
if (str($columnName)->endsWith([
|
||||
'_token',
|
||||
])) {
|
||||
@@ -38,10 +33,10 @@ trait CanGenerateExporterColumns
|
||||
}
|
||||
|
||||
if (str($columnName)->endsWith('_id')) {
|
||||
$guessedRelationshipName = $this->guessBelongsToRelationshipName($column, $model);
|
||||
$guessedRelationshipName = $this->guessBelongsToRelationshipName($columnName, $model);
|
||||
|
||||
if (filled($guessedRelationshipName)) {
|
||||
$guessedRelationshipTitleColumnName = $this->guessBelongsToRelationshipTitleColumnName($column, app($model)->{$guessedRelationshipName}()->getModel()::class);
|
||||
$guessedRelationshipTitleColumnName = $this->guessBelongsToRelationshipTitleColumnName($columnName, app($model)->{$guessedRelationshipName}()->getModel()::class);
|
||||
|
||||
$columnName = "{$guessedRelationshipName}.{$guessedRelationshipTitleColumnName}";
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Filament\Actions\Commands\Concerns;
|
||||
|
||||
use Doctrine\DBAL\Types;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait CanGenerateImporterColumns
|
||||
@@ -15,20 +14,17 @@ trait CanGenerateImporterColumns
|
||||
return '//';
|
||||
}
|
||||
|
||||
$schema = $this->getModelSchema($model);
|
||||
$table = $this->getModelTable($model);
|
||||
|
||||
if (blank($table)) {
|
||||
return '//';
|
||||
}
|
||||
|
||||
$columns = [];
|
||||
|
||||
foreach ($table->getColumns() as $column) {
|
||||
if ($column->getAutoincrement()) {
|
||||
foreach ($schema->getColumns($table) as $column) {
|
||||
if ($column['auto_increment']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columnName = $column->getName();
|
||||
$columnName = $column['name'];
|
||||
|
||||
if (str($columnName)->is([
|
||||
app($model)->getKeyName(),
|
||||
@@ -50,7 +46,7 @@ trait CanGenerateImporterColumns
|
||||
$columnData['label'] = [Str::upper($columnName)];
|
||||
}
|
||||
|
||||
if ($column->getNotnull()) {
|
||||
if (! $column['nullable']) {
|
||||
$columnData['rules'][0][] = 'required';
|
||||
$columnData['requiredMapping'] = [];
|
||||
}
|
||||
@@ -59,44 +55,39 @@ trait CanGenerateImporterColumns
|
||||
$columnData['rules'][0][] = 'email';
|
||||
}
|
||||
|
||||
$type = $this->parseColumnType($column);
|
||||
|
||||
if (
|
||||
str($columnName)->endsWith('_id') &&
|
||||
filled($guessedRelationshipName = $this->guessBelongsToRelationshipName($column, $model))
|
||||
filled($guessedRelationshipName = $this->guessBelongsToRelationshipName($columnName, $model))
|
||||
) {
|
||||
$columnName = $guessedRelationshipName;
|
||||
$columnData['relationship'] = [];
|
||||
} elseif (in_array($column->getType()::class, [
|
||||
Types\BooleanType::class,
|
||||
} elseif (in_array($type['name'], [
|
||||
'boolean',
|
||||
])) {
|
||||
$columnData['rules'][0][] = 'boolean';
|
||||
$columnData['boolean'] = [];
|
||||
} elseif (in_array($column->getType()::class, [
|
||||
Types\DateImmutableType::class,
|
||||
Types\DateType::class,
|
||||
} elseif (in_array($type['name'], [
|
||||
'date',
|
||||
])) {
|
||||
$columnData['rules'][0][] = 'date';
|
||||
} elseif (in_array($column->getType()::class, [
|
||||
Types\DateTimeImmutableType::class,
|
||||
Types\DateTimeType::class,
|
||||
Types\DateTimeTzImmutableType::class,
|
||||
Types\DateTimeTzType::class,
|
||||
} elseif (in_array($type['name'], [
|
||||
'datetime',
|
||||
'timestamp',
|
||||
])) {
|
||||
$columnData['rules'][0][] = 'datetime';
|
||||
} elseif (in_array($column->getType()::class, [
|
||||
Types\IntegerType::class,
|
||||
Types\SmallIntType::class,
|
||||
Types\BigIntType::class,
|
||||
} elseif (in_array($type['name'], [
|
||||
'integer',
|
||||
'decimal',
|
||||
'float',
|
||||
'double',
|
||||
'money',
|
||||
])) {
|
||||
$columnData['rules'][0][] = 'integer';
|
||||
$columnData['numeric'] = [];
|
||||
} elseif (in_array($column->getType()::class, [
|
||||
Types\DecimalType::class,
|
||||
Types\FloatType::class,
|
||||
])) {
|
||||
$columnData['rules'][0][] = 'integer';
|
||||
$columnData['numeric'] = [];
|
||||
} elseif ($length = $column->getLength()) {
|
||||
$columnData['rules'][0][] = "max:{$length}";
|
||||
} elseif (isset($type['length'])) {
|
||||
$columnData['rules'][0][] = "max:{$type['length']}";
|
||||
}
|
||||
|
||||
// Move rules to the end of the column definition.
|
||||
|
||||
@@ -25,11 +25,10 @@ use Illuminate\Foundation\Bus\PendingChain;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Number;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
|
||||
use function Filament\Support\format_number;
|
||||
|
||||
trait CanExportRecords
|
||||
{
|
||||
/**
|
||||
@@ -61,6 +60,8 @@ trait CanExportRecords
|
||||
|
||||
protected ?Closure $modifyQueryUsing = null;
|
||||
|
||||
protected bool | Closure $hasColumnMapping = true;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
@@ -73,8 +74,8 @@ trait CanExportRecords
|
||||
|
||||
$this->groupedIcon(FilamentIcon::resolve('actions::export-action.grouped') ?? 'heroicon-m-arrow-down-tray');
|
||||
|
||||
$this->form(fn (ExportAction | ExportTableAction | ExportTableBulkAction $action): array => array_merge([
|
||||
Fieldset::make(__('filament-actions::export.modal.form.columns.label'))
|
||||
$this->form(fn (ExportAction | ExportTableAction | ExportTableBulkAction $action): array => [
|
||||
...($action->hasColumnMapping() ? [Fieldset::make(__('filament-actions::export.modal.form.columns.label'))
|
||||
->columns(1)
|
||||
->inlineLabel()
|
||||
->schema(function () use ($action): array {
|
||||
@@ -99,8 +100,9 @@ trait CanExportRecords
|
||||
$action->getExporter()::getColumns(),
|
||||
);
|
||||
})
|
||||
->statePath('columnMap'),
|
||||
], $action->getExporter()::getOptionsFormComponents()));
|
||||
->statePath('columnMap')] : []),
|
||||
...$action->getExporter()::getOptionsFormComponents(),
|
||||
]);
|
||||
|
||||
$this->action(function (ExportAction | ExportTableAction | ExportTableBulkAction $action, array $data, Component $livewire) {
|
||||
if ($livewire instanceof HasTable) {
|
||||
@@ -124,7 +126,7 @@ trait CanExportRecords
|
||||
Notification::make()
|
||||
->title(__('filament-actions::export.notifications.max_rows.title'))
|
||||
->body(trans_choice('filament-actions::export.notifications.max_rows.body', $maxRows, [
|
||||
'count' => format_number($maxRows),
|
||||
'count' => Number::format($maxRows),
|
||||
]))
|
||||
->danger()
|
||||
->send();
|
||||
@@ -139,14 +141,20 @@ trait CanExportRecords
|
||||
Arr::except($data, ['columnMap']),
|
||||
);
|
||||
|
||||
$columnMap = collect($data['columnMap'])
|
||||
->dot()
|
||||
->reduce(fn (Collection $carry, mixed $value, string $key): Collection => $carry->mergeRecursive([
|
||||
Str::beforeLast($key, '.') => [Str::afterLast($key, '.') => $value],
|
||||
]), collect())
|
||||
->filter(fn (array $column): bool => $column['isEnabled'] ?? false)
|
||||
->mapWithKeys(fn (array $column, string $columnName): array => [$columnName => $column['label']])
|
||||
->all();
|
||||
if ($action->hasColumnMapping()) {
|
||||
$columnMap = collect($data['columnMap'])
|
||||
->dot()
|
||||
->reduce(fn (Collection $carry, mixed $value, string $key): Collection => $carry->mergeRecursive([
|
||||
Str::beforeLast($key, '.') => [Str::afterLast($key, '.') => $value],
|
||||
]), collect())
|
||||
->filter(fn (array $column): bool => $column['isEnabled'] ?? false)
|
||||
->mapWithKeys(fn (array $column, string $columnName): array => [$columnName => $column['label']])
|
||||
->all();
|
||||
} else {
|
||||
$columnMap = collect($action->getExporter()::getColumns())
|
||||
->mapWithKeys(fn (ExportColumn $column): array => [$column->getName() => $column->getLabel()])
|
||||
->all();
|
||||
}
|
||||
|
||||
$export = app(Export::class);
|
||||
$export->user()->associate($user);
|
||||
@@ -230,7 +238,7 @@ trait CanExportRecords
|
||||
Notification::make()
|
||||
->title($action->getSuccessNotificationTitle())
|
||||
->body(trans_choice('filament-actions::export.notifications.started.body', $export->total_rows, [
|
||||
'count' => format_number($export->total_rows),
|
||||
'count' => Number::format($export->total_rows),
|
||||
]))
|
||||
->success()
|
||||
->send();
|
||||
@@ -387,4 +395,16 @@ trait CanExportRecords
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function columnMapping(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->hasColumnMapping = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasColumnMapping(): bool
|
||||
{
|
||||
return (bool) $this->evaluate($this->hasColumnMapping);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ use Illuminate\Filesystem\AwsS3V3Adapter;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Number;
|
||||
use League\Csv\Info;
|
||||
use League\Csv\Reader as CsvReader;
|
||||
use League\Csv\Statement;
|
||||
@@ -34,8 +35,6 @@ use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
use SplTempFileObject;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
use function Filament\Support\format_number;
|
||||
|
||||
trait CanImportRecords
|
||||
{
|
||||
/**
|
||||
@@ -181,7 +180,7 @@ trait CanImportRecords
|
||||
Notification::make()
|
||||
->title(__('filament-actions::import.notifications.max_rows.title'))
|
||||
->body(trans_choice('filament-actions::import.notifications.max_rows.body', $maxRows, [
|
||||
'count' => format_number($maxRows),
|
||||
'count' => Number::format($maxRows),
|
||||
]))
|
||||
->danger()
|
||||
->send();
|
||||
@@ -271,7 +270,7 @@ trait CanImportRecords
|
||||
fn (Notification $notification) => $notification->actions([
|
||||
NotificationAction::make('downloadFailedRowsCsv')
|
||||
->label(trans_choice('filament-actions::import.notifications.completed.actions.download_failed_rows_csv.label', $failedRowsCount, [
|
||||
'count' => format_number($failedRowsCount),
|
||||
'count' => Number::format($failedRowsCount),
|
||||
]))
|
||||
->color('danger')
|
||||
->url(route('filament.imports.failed-rows.download', ['import' => $import], absolute: false), shouldOpenInNewTab: true)
|
||||
@@ -285,7 +284,7 @@ trait CanImportRecords
|
||||
Notification::make()
|
||||
->title($action->getSuccessNotificationTitle())
|
||||
->body(trans_choice('filament-actions::import.notifications.started.body', $import->total_rows, [
|
||||
'count' => format_number($import->total_rows),
|
||||
'count' => Number::format($import->total_rows),
|
||||
]))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Actions\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
trait CanUseDatabaseTransactions
|
||||
{
|
||||
protected bool | Closure $hasDatabaseTransactions = false;
|
||||
|
||||
public function databaseTransaction(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->hasDatabaseTransactions = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasDatabaseTransactions(): bool
|
||||
{
|
||||
return (bool) $this->evaluate($this->hasDatabaseTransactions);
|
||||
}
|
||||
|
||||
public function beginDatabaseTransaction(): void
|
||||
{
|
||||
if (! $this->hasDatabaseTransactions()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
}
|
||||
|
||||
public function commitDatabaseTransaction(): void
|
||||
{
|
||||
if (! $this->hasDatabaseTransactions()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
}
|
||||
|
||||
public function rollBackDatabaseTransaction(): void
|
||||
{
|
||||
if (! $this->hasDatabaseTransactions()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::rollBack();
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use InvalidArgumentException;
|
||||
use Livewire\Attributes\Url;
|
||||
use Throwable;
|
||||
|
||||
use function Livewire\store;
|
||||
|
||||
@@ -79,6 +80,8 @@ trait InteractsWithActions
|
||||
$originallyMountedActions = $this->mountedActions;
|
||||
|
||||
try {
|
||||
$action->beginDatabaseTransaction();
|
||||
|
||||
if ($this->mountedActionHasForm()) {
|
||||
$action->callBeforeFormValidated();
|
||||
|
||||
@@ -96,10 +99,21 @@ trait InteractsWithActions
|
||||
$result = $action->callAfter() ?? $result;
|
||||
|
||||
$this->afterActionCalled();
|
||||
|
||||
$action->commitDatabaseTransaction();
|
||||
} catch (Halt $exception) {
|
||||
$exception->shouldRollbackDatabaseTransaction() ?
|
||||
$action->rollBackDatabaseTransaction() :
|
||||
$action->commitDatabaseTransaction();
|
||||
|
||||
return null;
|
||||
} catch (Cancel $exception) {
|
||||
$exception->shouldRollbackDatabaseTransaction() ?
|
||||
$action->rollBackDatabaseTransaction() :
|
||||
$action->commitDatabaseTransaction();
|
||||
} catch (ValidationException $exception) {
|
||||
$action->rollBackDatabaseTransaction();
|
||||
|
||||
if (! $this->mountedActionShouldOpenModal()) {
|
||||
$action->resetArguments();
|
||||
$action->resetFormData();
|
||||
@@ -107,6 +121,10 @@ trait InteractsWithActions
|
||||
$this->unmountAction();
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
} catch (Throwable $exception) {
|
||||
$action->rollBackDatabaseTransaction();
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ class CreateAction extends Action
|
||||
|
||||
$this->record(null);
|
||||
|
||||
$this->databaseTransaction();
|
||||
|
||||
$this->action(function (array $arguments, Form $form): void {
|
||||
$model = $this->getModel();
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ class EditAction extends Action
|
||||
return $data;
|
||||
});
|
||||
|
||||
$this->databaseTransaction();
|
||||
|
||||
$this->action(function (): void {
|
||||
$this->process(function (array $data, HasActions $livewire, Model $record) {
|
||||
if ($translatableContentDriver = $livewire->makeFilamentTranslatableContentDriver()) {
|
||||
|
||||
@@ -8,6 +8,7 @@ use Filament\Actions\Exports\Models\Export;
|
||||
use Filament\Forms\Components\Component;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use OpenSpout\Common\Entity\Style\Style;
|
||||
|
||||
abstract class Exporter
|
||||
{
|
||||
@@ -166,4 +167,14 @@ abstract class Exporter
|
||||
{
|
||||
return [ExportFormat::Csv, ExportFormat::Xlsx];
|
||||
}
|
||||
|
||||
public function getXlsxCellStyle(): ?Style
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getXlsxHeaderCellStyle(): ?Style
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use Illuminate\Queue\SerializesModels;
|
||||
use League\Csv\Reader as CsvReader;
|
||||
use League\Csv\Statement;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Common\Entity\Style\Style;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
|
||||
class CreateXlsxFile implements ShouldQueue
|
||||
@@ -51,17 +52,22 @@ class CreateXlsxFile implements ShouldQueue
|
||||
|
||||
$csvDelimiter = $this->exporter::getCsvDelimiter();
|
||||
|
||||
$writeRowsFromFile = function (string $file) use ($csvDelimiter, $disk, $writer) {
|
||||
$writeRowsFromFile = function (string $file, ?Style $style = null) use ($csvDelimiter, $disk, $writer) {
|
||||
$csvReader = CsvReader::createFromStream($disk->readStream($file));
|
||||
$csvReader->setDelimiter($csvDelimiter);
|
||||
$csvResults = Statement::create()->process($csvReader);
|
||||
|
||||
foreach ($csvResults->getRecords() as $row) {
|
||||
$writer->addRow(Row::fromValues($row));
|
||||
$writer->addRow(Row::fromValues($row, $style));
|
||||
}
|
||||
};
|
||||
|
||||
$writeRowsFromFile($this->export->getFileDirectory() . DIRECTORY_SEPARATOR . 'headers.csv');
|
||||
$cellStyle = $this->exporter->getXlsxCellStyle();
|
||||
|
||||
$writeRowsFromFile(
|
||||
$this->export->getFileDirectory() . DIRECTORY_SEPARATOR . 'headers.csv',
|
||||
$this->exporter->getXlsxHeaderCellStyle() ?? $cellStyle,
|
||||
);
|
||||
|
||||
foreach ($disk->files($this->export->getFileDirectory()) as $file) {
|
||||
if (str($file)->endsWith('headers.csv')) {
|
||||
@@ -72,7 +78,7 @@ class CreateXlsxFile implements ShouldQueue
|
||||
continue;
|
||||
}
|
||||
|
||||
$writeRowsFromFile($file);
|
||||
$writeRowsFromFile($file, $cellStyle);
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Filament\Actions\Imports;
|
||||
use Closure;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Support\Components\Component;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
@@ -70,6 +71,8 @@ class ImportColumn extends Component
|
||||
|
||||
protected string $evaluationIdentifier = 'column';
|
||||
|
||||
protected string | Htmlable | Closure | null $helperText = null;
|
||||
|
||||
final public function __construct(string $name)
|
||||
{
|
||||
$this->name($name);
|
||||
@@ -88,7 +91,8 @@ class ImportColumn extends Component
|
||||
return Select::make($this->getName())
|
||||
->label($this->getLabel())
|
||||
->placeholder(__('filament-actions::import.modal.form.columns.placeholder'))
|
||||
->required($this->isMappingRequired());
|
||||
->required($this->isMappingRequired())
|
||||
->helperText($this->helperText);
|
||||
}
|
||||
|
||||
public function name(string $name): static
|
||||
@@ -134,6 +138,13 @@ class ImportColumn extends Component
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function helperText(string | Htmlable | Closure | null $text): static
|
||||
{
|
||||
$this->helperText = $text;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function integer(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->numeric($condition, decimalPlaces: 0);
|
||||
|
||||
@@ -80,7 +80,12 @@ abstract class Importer
|
||||
|
||||
foreach ($this->getCachedColumns() as $column) {
|
||||
$columnName = $column->getName();
|
||||
$rowColumnName = $this->columnMap[$columnName] ?? null;
|
||||
|
||||
if (blank($this->columnMap[$columnName] ?? null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rowColumnName = $this->columnMap[$columnName];
|
||||
|
||||
if (! array_key_exists($rowColumnName, $this->data)) {
|
||||
continue;
|
||||
@@ -141,6 +146,10 @@ abstract class Importer
|
||||
foreach ($this->getCachedColumns() as $column) {
|
||||
$columnName = $column->getName();
|
||||
|
||||
if (blank($this->columnMap[$columnName] ?? null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rules[$columnName] = $column->getDataValidationRules();
|
||||
|
||||
if (
|
||||
@@ -170,13 +179,19 @@ abstract class Importer
|
||||
$attributes = [];
|
||||
|
||||
foreach ($this->getCachedColumns() as $column) {
|
||||
$columnName = $column->getName();
|
||||
|
||||
if (blank($this->columnMap[$columnName] ?? null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$validationAttribute = $column->getValidationAttribute();
|
||||
|
||||
if (blank($validationAttribute)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributes[$column->getName()] = $validationAttribute;
|
||||
$attributes[$columnName] = $validationAttribute;
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
@@ -187,6 +202,10 @@ abstract class Importer
|
||||
foreach ($this->getCachedColumns() as $column) {
|
||||
$columnName = $column->getName();
|
||||
|
||||
if (blank($this->columnMap[$columnName] ?? null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! array_key_exists($columnName, $this->data)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ abstract class MountableAction extends StaticAction implements HasLivewire
|
||||
use Concerns\CanOpenModal;
|
||||
use Concerns\CanRedirect;
|
||||
use Concerns\CanRequireConfirmation;
|
||||
use Concerns\CanUseDatabaseTransactions;
|
||||
use Concerns\HasForm;
|
||||
use Concerns\HasInfolist;
|
||||
use Concerns\HasLifecycleHooks;
|
||||
@@ -39,14 +40,14 @@ abstract class MountableAction extends StaticAction implements HasLivewire
|
||||
return $this->evaluate($this->getActionFunction(), $parameters);
|
||||
}
|
||||
|
||||
public function cancel(): void
|
||||
public function cancel(bool $shouldRollBackDatabaseTransaction = false): void
|
||||
{
|
||||
throw new Cancel();
|
||||
throw (new Cancel())->rollBackDatabaseTransaction($shouldRollBackDatabaseTransaction);
|
||||
}
|
||||
|
||||
public function halt(): void
|
||||
public function halt(bool $shouldRollBackDatabaseTransaction = false): void
|
||||
{
|
||||
throw new Halt();
|
||||
throw (new Halt())->rollBackDatabaseTransaction($shouldRollBackDatabaseTransaction);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<?php
|
||||
|
||||
use Closure;
|
||||
|
||||
namespace Livewire\Features\SupportTesting {
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Closure;
|
||||
|
||||
class Testable {
|
||||
public function fillForm(array $state = [], string $formName = 'form'): static {}
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
"danharrin/date-format-converter": "^0.3",
|
||||
"filament/actions": "self.version",
|
||||
"filament/support": "self.version",
|
||||
"illuminate/console": "^10.0",
|
||||
"illuminate/contracts": "^10.0",
|
||||
"illuminate/database": "^10.0",
|
||||
"illuminate/filesystem": "^10.0",
|
||||
"illuminate/support": "^10.0",
|
||||
"illuminate/validation": "^10.0",
|
||||
"illuminate/view": "^10.0",
|
||||
"illuminate/console": "^10.45|^11.0",
|
||||
"illuminate/contracts": "^10.45|^11.0",
|
||||
"illuminate/database": "^10.45|^11.0",
|
||||
"illuminate/filesystem": "^10.45|^11.0",
|
||||
"illuminate/support": "^10.45|^11.0",
|
||||
"illuminate/validation": "^10.45|^11.0",
|
||||
"illuminate/view": "^10.45|^11.0",
|
||||
"spatie/laravel-package-tools": "^1.9"
|
||||
},
|
||||
"autoload": {
|
||||
|
||||
+7
-7
File diff suppressed because one or more lines are too long
@@ -264,13 +264,27 @@ Select::make('author_id')
|
||||
->searchable(['first_name', 'last_name'])
|
||||
```
|
||||
|
||||
### Saving pivot data to the relationship
|
||||
|
||||
If you're using a `multiple()` relationship and your pivot table has additional columns, you can use the `pivotData()` method to specify the data that should be saved in them:
|
||||
|
||||
```php
|
||||
use Filament\Forms\Components\Select;
|
||||
|
||||
Select::make('primaryTechnologies')
|
||||
->relationship(name: 'technologies', titleAttribute: 'name')
|
||||
->multiple()
|
||||
->pivotData([
|
||||
'is_primary' => true,
|
||||
])
|
||||
```
|
||||
|
||||
### Creating a new option in a modal
|
||||
|
||||
You may define a custom form that can be used to create a new record and attach it to the `BelongsTo` relationship:
|
||||
|
||||
```php
|
||||
use Filament\Forms\Components\Select;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
Select::make('author_id')
|
||||
->relationship(name: 'author', titleAttribute: 'name')
|
||||
@@ -289,13 +303,29 @@ The form opens in a modal, where the user can fill it with data. Upon form submi
|
||||
|
||||
<AutoScreenshot name="forms/fields/select/create-option-modal" alt="Select with create option modal" version="3.x" />
|
||||
|
||||
#### Customizing new option creation
|
||||
|
||||
You can customize the creation process of the new option defined in the form using the `createOptionUsing()` method, which should return the primary key of the newly created record:
|
||||
|
||||
```php
|
||||
use Filament\Forms\Components\Select;
|
||||
|
||||
Select::make('author_id')
|
||||
->relationship(name: 'author', titleAttribute: 'name')
|
||||
->createOptionForm([
|
||||
// ...
|
||||
])
|
||||
->createOptionUsing(function (array $data): int {
|
||||
return auth()->user()->team->members()->create($data)->getKey();
|
||||
}),
|
||||
```
|
||||
|
||||
### Editing the selected option in a modal
|
||||
|
||||
You may define a custom form that can be used to edit the selected record and save it back to the `BelongsTo` relationship:
|
||||
|
||||
```php
|
||||
use Filament\Forms\Components\Select;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
Select::make('author_id')
|
||||
->relationship(name: 'author', titleAttribute: 'name')
|
||||
|
||||
@@ -240,6 +240,20 @@ CheckboxList::make('authors')
|
||||
->getOptionLabelFromRecordUsing(fn (Model $record) => "{$record->first_name} {$record->last_name}")
|
||||
```
|
||||
|
||||
### Saving pivot data to the relationship
|
||||
|
||||
If your pivot table has additional columns, you can use the `pivotData()` method to specify the data that should be saved in them:
|
||||
|
||||
```php
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
|
||||
CheckboxList::make('primaryTechnologies')
|
||||
->relationship(name: 'technologies', titleAttribute: 'name')
|
||||
->pivotData([
|
||||
'is_primary' => true,
|
||||
])
|
||||
```
|
||||
|
||||
## Setting a custom no search results message
|
||||
|
||||
When you're using a searchable checkbox list, you may want to display a custom message when no search results are found. You can do this using the `noSearchResultsMessage()` method:
|
||||
|
||||
@@ -234,7 +234,7 @@ Repeater::make('qualifications')
|
||||
|
||||
### Reordering items in a relationship
|
||||
|
||||
By default, [reordering](#reordering-items) relationship repeater items is disabled. This is because your related model needs an `sort` column to store the order of related records. To enable reordering, you may use the `orderColumn()` method, passing in a name of the column on your related model to store the order in:
|
||||
By default, [reordering](#reordering-items) relationship repeater items is disabled. This is because your related model needs a `sort` column to store the order of related records. To enable reordering, you may use the `orderColumn()` method, passing in a name of the column on your related model to store the order in:
|
||||
|
||||
```php
|
||||
use Filament\Forms\Components\Repeater;
|
||||
@@ -287,7 +287,7 @@ class OrderProduct extends Pivot
|
||||
{
|
||||
return $this->belongsTo(Order::class);
|
||||
}
|
||||
|
||||
|
||||
public function product(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
@@ -634,7 +634,7 @@ Repeater::make('members')
|
||||
->icon('heroicon-m-envelope')
|
||||
->action(function (array $arguments, Repeater $component): void {
|
||||
$itemData = $component->getItemState($arguments['item']);
|
||||
|
||||
|
||||
Mail::to($itemData['email'])
|
||||
->send(
|
||||
// ...
|
||||
|
||||
@@ -26,7 +26,7 @@ This assumes that you have a `resources/views/filament/forms/components/wizard.b
|
||||
|
||||
You may create your own custom component classes and views, which you can reuse across your project, and even release as a plugin to the community.
|
||||
|
||||
> If you're just creating a simple custom component to use once, you could instead use a [view component](#view) to render any custom Blade file.
|
||||
> If you're just creating a simple custom component to use once, you could instead use a [view component](#view-components) to render any custom Blade file.
|
||||
|
||||
To create a custom column class and view, you may use the following command:
|
||||
|
||||
|
||||
@@ -475,12 +475,21 @@ Field::make('email')
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### ULID
|
||||
|
||||
The field under validation must be a valid [Universally Unique Lexicographically Sortable Identifier](https://github.com/ulid/spec) (ULID). [See the Laravel documentation.](https://laravel.com/docs/validation#rule-ulid)
|
||||
|
||||
```php
|
||||
Field::make('identifier')->ulid()
|
||||
```
|
||||
|
||||
### UUID
|
||||
|
||||
The field must be a valid RFC 4122 (version 1, 3, 4, or 5) universally unique identifier (UUID). [See the Laravel documentation.](https://laravel.com/docs/validation#rule-uuid)
|
||||
|
||||
```php
|
||||
Field::make('identifer')->uuid()
|
||||
Field::make('identifier')->uuid()
|
||||
```
|
||||
|
||||
## Other rules
|
||||
|
||||
@@ -329,5 +329,3 @@ Filament is also able to guess which form fields you want in the schema, based o
|
||||
```bash
|
||||
php artisan make:livewire-form Products/CreateProduct --generate
|
||||
```
|
||||
|
||||
> If your table contains ENUM columns, the `doctrine/dbal` package we use is unable to scan your table and will crash. Hence, Filament is unable to generate the schema for your table if it contains an ENUM column. Read more about this issue [here](https://github.com/doctrine/dbal/issues/3819#issuecomment-573419808).
|
||||
|
||||
@@ -189,7 +189,7 @@ return [
|
||||
|
||||
'messages' => [
|
||||
'confirmation' => 'Não é recomendado editar ficheiros SVG, pois pode resultar em perda de qualidade ao redimensionar.\n Tem a certeza de que deseja prosseguir?',
|
||||
'disabled' => 'A edição de ficheiros SVG está desativada, pois pode resultar em perda de qualidade ao redimensionar.',
|
||||
'disabled' => 'A edição de ficheiros SVG está desactivada, pois pode resultar em perda de qualidade ao redimensionar.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Filament\Forms\Commands\Concerns;
|
||||
|
||||
use Doctrine\DBAL\Types;
|
||||
use Filament\Forms;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -16,20 +15,17 @@ trait CanGenerateForms
|
||||
return '//';
|
||||
}
|
||||
|
||||
$schema = $this->getModelSchema($model);
|
||||
$table = $this->getModelTable($model);
|
||||
|
||||
if (blank($table)) {
|
||||
return '//';
|
||||
}
|
||||
|
||||
$components = [];
|
||||
|
||||
foreach ($table->getColumns() as $column) {
|
||||
if ($column->getAutoincrement()) {
|
||||
foreach ($schema->getColumns($table) as $column) {
|
||||
if ($column['auto_increment']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columnName = $column->getName();
|
||||
$columnName = $column['name'];
|
||||
|
||||
if (str($columnName)->is([
|
||||
app($model)->getKeyName(),
|
||||
@@ -41,22 +37,24 @@ trait CanGenerateForms
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $this->parseColumnType($column);
|
||||
|
||||
$componentData = [];
|
||||
|
||||
$componentData['type'] = match (true) {
|
||||
$column->getType()::class === Types\BooleanType::class => Forms\Components\Toggle::class,
|
||||
in_array($column->getType()::class, [Types\DateImmutableType::class, Types\DateType::class]) => Forms\Components\DatePicker::class,
|
||||
in_array($column->getType()::class, [Types\DateTimeImmutableType::class, Types\DateTimeType::class, Types\DateTimeTzImmutableType::class, Types\DateTimeTzType::class]) => Forms\Components\DateTimePicker::class,
|
||||
$column->getType()::class === Types\TextType::class => Forms\Components\Textarea::class,
|
||||
$type['name'] === 'boolean' => Forms\Components\Toggle::class,
|
||||
$type['name'] === 'date' => Forms\Components\DatePicker::class,
|
||||
in_array($type['name'], ['datetime', 'timestamp']) => Forms\Components\DateTimePicker::class,
|
||||
$type['name'] === 'text' => Forms\Components\Textarea::class,
|
||||
$columnName === 'image', str($columnName)->startsWith('image_'), str($columnName)->contains('_image_'), str($columnName)->endsWith('_image') => Forms\Components\FileUpload::class,
|
||||
default => Forms\Components\TextInput::class,
|
||||
};
|
||||
|
||||
if (str($columnName)->endsWith('_id')) {
|
||||
$guessedRelationshipName = $this->guessBelongsToRelationshipName($column, $model);
|
||||
$guessedRelationshipName = $this->guessBelongsToRelationshipName($columnName, $model);
|
||||
|
||||
if (filled($guessedRelationshipName)) {
|
||||
$guessedRelationshipTitleColumnName = $this->guessBelongsToRelationshipTitleColumnName($column, app($model)->{$guessedRelationshipName}()->getModel()::class);
|
||||
$guessedRelationshipTitleColumnName = $this->guessBelongsToRelationshipTitleColumnName($columnName, app($model)->{$guessedRelationshipName}()->getModel()::class);
|
||||
|
||||
$componentData['type'] = Forms\Components\Select::class;
|
||||
$componentData['relationship'] = [$guessedRelationshipName, $guessedRelationshipTitleColumnName];
|
||||
@@ -89,40 +87,40 @@ trait CanGenerateForms
|
||||
$componentData['image'] = [];
|
||||
}
|
||||
|
||||
if ($column->getNotnull()) {
|
||||
if (! $column['nullable']) {
|
||||
$componentData['required'] = [];
|
||||
}
|
||||
|
||||
if (in_array($column->getType()::class, [
|
||||
Types\BigIntType::class,
|
||||
Types\DecimalType::class,
|
||||
Types\FloatType::class,
|
||||
Types\IntegerType::class,
|
||||
Types\SmallIntType::class,
|
||||
if (in_array($type['name'], [
|
||||
'integer',
|
||||
'decimal',
|
||||
'float',
|
||||
'double',
|
||||
'money',
|
||||
])) {
|
||||
if ($componentData['type'] === Forms\Components\TextInput::class) {
|
||||
$componentData['numeric'] = [];
|
||||
}
|
||||
|
||||
if (filled($column->getDefault())) {
|
||||
$componentData['default'] = [$column->getDefault()];
|
||||
if (filled($column['default'])) {
|
||||
$componentData['default'] = [$this->parseDefaultExpression($column, $model)];
|
||||
}
|
||||
|
||||
if (in_array($columnName, [
|
||||
'cost',
|
||||
'money',
|
||||
'price',
|
||||
])) {
|
||||
]) || $type['name'] === 'money') {
|
||||
$componentData['prefix'] = ['$'];
|
||||
}
|
||||
} elseif (in_array($componentData['type'], [
|
||||
Forms\Components\TextInput::class,
|
||||
Forms\Components\Textarea::class,
|
||||
]) && ($length = $column->getLength())) {
|
||||
$componentData['maxLength'] = [$length];
|
||||
]) && isset($type['length'])) {
|
||||
$componentData['maxLength'] = [$type['length']];
|
||||
|
||||
if (filled($column->getDefault())) {
|
||||
$componentData['default'] = [$column->getDefault()];
|
||||
if (filled($column['default'])) {
|
||||
$componentData['default'] = [$this->parseDefaultExpression($column, $model)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +153,7 @@ trait CanGenerateForms
|
||||
$parameterValue = match (true) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
is_bool($parameterValue) => $parameterValue ? 'true' : 'false',
|
||||
/** @phpstan-ignore-next-line */
|
||||
is_null($parameterValue) => 'null',
|
||||
is_numeric($parameterValue) => $parameterValue,
|
||||
default => "'{$parameterValue}'",
|
||||
|
||||
@@ -24,6 +24,7 @@ class CheckboxList extends Field implements Contracts\CanDisableOptions, Contrac
|
||||
use Concerns\HasGridDirection;
|
||||
use Concerns\HasNestedRecursiveValidationRules;
|
||||
use Concerns\HasOptions;
|
||||
use Concerns\HasPivotData;
|
||||
|
||||
/**
|
||||
* @var view-string
|
||||
@@ -185,7 +186,15 @@ class CheckboxList extends Field implements Contracts\CanDisableOptions, Contrac
|
||||
});
|
||||
|
||||
$this->saveRelationshipsUsing(static function (CheckboxList $component, ?array $state) {
|
||||
$component->getRelationship()->sync($state ?? []);
|
||||
$pivotData = $component->getPivotData();
|
||||
|
||||
if ($pivotData === []) {
|
||||
$component->getRelationship()->sync($state ?? []);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$component->getRelationship()->syncWithPivotValues($state ?? [], $pivotData);
|
||||
});
|
||||
|
||||
$this->dehydrated(false);
|
||||
|
||||
@@ -400,6 +400,13 @@ trait CanBeValidated
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function ulid(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->rule('ulid', $condition);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function uuid(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->rule('uuid', $condition);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Forms\Components\Concerns;
|
||||
|
||||
use Closure;
|
||||
|
||||
trait HasPivotData
|
||||
{
|
||||
/**
|
||||
* @var array<string, mixed> | Closure
|
||||
*/
|
||||
protected array | Closure $pivotData = [];
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> | Closure $data
|
||||
*/
|
||||
public function pivotData(array | Closure $data): static
|
||||
{
|
||||
$this->pivotData = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getPivotData(): array
|
||||
{
|
||||
return $this->evaluate($this->pivotData) ?? [];
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -46,6 +46,7 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has
|
||||
use Concerns\HasLoadingMessage;
|
||||
use Concerns\HasNestedRecursiveValidationRules;
|
||||
use Concerns\HasOptions;
|
||||
use Concerns\HasPivotData;
|
||||
use Concerns\HasPlaceholder;
|
||||
use HasExtraAlpineAttributes;
|
||||
|
||||
@@ -829,7 +830,7 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has
|
||||
}
|
||||
|
||||
if (
|
||||
($relationship instanceof HasOneThrough) ||
|
||||
($relationship instanceof HasManyThrough) ||
|
||||
($relationship instanceof \Znck\Eloquent\Relations\BelongsToThrough)
|
||||
) {
|
||||
$relatedModel = $relationship->getResults();
|
||||
@@ -952,7 +953,11 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has
|
||||
$this->saveRelationshipsUsing(static function (Select $component, Model $record, $state) {
|
||||
$relationship = $component->getRelationship();
|
||||
|
||||
if ($relationship instanceof \Znck\Eloquent\Relations\BelongsToThrough) {
|
||||
if (
|
||||
($relationship instanceof HasOneOrMany) ||
|
||||
($relationship instanceof HasManyThrough) ||
|
||||
($relationship instanceof \Znck\Eloquent\Relations\BelongsToThrough)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -963,7 +968,15 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has
|
||||
return;
|
||||
}
|
||||
|
||||
$relationship->sync($state ?? []);
|
||||
$pivotData = $component->getPivotData();
|
||||
|
||||
if ($pivotData === []) {
|
||||
$relationship->sync($state ?? []);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$relationship->syncWithPivotValues($state ?? [], $pivotData);
|
||||
});
|
||||
|
||||
$this->createOptionUsing(static function (Select $component, array $data, Form $form) {
|
||||
@@ -1063,7 +1076,7 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has
|
||||
return parent::getLabel();
|
||||
}
|
||||
|
||||
public function getRelationship(): BelongsTo | BelongsToMany | HasOneOrMany | HasOneThrough | \Znck\Eloquent\Relations\BelongsToThrough | null
|
||||
public function getRelationship(): BelongsTo | BelongsToMany | HasOneOrMany | HasManyThrough | \Znck\Eloquent\Relations\BelongsToThrough | null
|
||||
{
|
||||
if (blank($this->getRelationshipName())) {
|
||||
return null;
|
||||
@@ -1188,7 +1201,7 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has
|
||||
return $relationship->getQualifiedRelatedKeyName();
|
||||
}
|
||||
|
||||
if ($relationship instanceof HasOneThrough) {
|
||||
if ($relationship instanceof HasManyThrough) {
|
||||
return $relationship->getQualifiedForeignKeyName();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use Filament\Forms\Form;
|
||||
use Filament\Support\Exceptions\Cancel;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Throwable;
|
||||
|
||||
use function Livewire\store;
|
||||
|
||||
@@ -59,6 +60,8 @@ trait HasFormComponentActions
|
||||
$result = null;
|
||||
|
||||
try {
|
||||
$action->beginDatabaseTransaction();
|
||||
|
||||
if ($this->mountedFormComponentActionHasForm()) {
|
||||
$action->callBeforeFormValidated();
|
||||
|
||||
@@ -74,10 +77,21 @@ trait HasFormComponentActions
|
||||
]);
|
||||
|
||||
$result = $action->callAfter() ?? $result;
|
||||
|
||||
$action->commitDatabaseTransaction();
|
||||
} catch (Halt $exception) {
|
||||
$exception->shouldRollbackDatabaseTransaction() ?
|
||||
$action->rollBackDatabaseTransaction() :
|
||||
$action->commitDatabaseTransaction();
|
||||
|
||||
return null;
|
||||
} catch (Cancel $exception) {
|
||||
$exception->shouldRollbackDatabaseTransaction() ?
|
||||
$action->rollBackDatabaseTransaction() :
|
||||
$action->commitDatabaseTransaction();
|
||||
} catch (ValidationException $exception) {
|
||||
$action->rollBackDatabaseTransaction();
|
||||
|
||||
if (! $this->mountedFormComponentActionShouldOpenModal()) {
|
||||
$action->resetArguments();
|
||||
$action->resetFormData();
|
||||
@@ -85,6 +99,10 @@ trait HasFormComponentActions
|
||||
$this->unmountFormComponentAction();
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
} catch (Throwable $exception) {
|
||||
$action->rollBackDatabaseTransaction();
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
"php": "^8.1",
|
||||
"filament/actions": "self.version",
|
||||
"filament/support": "self.version",
|
||||
"illuminate/console": "^10.0",
|
||||
"illuminate/contracts": "^10.0",
|
||||
"illuminate/database": "^10.0",
|
||||
"illuminate/filesystem": "^10.0",
|
||||
"illuminate/support": "^10.0",
|
||||
"illuminate/view": "^10.0",
|
||||
"illuminate/console": "^10.45|^11.0",
|
||||
"illuminate/contracts": "^10.45|^11.0",
|
||||
"illuminate/database": "^10.45|^11.0",
|
||||
"illuminate/filesystem": "^10.45|^11.0",
|
||||
"illuminate/support": "^10.45|^11.0",
|
||||
"illuminate/view": "^10.45|^11.0",
|
||||
"spatie/laravel-package-tools": "^1.9"
|
||||
},
|
||||
"autoload": {
|
||||
|
||||
@@ -58,17 +58,39 @@ TextEntry::make('created_at')
|
||||
|
||||
## Number formatting
|
||||
|
||||
The `numeric()` method allows you to format an entry as a number, using PHP's `number_format()`:
|
||||
The `numeric()` method allows you to format an entry as a number:
|
||||
|
||||
```php
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
|
||||
TextEntry::make('stock')
|
||||
->numeric(
|
||||
decimalPlaces: 0,
|
||||
decimalSeparator: '.',
|
||||
thousandsSeparator: ',',
|
||||
)
|
||||
->numeric()
|
||||
```
|
||||
|
||||
If you would like to customize the number of decimal places used to format the number with, you can use the `decimalPlaces` argument:
|
||||
|
||||
```php
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
|
||||
TextEntry::make('stock')
|
||||
->numeric(decimalPlaces: 0)
|
||||
```
|
||||
|
||||
By default, your app's locale will be used to format the number suitably. If you would like to customize the locale used, you can pass it to the `locale` argument:
|
||||
|
||||
```php
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
|
||||
TextEntry::make('stock')
|
||||
->numeric(locale: 'nl')
|
||||
```
|
||||
|
||||
Alternatively, you can set the default locale used across your app using the `Number::useLocale()` method in the `boot()` method of a service provider:
|
||||
|
||||
```php
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
Number::useLocale('nl');
|
||||
```
|
||||
|
||||
## Currency formatting
|
||||
@@ -91,6 +113,23 @@ TextEntry::make('price')
|
||||
->money('EUR', divideBy: 100)
|
||||
```
|
||||
|
||||
By default, your app's locale will be used to format the money suitably. If you would like to customize the locale used, you can pass it to the `locale` argument:
|
||||
|
||||
```php
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
|
||||
TextEntry::make('price')
|
||||
->money(currency: 'EUR', locale: 'nl')
|
||||
```
|
||||
|
||||
Alternatively, you can set the default locale used across your app using the `Number::useLocale()` method in the `boot()` method of a service provider:
|
||||
|
||||
```php
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
Number::useLocale('nl');
|
||||
```
|
||||
|
||||
## Limiting text length
|
||||
|
||||
You may `limit()` the length of the entry's value:
|
||||
|
||||
@@ -15,6 +15,24 @@ return [
|
||||
|
||||
],
|
||||
|
||||
'key_value' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'key' => [
|
||||
'label' => 'Clé',
|
||||
],
|
||||
|
||||
'value' => [
|
||||
'label' => 'Valeur',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'placeholder' => 'Aucune entrée',
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -2,8 +2,37 @@
|
||||
|
||||
return [
|
||||
|
||||
'text_entry' => [
|
||||
'more_list_items' => 'e :count mais',
|
||||
'entries' => [
|
||||
|
||||
'text' => [
|
||||
|
||||
'actions' => [
|
||||
'collapse_list' => 'Mostrar menos :count',
|
||||
'expand_list' => 'Mostrar mais :count',
|
||||
],
|
||||
|
||||
'more_list_items' => 'e mais :count',
|
||||
|
||||
],
|
||||
|
||||
'key_value' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'key' => [
|
||||
'label' => 'Chave',
|
||||
],
|
||||
|
||||
'value' => [
|
||||
'label' => 'Valor',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'placeholder' => 'Sem dados',
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -11,10 +11,15 @@
|
||||
$height = $getHeight() ?? ($isStacked ? '2.5rem' : '8rem');
|
||||
$width = $getWidth() ?? (($isCircular || $isSquare) ? $height : null);
|
||||
|
||||
$stateCount = count($state);
|
||||
$limitedStateCount = count($limitedState);
|
||||
|
||||
$defaultImageUrl = $getDefaultImageUrl();
|
||||
|
||||
if ((! count($limitedState)) && filled($defaultImageUrl)) {
|
||||
if ((! $limitedStateCount) && filled($defaultImageUrl)) {
|
||||
$limitedState = [null];
|
||||
|
||||
$limitedStateCount = 1;
|
||||
}
|
||||
|
||||
$ringClasses = \Illuminate\Support\Arr::toCssClasses([
|
||||
@@ -29,7 +34,7 @@
|
||||
},
|
||||
]);
|
||||
|
||||
$hasLimitedRemainingText = $hasLimitedRemainingText();
|
||||
$hasLimitedRemainingText = $hasLimitedRemainingText() && ($limitedStateCount < $stateCount);
|
||||
$isLimitedRemainingTextSeparate = $isLimitedRemainingTextSeparate();
|
||||
|
||||
$limitedRemainingTextSizeClasses = match ($getLimitedRemainingTextSize()) {
|
||||
@@ -50,7 +55,7 @@
|
||||
])
|
||||
}}
|
||||
>
|
||||
@if (count($limitedState))
|
||||
@if ($limitedStateCount)
|
||||
<div
|
||||
@class([
|
||||
'flex flex-wrap',
|
||||
@@ -86,7 +91,7 @@
|
||||
/>
|
||||
@endforeach
|
||||
|
||||
@if ($hasLimitedRemainingText && ($loop->iteration < count($limitedState)) && (! $isLimitedRemainingTextSeparate) && $isCircular)
|
||||
@if ($hasLimitedRemainingText && (! $isLimitedRemainingTextSeparate) && $isCircular)
|
||||
<div
|
||||
style="
|
||||
@if ($height) height: {{ $height }}; @endif
|
||||
@@ -104,20 +109,20 @@
|
||||
])
|
||||
>
|
||||
<span class="-ms-0.5">
|
||||
+{{ count($state) - count($limitedState) }}
|
||||
+{{ $stateCount - $limitedStateCount }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($hasLimitedRemainingText && ($loop->iteration < count($limitedState)) && ($isLimitedRemainingTextSeparate || (! $isCircular)))
|
||||
@if ($hasLimitedRemainingText && ($isLimitedRemainingTextSeparate || (! $isCircular)))
|
||||
<div
|
||||
@class([
|
||||
'font-medium text-gray-500 dark:text-gray-400',
|
||||
$limitedRemainingTextSizeClasses,
|
||||
])
|
||||
>
|
||||
+{{ count($state) - count($limitedState) }}
|
||||
+{{ $stateCount - $limitedStateCount }}
|
||||
</div>
|
||||
@endif
|
||||
@elseif (($placeholder = $getPlaceholder()) !== null)
|
||||
|
||||
@@ -6,13 +6,12 @@ use Closure;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Infolists\Infolist;
|
||||
use Filament\Support\Contracts\HasLabel as LabelInterface;
|
||||
use Filament\Support\Enums\ArgumentValue;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Number;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use function Filament\Support\format_money;
|
||||
use function Filament\Support\format_number;
|
||||
|
||||
trait CanFormatState
|
||||
{
|
||||
protected ?Closure $formatStateUsing = null;
|
||||
@@ -99,28 +98,11 @@ trait CanFormatState
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function money(string | Closure | null $currency = null, int $divideBy = 0): static
|
||||
public function money(string | Closure | null $currency = null, int $divideBy = 0, string | Closure | null $locale = null): static
|
||||
{
|
||||
$this->isMoney = true;
|
||||
|
||||
$this->formatStateUsing(static function (TextEntry $component, $state) use ($currency, $divideBy): ?string {
|
||||
if (blank($state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$currency = $component->evaluate($currency) ?? Infolist::$defaultCurrency;
|
||||
|
||||
return format_money($state, $currency, $divideBy);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function numeric(int | Closure | null $decimalPlaces = null, string | Closure | null $decimalSeparator = '.', string | Closure | null $thousandsSeparator = ','): static
|
||||
{
|
||||
$this->isNumeric = true;
|
||||
|
||||
$this->formatStateUsing(static function (TextEntry $component, $state) use ($decimalPlaces, $decimalSeparator, $thousandsSeparator): ?string {
|
||||
$this->formatStateUsing(static function (TextEntry $component, $state) use ($currency, $divideBy, $locale): ?string {
|
||||
if (blank($state)) {
|
||||
return null;
|
||||
}
|
||||
@@ -129,16 +111,47 @@ trait CanFormatState
|
||||
return $state;
|
||||
}
|
||||
|
||||
if ($decimalPlaces === null) {
|
||||
return format_number($state);
|
||||
$currency = $component->evaluate($currency) ?? Infolist::$defaultCurrency;
|
||||
|
||||
if ($divideBy) {
|
||||
$state /= $divideBy;
|
||||
}
|
||||
|
||||
return number_format(
|
||||
$state,
|
||||
$component->evaluate($decimalPlaces),
|
||||
$component->evaluate($decimalSeparator),
|
||||
$component->evaluate($thousandsSeparator),
|
||||
);
|
||||
return Number::currency($state, $currency, $component->evaluate($locale));
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function numeric(int | Closure | null $decimalPlaces = null, string | Closure | null | ArgumentValue $decimalSeparator = ArgumentValue::Default, string | Closure | null | ArgumentValue $thousandsSeparator = ArgumentValue::Default, int | Closure | null $maxDecimalPlaces = null, string | Closure | null $locale = null): static
|
||||
{
|
||||
$this->isNumeric = true;
|
||||
|
||||
$this->formatStateUsing(static function (TextEntry $component, $state) use ($decimalPlaces, $decimalSeparator, $locale, $maxDecimalPlaces, $thousandsSeparator): ?string {
|
||||
if (blank($state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! is_numeric($state)) {
|
||||
return $state;
|
||||
}
|
||||
|
||||
$decimalSeparator = $component->evaluate($decimalSeparator);
|
||||
$thousandsSeparator = $component->evaluate($thousandsSeparator);
|
||||
|
||||
if (
|
||||
($decimalSeparator !== ArgumentValue::Default) ||
|
||||
($thousandsSeparator !== ArgumentValue::Default)
|
||||
) {
|
||||
return number_format(
|
||||
$state,
|
||||
$component->evaluate($decimalPlaces),
|
||||
$decimalSeparator === ArgumentValue::Default ? '.' : $decimalSeparator,
|
||||
$thousandsSeparator === ArgumentValue::Default ? ',' : $thousandsSeparator,
|
||||
);
|
||||
}
|
||||
|
||||
return Number::format($state, $decimalPlaces, $maxDecimalPlaces, $component->evaluate($locale));
|
||||
});
|
||||
|
||||
return $this;
|
||||
|
||||
@@ -14,6 +14,7 @@ use Filament\Support\Exceptions\Cancel;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Throwable;
|
||||
|
||||
use function Livewire\store;
|
||||
|
||||
@@ -103,6 +104,8 @@ trait InteractsWithInfolists
|
||||
$result = null;
|
||||
|
||||
try {
|
||||
$action->beginDatabaseTransaction();
|
||||
|
||||
if ($this->mountedInfolistActionHasForm()) {
|
||||
$action->callBeforeFormValidated();
|
||||
|
||||
@@ -118,10 +121,21 @@ trait InteractsWithInfolists
|
||||
]);
|
||||
|
||||
$result = $action->callAfter() ?? $result;
|
||||
|
||||
$action->commitDatabaseTransaction();
|
||||
} catch (Halt $exception) {
|
||||
$exception->shouldRollbackDatabaseTransaction() ?
|
||||
$action->rollBackDatabaseTransaction() :
|
||||
$action->commitDatabaseTransaction();
|
||||
|
||||
return null;
|
||||
} catch (Cancel $exception) {
|
||||
$exception->shouldRollbackDatabaseTransaction() ?
|
||||
$action->rollBackDatabaseTransaction() :
|
||||
$action->commitDatabaseTransaction();
|
||||
} catch (ValidationException $exception) {
|
||||
$action->rollBackDatabaseTransaction();
|
||||
|
||||
if (! $this->mountedInfolistActionShouldOpenModal()) {
|
||||
$action->resetArguments();
|
||||
$action->resetFormData();
|
||||
@@ -129,6 +143,10 @@ trait InteractsWithInfolists
|
||||
$this->unmountInfolistAction();
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
} catch (Throwable $exception) {
|
||||
$action->rollBackDatabaseTransaction();
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
"php": "^8.1",
|
||||
"filament/actions": "self.version",
|
||||
"filament/support": "self.version",
|
||||
"illuminate/contracts": "^10.0",
|
||||
"illuminate/filesystem": "^10.0",
|
||||
"illuminate/notifications": "^10.0",
|
||||
"illuminate/support": "^10.0",
|
||||
"illuminate/contracts": "^10.45|^11.0",
|
||||
"illuminate/filesystem": "^10.45|^11.0",
|
||||
"illuminate/notifications": "^10.45|^11.0",
|
||||
"illuminate/support": "^10.45|^11.0",
|
||||
"spatie/laravel-package-tools": "^1.9"
|
||||
},
|
||||
"autoload": {
|
||||
|
||||
@@ -23,7 +23,7 @@ To add database notifications to your app, you must add a new Livewire component
|
||||
|
||||
<AutoScreenshot name="notifications/database" alt="Database notifications" version="3.x" />
|
||||
|
||||
Database notifications will be rendered within a modal. To open this modal, you must have a "trigger" button in your view. Create a new trigger button component in your app, for instance at `/resources/views/notifications/database-notifications-trigger.blade.php`:
|
||||
Database notifications will be rendered within a modal. To open this modal, you must have a "trigger" button in your view. Create a new trigger button component in your app, for instance at `/resources/views/filament/notifications/database-notifications-trigger.blade.php`:
|
||||
|
||||
```blade
|
||||
<button type="button">
|
||||
@@ -38,7 +38,7 @@ In the service provider, point to this new trigger view:
|
||||
```php
|
||||
use Filament\Notifications\Livewire\DatabaseNotifications;
|
||||
|
||||
DatabaseNotifications::trigger('filament-notifications.database-notifications-trigger');
|
||||
DatabaseNotifications::trigger('filament.notifications.database-notifications-trigger');
|
||||
```
|
||||
|
||||
Now, click on the trigger button that is rendered in your view. A modal should appear containing your database notifications when clicked!
|
||||
|
||||
@@ -17,16 +17,16 @@
|
||||
"filament/support": "self.version",
|
||||
"filament/tables": "self.version",
|
||||
"filament/widgets": "self.version",
|
||||
"illuminate/auth": "^10.0",
|
||||
"illuminate/console": "^10.0",
|
||||
"illuminate/contracts": "^10.0",
|
||||
"illuminate/cookie": "^10.0",
|
||||
"illuminate/database": "^10.0",
|
||||
"illuminate/http": "^10.0",
|
||||
"illuminate/routing": "^10.0",
|
||||
"illuminate/session": "^10.0",
|
||||
"illuminate/support": "^10.0",
|
||||
"illuminate/view": "^10.0",
|
||||
"illuminate/auth": "^10.45|^11.0",
|
||||
"illuminate/console": "^10.45|^11.0",
|
||||
"illuminate/contracts": "^10.45|^11.0",
|
||||
"illuminate/cookie": "^10.45|^11.0",
|
||||
"illuminate/database": "^10.45|^11.0",
|
||||
"illuminate/http": "^10.45|^11.0",
|
||||
"illuminate/routing": "^10.45|^11.0",
|
||||
"illuminate/session": "^10.45|^11.0",
|
||||
"illuminate/support": "^10.45|^11.0",
|
||||
"illuminate/view": "^10.45|^11.0",
|
||||
"spatie/laravel-package-tools": "^1.9"
|
||||
},
|
||||
"autoload": {
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -27,7 +27,7 @@ php artisan filament:install --panels
|
||||
|
||||
This will create and register a new [Laravel service provider](https://laravel.com/docs/providers) called `app/Providers/Filament/AdminPanelProvider.php`.
|
||||
|
||||
> If you get an error when accessing your panel, check that the service provider was registered in your `config/app.php`. If not, you should manually add it to the `providers` array.
|
||||
> If you get an error when accessing your panel, check that the service provider was registered in `bootstrap/providers.php` (Laravel 11 and above) or `config/app.php` (Laravel 10 and below). If not, you should manually add it.
|
||||
|
||||
## Create a user
|
||||
You can create a new user account with the following command:
|
||||
|
||||
@@ -60,8 +60,6 @@ If you'd like to save time, Filament can automatically generate the [form](#reso
|
||||
php artisan make:filament-resource Customer --generate
|
||||
```
|
||||
|
||||
> If your table contains ENUM columns, the `doctrine/dbal` package we use is unable to scan your table and will crash. Hence, Filament is unable to generate the schema for your resource if it contains an ENUM column. Read more about this issue [here](https://github.com/doctrine/dbal/issues/3819#issuecomment-573419808).
|
||||
|
||||
### Handling soft deletes
|
||||
|
||||
By default, you will not be able to interact with deleted records in the app. If you'd like to add functionality to restore, force delete and filter trashed records in your resource, use the `--soft-deletes` flag when generating the resource:
|
||||
|
||||
@@ -486,6 +486,18 @@ MenuItem::make()
|
||||
->hidden(fn (): bool => ! auth()->user()->can('viewAny', Payment::class))
|
||||
```
|
||||
|
||||
### Sending a `POST` HTTP request from a user menu item
|
||||
|
||||
You can send a `POST` HTTP request from a user menu item by passing a URL to the `postAction()` method:
|
||||
|
||||
```php
|
||||
use Filament\Navigation\MenuItem;
|
||||
|
||||
MenuItem::make()
|
||||
->label('Lock session')
|
||||
->postAction(fn (): string => route('lock-session'))
|
||||
```
|
||||
|
||||
## Disabling breadcrumbs
|
||||
|
||||
The default layout will show breadcrumbs to indicate the location of the current page within the hierarchy of the app.
|
||||
|
||||
@@ -30,7 +30,7 @@ php artisan make:filament-panel app
|
||||
|
||||
This command will create a new panel called "app". A configuration file will be created at `app/Providers/Filament/AppPanelProvider.php`. You can access this panel at `/app`, but you can [customize the path](#changing-the-path) if you don't want that.
|
||||
|
||||
Since this configuration file is also a [Laravel service provider](https://laravel.com/docs/providers), it needs to be registered in `config/app.php`. Filament will attempt to do this for you, but if you get an error while trying to access your panel then this process has probably failed. You can manually register the service provider by adding it to the `providers` array.
|
||||
Since this configuration file is also a [Laravel service provider](https://laravel.com/docs/providers), it needs to be registered in `bootstrap/providers.php` (Laravel 11 and above) or `config/app.php` (Laravel 10 and below). Filament will attempt to do this for you, but if you get an error while trying to access your panel then this process has probably failed.
|
||||
|
||||
## Changing the path
|
||||
|
||||
|
||||
@@ -435,6 +435,18 @@ MenuItem::make()
|
||||
->hidden(fn (): bool => ! auth()->user()->can('manage-team'))
|
||||
```
|
||||
|
||||
### Sending a `POST` HTTP request from a tenant menu item
|
||||
|
||||
You can send a `POST` HTTP request from a tenant menu item by passing a URL to the `postAction()` method:
|
||||
|
||||
```php
|
||||
use Filament\Navigation\MenuItem;
|
||||
|
||||
MenuItem::make()
|
||||
->label('Lock session')
|
||||
->postAction(fn (): string => route('lock-session'))
|
||||
```
|
||||
|
||||
### Hiding the tenant menu
|
||||
|
||||
You can hide the tenant menu by using the `tenantMenu(false)`
|
||||
@@ -677,6 +689,38 @@ public function panel(Panel $panel): Panel
|
||||
|
||||
Before, the URL structure was `/admin/1` for tenant 1. Now, it is `/admin/team/1`.
|
||||
|
||||
## Using a domain to identify the tenant
|
||||
|
||||
When using a tenant, you might want to use domain or subdomain routing like `team1.example.com/posts` instead of a route prefix like `/team1/posts` . You can do that with the `tenantDomain()` method, alongside the `tenant()` configuration method. The `tenant` argument name is followed by a colon `:` and then the attribute you wish to resolve that part of the domain using, like the `slug` attribute:
|
||||
|
||||
```php
|
||||
use Filament\Panel;
|
||||
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
// ...
|
||||
->tenant(Team::class)
|
||||
->tenantDomain('{tenant:slug}.example.com');
|
||||
}
|
||||
```
|
||||
|
||||
In the above examples, the tenants live on subdomains of the main app domain. You may also set the system up to resolve the entire domain from the tenant as well:
|
||||
|
||||
```php
|
||||
use Filament\Panel;
|
||||
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
// ...
|
||||
->tenant(Team::class)
|
||||
->tenantDomain('{tenant:domain}');
|
||||
}
|
||||
```
|
||||
|
||||
In this example, the `domain` attribute should contain a valid domain host, like `example.com` or `subdomain.example.com`.
|
||||
|
||||
## Disabling tenancy for a resource
|
||||
|
||||
By default, all resources within a panel with tenancy will be scoped to the current tenant. If you have resources that are shared between tenants, you can disable tenancy for them by setting the `$isScopedToTenant` static property to `false` on the resource class:
|
||||
|
||||
@@ -37,7 +37,11 @@ document.addEventListener('alpine:init', () => {
|
||||
},
|
||||
})
|
||||
|
||||
const theme = localStorage.getItem('theme') ?? 'system'
|
||||
const theme =
|
||||
localStorage.getItem('theme') ??
|
||||
getComputedStyle(document.documentElement).getPropertyValue(
|
||||
'--default-theme-mode',
|
||||
)
|
||||
|
||||
window.Alpine.store(
|
||||
'theme',
|
||||
|
||||
@@ -14,7 +14,7 @@ return [
|
||||
],
|
||||
|
||||
'request_password_reset' => [
|
||||
'label' => 'Forgotten your password?',
|
||||
'label' => 'Forgot password?',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ return [
|
||||
|
||||
'title' => 'Reset your password',
|
||||
|
||||
'heading' => 'Forgotten your password?',
|
||||
'heading' => 'Forgot password?',
|
||||
|
||||
'actions' => [
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ return [
|
||||
'actions' => [
|
||||
|
||||
'cancel' => [
|
||||
'label' => 'regresar',
|
||||
'label' => 'Regresar',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -4,4 +4,30 @@ return [
|
||||
|
||||
'title' => 'Panoul de control',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'filter' => [
|
||||
|
||||
'label' => 'Filtru',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Filtru',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'apply' => [
|
||||
|
||||
'label' => 'Aplică',
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<title>
|
||||
{{ filled($title = strip_tags(($livewire ?? null)?->getTitle() ?? '')) ? "{$title} - " : null }}
|
||||
{{ filament()->getBrandName() }}
|
||||
{{ strip_tags(filament()->getBrandName()) }}
|
||||
</title>
|
||||
|
||||
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::STYLES_BEFORE, scopes: $livewire->getRenderHookScopes()) }}
|
||||
@@ -59,6 +59,7 @@
|
||||
--font-family: '{!! filament()->getFontFamily() !!}';
|
||||
--sidebar-width: {{ filament()->getSidebarWidth() }};
|
||||
--collapsed-sidebar-width: {{ filament()->getCollapsedSidebarWidth() }};
|
||||
--default-theme-mode: {{ filament()->getDefaultThemeMode()->value }};
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -116,12 +116,18 @@
|
||||
@if (count($items))
|
||||
<x-filament::dropdown.list>
|
||||
@foreach ($items as $item)
|
||||
@php
|
||||
$itemPostAction = $item->getPostAction();
|
||||
@endphp
|
||||
|
||||
<x-filament::dropdown.list.item
|
||||
:action="$itemPostAction"
|
||||
:color="$item->getColor()"
|
||||
:href="$item->getUrl()"
|
||||
:target="$item->shouldOpenUrlInNewTab() ? '_blank' : null"
|
||||
:icon="$item->getIcon()"
|
||||
tag="a"
|
||||
:method="filled($itemPostAction) ? 'post' : null"
|
||||
:tag="filled($itemPostAction) ? 'form' : 'a'"
|
||||
:target="$item->shouldOpenUrlInNewTab() ? '_blank' : null"
|
||||
>
|
||||
{{ $item->getLabel() }}
|
||||
</x-filament::dropdown.list.item>
|
||||
|
||||
@@ -67,12 +67,18 @@
|
||||
|
||||
<x-filament::dropdown.list>
|
||||
@foreach ($items as $key => $item)
|
||||
@php
|
||||
$itemPostAction = $item->getPostAction();
|
||||
@endphp
|
||||
|
||||
<x-filament::dropdown.list.item
|
||||
:action="$itemPostAction"
|
||||
:color="$item->getColor()"
|
||||
:href="$item->getUrl()"
|
||||
:target="$item->shouldOpenUrlInNewTab() ? '_blank' : null"
|
||||
:icon="$item->getIcon()"
|
||||
tag="a"
|
||||
:method="filled($itemPostAction) ? 'post' : null"
|
||||
:tag="filled($itemPostAction) ? 'form' : 'a'"
|
||||
:target="$item->shouldOpenUrlInNewTab() ? '_blank' : null"
|
||||
>
|
||||
{{ $item->getLabel() }}
|
||||
</x-filament::dropdown.list.item>
|
||||
|
||||
@@ -14,6 +14,7 @@ Route::name('filament.')
|
||||
$panelId = $panel->getId();
|
||||
$hasTenancy = $panel->hasTenancy();
|
||||
$tenantRoutePrefix = $panel->getTenantRoutePrefix();
|
||||
$tenantDomain = $panel->getTenantDomain();
|
||||
$tenantSlugAttribute = $panel->getTenantSlugAttribute();
|
||||
$domains = $panel->getDomains();
|
||||
|
||||
@@ -22,7 +23,7 @@ Route::name('filament.')
|
||||
->middleware($panel->getMiddleware())
|
||||
->name("{$panelId}.")
|
||||
->prefix($panel->getPath())
|
||||
->group(function () use ($panel, $hasTenancy, $tenantRoutePrefix, $tenantSlugAttribute) {
|
||||
->group(function () use ($panel, $hasTenancy, $tenantDomain, $tenantRoutePrefix, $tenantSlugAttribute) {
|
||||
if ($routes = $panel->getRoutes()) {
|
||||
$routes($panel);
|
||||
}
|
||||
@@ -52,7 +53,7 @@ Route::name('filament.')
|
||||
});
|
||||
|
||||
Route::middleware($panel->getAuthMiddleware())
|
||||
->group(function () use ($panel, $hasTenancy, $tenantRoutePrefix, $tenantSlugAttribute): void {
|
||||
->group(function () use ($panel, $hasTenancy, $tenantDomain, $tenantRoutePrefix, $tenantSlugAttribute): void {
|
||||
if ($routes = $panel->getAuthenticatedRoutes()) {
|
||||
$routes($panel);
|
||||
}
|
||||
@@ -89,8 +90,27 @@ Route::name('filament.')
|
||||
}
|
||||
});
|
||||
|
||||
Route::middleware($hasTenancy ? $panel->getTenantMiddleware() : [])
|
||||
->prefix($hasTenancy ? (($tenantRoutePrefix) ? "{$tenantRoutePrefix}/" : '') . ('{tenant' . (($tenantSlugAttribute) ? ":{$tenantSlugAttribute}" : '') . '}') : '')
|
||||
$routeGroup = Route::middleware($hasTenancy ? $panel->getTenantMiddleware() : []);
|
||||
|
||||
if (filled($tenantDomain)) {
|
||||
$routeGroup->domain($tenantDomain);
|
||||
} else {
|
||||
$routeGroup->prefix(
|
||||
($hasTenancy && blank($tenantDomain)) ?
|
||||
(
|
||||
filled($tenantRoutePrefix) ?
|
||||
"{$tenantRoutePrefix}/" :
|
||||
''
|
||||
) . ('{tenant' . (
|
||||
filled($tenantSlugAttribute) ?
|
||||
":{$tenantSlugAttribute}" :
|
||||
''
|
||||
) . '}') :
|
||||
'',
|
||||
);
|
||||
}
|
||||
|
||||
$routeGroup
|
||||
->group(function () use ($panel): void {
|
||||
if ($routes = $panel->getAuthenticatedTenantRoutes()) {
|
||||
$routes($panel);
|
||||
@@ -117,12 +137,28 @@ Route::name('filament.')
|
||||
$resource::registerRoutes($panel);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
if ($hasTenancy) {
|
||||
Route::middleware($panel->getTenantMiddleware())
|
||||
->prefix((($tenantRoutePrefix) ? "{$tenantRoutePrefix}/" : '') . '{tenant' . (($tenantSlugAttribute) ? ":{$tenantSlugAttribute}" : '') . '}')
|
||||
$routeGroup = Route::middleware($panel->getTenantMiddleware());
|
||||
|
||||
if (filled($tenantDomain)) {
|
||||
$routeGroup->domain($tenantDomain);
|
||||
} else {
|
||||
$routeGroup->prefix(
|
||||
(
|
||||
filled($tenantRoutePrefix) ?
|
||||
"{$tenantRoutePrefix}/" :
|
||||
''
|
||||
) . '{tenant' . (
|
||||
filled($tenantSlugAttribute) ?
|
||||
":{$tenantSlugAttribute}" :
|
||||
''
|
||||
) . '}',
|
||||
);
|
||||
}
|
||||
|
||||
$routeGroup
|
||||
->group(function () use ($panel): void {
|
||||
if ($routes = $panel->getTenantRoutes()) {
|
||||
$routes($panel);
|
||||
|
||||
@@ -266,7 +266,7 @@ class MakePageCommand extends Command
|
||||
$this->copyStubToApp('ResourceManageRelatedRecordsPage', $path, [
|
||||
'baseResourcePage' => "Filament\\Resources\\Pages\\{$resourcePage}",
|
||||
'baseResourcePageClass' => $resourcePage,
|
||||
'modifyQueryUsing' => filled($modifyQueryUsing ?? null) ? PHP_EOL . $this->indentString($modifyQueryUsing ?? '', 3) : $modifyQueryUsing ?? '',
|
||||
'modifyQueryUsing' => filled($modifyQueryUsing ?? null) ? PHP_EOL . $this->indentString($modifyQueryUsing, 3) : $modifyQueryUsing ?? '',
|
||||
'namespace' => "{$resourceNamespace}\\{$resource}\\Pages" . ($pageNamespace !== '' ? "\\{$pageNamespace}" : ''),
|
||||
'recordTitleAttribute' => $recordTitleAttribute ?? null,
|
||||
'relationship' => $relationship ?? null,
|
||||
|
||||
@@ -17,6 +17,8 @@ class MenuItem extends Component
|
||||
|
||||
protected string | Closure | null $label = null;
|
||||
|
||||
protected string | Closure | null $postAction = null;
|
||||
|
||||
protected int | Closure | null $sort = null;
|
||||
|
||||
protected string | Closure | Native | null $url = null;
|
||||
@@ -63,6 +65,13 @@ class MenuItem extends Component
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function postAction(string | Closure | null $action): static
|
||||
{
|
||||
$this->postAction = $action;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function sort(int | Closure | null $sort): static
|
||||
{
|
||||
$this->sort = $sort;
|
||||
@@ -131,6 +140,11 @@ class MenuItem extends Component
|
||||
return $this->evaluate($this->label);
|
||||
}
|
||||
|
||||
public function getPostAction(): ?string
|
||||
{
|
||||
return $this->evaluate($this->postAction);
|
||||
}
|
||||
|
||||
public function getSort(): int
|
||||
{
|
||||
return $this->evaluate($this->sort) ?? -1;
|
||||
|
||||
@@ -21,9 +21,11 @@ use Filament\Support\Facades\FilamentView;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Throwable;
|
||||
|
||||
use function Filament\Support\is_app_url;
|
||||
|
||||
@@ -143,6 +145,8 @@ class EditProfile extends Page
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$this->callHook('beforeValidate');
|
||||
|
||||
$data = $this->form->getState();
|
||||
@@ -156,8 +160,18 @@ class EditProfile extends Page
|
||||
$this->handleRecordUpdate($this->getUser(), $data);
|
||||
|
||||
$this->callHook('afterSave');
|
||||
|
||||
DB::commit();
|
||||
} catch (Halt $exception) {
|
||||
$exception->shouldRollbackDatabaseTransaction() ?
|
||||
DB::rollBack() :
|
||||
DB::commit();
|
||||
|
||||
return;
|
||||
} catch (Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if (request()->hasSession() && array_key_exists('password', $data)) {
|
||||
|
||||
@@ -22,6 +22,7 @@ use Illuminate\Auth\SessionGuard;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
@@ -74,9 +75,11 @@ class Register extends SimplePage
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $this->form->getState();
|
||||
$user = DB::transaction(function () {
|
||||
$data = $this->form->getState();
|
||||
|
||||
$user = $this->getUserModel()::create($data);
|
||||
return $this->getUserModel()::create($data);
|
||||
});
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@ use Filament\Support\Facades\FilamentView;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Throwable;
|
||||
|
||||
use function Filament\authorize;
|
||||
use function Filament\Support\is_app_url;
|
||||
@@ -108,6 +110,8 @@ abstract class EditTenantProfile extends Page
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$this->callHook('beforeValidate');
|
||||
|
||||
$data = $this->form->getState();
|
||||
@@ -121,8 +125,18 @@ abstract class EditTenantProfile extends Page
|
||||
$this->handleRecordUpdate($this->tenant, $data);
|
||||
|
||||
$this->callHook('afterSave');
|
||||
|
||||
DB::commit();
|
||||
} catch (Halt $exception) {
|
||||
$exception->shouldRollbackDatabaseTransaction() ?
|
||||
DB::rollBack() :
|
||||
DB::commit();
|
||||
|
||||
return;
|
||||
} catch (Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$this->getSavedNotification()?->send();
|
||||
|
||||
@@ -15,6 +15,8 @@ use Filament\Support\Facades\FilamentView;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Throwable;
|
||||
|
||||
use function Filament\authorize;
|
||||
use function Filament\Support\is_app_url;
|
||||
@@ -70,6 +72,8 @@ abstract class RegisterTenant extends SimplePage
|
||||
public function register(): void
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$this->callHook('beforeValidate');
|
||||
|
||||
$data = $this->form->getState();
|
||||
@@ -85,8 +89,18 @@ abstract class RegisterTenant extends SimplePage
|
||||
$this->form->model($this->tenant)->saveRelationships();
|
||||
|
||||
$this->callHook('afterRegister');
|
||||
|
||||
DB::commit();
|
||||
} catch (Halt $exception) {
|
||||
$exception->shouldRollbackDatabaseTransaction() ?
|
||||
DB::rollBack() :
|
||||
DB::commit();
|
||||
|
||||
return;
|
||||
} catch (Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if ($redirectUrl = $this->getRedirectUrl()) {
|
||||
|
||||
@@ -671,6 +671,6 @@ trait HasComponents
|
||||
|
||||
public function getComponentCachePath(): string
|
||||
{
|
||||
return (config('filament.cache_path') ?? base_path('bootstrap/cache/filament')) . DIRECTORY_SEPARATOR . "panels/{$this->getId()}.php";
|
||||
return (config('filament.cache_path') ?? base_path('bootstrap/cache/filament')) . DIRECTORY_SEPARATOR . 'panels' . DIRECTORY_SEPARATOR . "{$this->getId()}.php";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ trait HasTenancy
|
||||
|
||||
protected ?string $tenantRoutePrefix = null;
|
||||
|
||||
protected ?string $tenantDomain = null;
|
||||
|
||||
protected ?string $tenantModel = null;
|
||||
|
||||
protected ?string $tenantProfilePage = null;
|
||||
@@ -79,6 +81,13 @@ trait HasTenancy
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function tenantDomain(?string $domain): static
|
||||
{
|
||||
$this->tenantDomain = $domain;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function tenantBillingProvider(?BillingProvider $provider): static
|
||||
{
|
||||
$this->tenantBillingProvider = $provider;
|
||||
@@ -142,6 +151,16 @@ trait HasTenancy
|
||||
return $this->tenantRoutePrefix;
|
||||
}
|
||||
|
||||
public function hasTenantDomain(): bool
|
||||
{
|
||||
return filled($this->getTenantDomain());
|
||||
}
|
||||
|
||||
public function getTenantDomain(): ?string
|
||||
{
|
||||
return $this->tenantDomain;
|
||||
}
|
||||
|
||||
public function getTenantBillingProvider(): ?BillingProvider
|
||||
{
|
||||
return $this->tenantBillingProvider;
|
||||
|
||||
@@ -14,6 +14,8 @@ use Filament\Support\Facades\FilamentView;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Throwable;
|
||||
|
||||
use function Filament\Support\is_app_url;
|
||||
|
||||
@@ -74,6 +76,8 @@ class CreateRecord extends Page
|
||||
$this->authorizeAccess();
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$this->callHook('beforeValidate');
|
||||
|
||||
$data = $this->form->getState();
|
||||
@@ -89,8 +93,18 @@ class CreateRecord extends Page
|
||||
$this->form->model($this->getRecord())->saveRelationships();
|
||||
|
||||
$this->callHook('afterCreate');
|
||||
|
||||
DB::commit();
|
||||
} catch (Halt $exception) {
|
||||
$exception->shouldRollbackDatabaseTransaction() ?
|
||||
DB::rollBack() :
|
||||
DB::commit();
|
||||
|
||||
return;
|
||||
} catch (Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$this->rememberData();
|
||||
|
||||
@@ -20,6 +20,8 @@ use Filament\Support\Facades\FilamentView;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Throwable;
|
||||
|
||||
use function Filament\Support\is_app_url;
|
||||
|
||||
@@ -82,22 +84,23 @@ class EditRecord extends Page
|
||||
|
||||
protected function fillForm(): void
|
||||
{
|
||||
$data = $this->getRecord()->attributesToArray();
|
||||
|
||||
/** @internal Read the DocBlock above the following method. */
|
||||
$this->fillFormWithDataAndCallHooks($data);
|
||||
$this->fillFormWithDataAndCallHooks($this->getRecord());
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Never override or call this method. If you completely override `fillForm()`, copy the contents of this method into your override.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @param array<string, mixed> $extraData
|
||||
*/
|
||||
protected function fillFormWithDataAndCallHooks(array $data): void
|
||||
protected function fillFormWithDataAndCallHooks(Model $record, array $extraData = []): void
|
||||
{
|
||||
$this->callHook('beforeFill');
|
||||
|
||||
$data = $this->mutateFormDataBeforeFill($data);
|
||||
$data = $this->mutateFormDataBeforeFill([
|
||||
...$record->attributesToArray(),
|
||||
...$extraData,
|
||||
]);
|
||||
|
||||
$this->form->fill($data);
|
||||
|
||||
@@ -129,6 +132,8 @@ class EditRecord extends Page
|
||||
$this->authorizeAccess();
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$this->callHook('beforeValidate');
|
||||
|
||||
$data = $this->form->getState();
|
||||
@@ -142,8 +147,18 @@ class EditRecord extends Page
|
||||
$this->handleRecordUpdate($this->getRecord(), $data);
|
||||
|
||||
$this->callHook('afterSave');
|
||||
|
||||
DB::commit();
|
||||
} catch (Halt $exception) {
|
||||
$exception->shouldRollbackDatabaseTransaction() ?
|
||||
DB::rollBack() :
|
||||
DB::commit();
|
||||
|
||||
return;
|
||||
} catch (Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$this->rememberData();
|
||||
|
||||
@@ -13,6 +13,7 @@ use Filament\Infolists\Infolist;
|
||||
use Filament\Pages\Concerns\InteractsWithFormActions;
|
||||
use Filament\Support\Facades\FilamentIcon;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
/**
|
||||
@@ -76,22 +77,23 @@ class ViewRecord extends Page
|
||||
|
||||
protected function fillForm(): void
|
||||
{
|
||||
$data = $this->getRecord()->attributesToArray();
|
||||
|
||||
/** @internal Read the DocBlock above the following method. */
|
||||
$this->fillFormWithDataAndCallHooks($data);
|
||||
$this->fillFormWithDataAndCallHooks($this->getRecord());
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Never override or call this method. If you completely override `fillForm()`, copy the contents of this method into your override.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @param array<string, mixed> $extraData
|
||||
*/
|
||||
protected function fillFormWithDataAndCallHooks(array $data): void
|
||||
protected function fillFormWithDataAndCallHooks(Model $record, array $extraData = []): void
|
||||
{
|
||||
$this->callHook('beforeFill');
|
||||
|
||||
$data = $this->mutateFormDataBeforeFill($data);
|
||||
$data = $this->mutateFormDataBeforeFill([
|
||||
...$record->attributesToArray(),
|
||||
...$extraData,
|
||||
]);
|
||||
|
||||
$this->form->fill($data);
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"illuminate/contracts": "^10.0",
|
||||
"illuminate/support": "^10.0",
|
||||
"illuminate/contracts": "^10.45|^11.0",
|
||||
"illuminate/support": "^10.45|^11.0",
|
||||
"spatie/laravel-google-fonts": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"illuminate/support": "^10.0",
|
||||
"illuminate/support": "^10.45|^11.0",
|
||||
"spatie/laravel-medialibrary": "^10.0|^11.0"
|
||||
},
|
||||
"autoload": {
|
||||
|
||||
+43
-26
@@ -3,6 +3,7 @@
|
||||
namespace Filament\Infolists\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||
use Throwable;
|
||||
@@ -46,34 +47,38 @@ class SpatieMediaLibraryImageEntry extends ImageEntry
|
||||
}
|
||||
|
||||
if ($this->hasRelationship($record)) {
|
||||
$record = $record->getRelationValue($this->getRelationshipName());
|
||||
$record = $this->getRelationshipResults($record);
|
||||
}
|
||||
|
||||
if (! $record) {
|
||||
return null;
|
||||
}
|
||||
$records = Arr::wrap($record);
|
||||
|
||||
/** @var ?Media $media */
|
||||
$media = $record->media->first(fn (Media $media): bool => $media->uuid === $state);
|
||||
foreach ($records as $record) {
|
||||
/** @var Model $record */
|
||||
|
||||
if (! $media) {
|
||||
return null;
|
||||
}
|
||||
/** @var ?Media $media */
|
||||
$media = $record->getRelationValue('media')->first(fn (Media $media): bool => $media->uuid === $state);
|
||||
|
||||
$conversion = $this->getConversion();
|
||||
|
||||
if ($this->getVisibility() === 'private') {
|
||||
try {
|
||||
return $media->getTemporaryUrl(
|
||||
now()->addMinutes(5),
|
||||
$conversion ?? '',
|
||||
);
|
||||
} catch (Throwable $exception) {
|
||||
// This driver does not support creating temporary URLs.
|
||||
if (! $media) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$conversion = $this->getConversion();
|
||||
|
||||
if ($this->getVisibility() === 'private') {
|
||||
try {
|
||||
return $media->getTemporaryUrl(
|
||||
now()->addMinutes(5),
|
||||
$conversion ?? '',
|
||||
);
|
||||
} catch (Throwable $exception) {
|
||||
// This driver does not support creating temporary URLs.
|
||||
}
|
||||
}
|
||||
|
||||
return $media->getAvailableUrl(Arr::wrap($conversion));
|
||||
}
|
||||
|
||||
return $media->getAvailableUrl(Arr::wrap($conversion));
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,13 +91,25 @@ class SpatieMediaLibraryImageEntry extends ImageEntry
|
||||
$record = $this->getRecord();
|
||||
|
||||
if ($this->hasRelationship($record)) {
|
||||
$record = $record->getRelationValue($this->getRelationshipName());
|
||||
$record = $this->getRelationshipResults($record);
|
||||
}
|
||||
|
||||
return $record?->getRelationValue('media')
|
||||
->filter(fn (Media $media): bool => blank($collection) || ($media->getAttributeValue('collection_name') === $collection))
|
||||
->sortBy('order_column')
|
||||
->map(fn (Media $media): string => $media->uuid)
|
||||
->all() ?? [];
|
||||
$records = Arr::wrap($record);
|
||||
|
||||
$state = [];
|
||||
|
||||
foreach ($records as $record) {
|
||||
/** @var Model $record */
|
||||
$state = [
|
||||
...$state,
|
||||
...$record->getRelationValue('media')
|
||||
->filter(fn (Media $media): bool => blank($collection) || ($media->getAttributeValue('collection_name') === $collection))
|
||||
->sortBy('order_column')
|
||||
->pluck('uuid')
|
||||
->all(),
|
||||
];
|
||||
}
|
||||
|
||||
return array_unique($state);
|
||||
}
|
||||
}
|
||||
|
||||
+55
-36
@@ -4,6 +4,7 @@ namespace Filament\Tables\Columns;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Arr;
|
||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||
@@ -44,34 +45,38 @@ class SpatieMediaLibraryImageColumn extends ImageColumn
|
||||
$record = $this->getRecord();
|
||||
|
||||
if ($this->hasRelationship($record)) {
|
||||
$record = $record->getRelationValue($this->getRelationshipName());
|
||||
$record = $this->getRelationshipResults($record);
|
||||
}
|
||||
|
||||
if (! $record) {
|
||||
return null;
|
||||
}
|
||||
$records = Arr::wrap($record);
|
||||
|
||||
/** @var ?Media $media */
|
||||
$media = $record->media->first(fn (Media $media): bool => $media->uuid === $state);
|
||||
foreach ($records as $record) {
|
||||
/** @var Model $record */
|
||||
|
||||
if (! $media) {
|
||||
return null;
|
||||
}
|
||||
/** @var ?Media $media */
|
||||
$media = $record->getRelationValue('media')->first(fn (Media $media): bool => $media->uuid === $state);
|
||||
|
||||
$conversion = $this->getConversion();
|
||||
|
||||
if ($this->getVisibility() === 'private') {
|
||||
try {
|
||||
return $media->getTemporaryUrl(
|
||||
now()->addMinutes(5),
|
||||
$conversion ?? '',
|
||||
);
|
||||
} catch (Throwable $exception) {
|
||||
// This driver does not support creating temporary URLs.
|
||||
if (! $media) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$conversion = $this->getConversion();
|
||||
|
||||
if ($this->getVisibility() === 'private') {
|
||||
try {
|
||||
return $media->getTemporaryUrl(
|
||||
now()->addMinutes(5),
|
||||
$conversion ?? '',
|
||||
);
|
||||
} catch (Throwable $exception) {
|
||||
// This driver does not support creating temporary URLs.
|
||||
}
|
||||
}
|
||||
|
||||
return $media->getAvailableUrl(Arr::wrap($conversion));
|
||||
}
|
||||
|
||||
return $media->getAvailableUrl(Arr::wrap($conversion));
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,19 +84,28 @@ class SpatieMediaLibraryImageColumn extends ImageColumn
|
||||
*/
|
||||
public function getState(): array
|
||||
{
|
||||
$collection = $this->getCollection();
|
||||
|
||||
$record = $this->getRecord();
|
||||
|
||||
if ($this->hasRelationship($record)) {
|
||||
$record = $record->getRelationValue($this->getRelationshipName());
|
||||
$record = $this->getRelationshipResults($record);
|
||||
}
|
||||
|
||||
return $record?->getRelationValue('media')
|
||||
->filter(fn (Media $media): bool => blank($collection) || ($media->getAttributeValue('collection_name') === $collection))
|
||||
->sortBy('order_column')
|
||||
->map(fn (Media $media): string => $media->uuid)
|
||||
->all() ?? [];
|
||||
$records = Arr::wrap($record);
|
||||
|
||||
$state = [];
|
||||
|
||||
foreach ($records as $record) {
|
||||
/** @var Model $record */
|
||||
$state = [
|
||||
...$state,
|
||||
...$record->getRelationValue('media')
|
||||
->sortBy('order_column')
|
||||
->pluck('uuid')
|
||||
->all(),
|
||||
];
|
||||
}
|
||||
|
||||
return array_unique($state);
|
||||
}
|
||||
|
||||
public function applyEagerLoading(Builder | Relation $query): Builder | Relation
|
||||
@@ -100,18 +114,23 @@ class SpatieMediaLibraryImageColumn extends ImageColumn
|
||||
return $query;
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$modifyMediaQuery = fn (Builder | Relation $query) => $query
|
||||
->ordered()
|
||||
->when(
|
||||
$this->getCollection(),
|
||||
fn (Builder | Relation $query, string $collection) => $query->where(
|
||||
'collection_name',
|
||||
$collection,
|
||||
),
|
||||
);
|
||||
|
||||
if ($this->hasRelationship($query->getModel())) {
|
||||
return $query->with([
|
||||
"{$this->getRelationshipName()}.media" => fn (Builder | Relation $query) => $query->when(
|
||||
$this->getCollection(),
|
||||
fn (Builder | Relation $query, string $collection) => $query->where(
|
||||
'collection_name',
|
||||
$collection,
|
||||
),
|
||||
),
|
||||
"{$this->getRelationshipName()}.media" => $modifyMediaQuery,
|
||||
]);
|
||||
}
|
||||
|
||||
return $query->with(['media']);
|
||||
return $query->with(['media' => $modifyMediaQuery]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"filament/filament": "self.version",
|
||||
"illuminate/console": "^10.0",
|
||||
"illuminate/filesystem": "^10.0",
|
||||
"illuminate/support": "^10.0",
|
||||
"illuminate/console": "^10.45|^11.0",
|
||||
"illuminate/filesystem": "^10.45|^11.0",
|
||||
"illuminate/support": "^10.45|^11.0",
|
||||
"spatie/laravel-settings": "^2.2|^3.0"
|
||||
},
|
||||
"autoload": {
|
||||
|
||||
@@ -9,6 +9,8 @@ use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Filament\Support\Facades\FilamentView;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Throwable;
|
||||
|
||||
use function Filament\Support\is_app_url;
|
||||
|
||||
@@ -58,6 +60,8 @@ class SettingsPage extends Page
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$this->callHook('beforeValidate');
|
||||
|
||||
$data = $this->form->getState();
|
||||
@@ -74,8 +78,18 @@ class SettingsPage extends Page
|
||||
$settings->save();
|
||||
|
||||
$this->callHook('afterSave');
|
||||
|
||||
DB::commit();
|
||||
} catch (Halt $exception) {
|
||||
$exception->shouldRollbackDatabaseTransaction() ?
|
||||
DB::rollBack() :
|
||||
DB::commit();
|
||||
|
||||
return;
|
||||
} catch (Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$this->getSavedNotification()?->send();
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"illuminate/database": "^10.0",
|
||||
"illuminate/database": "^10.45|^11.0",
|
||||
"spatie/laravel-tags": "^4.0"
|
||||
},
|
||||
"autoload": {
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace Filament\Infolists\Components;
|
||||
use Closure;
|
||||
use Filament\SpatieLaravelTagsPlugin\Types\AllTagTypes;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class SpatieTagsEntry extends TextEntry
|
||||
{
|
||||
@@ -37,27 +39,34 @@ class SpatieTagsEntry extends TextEntry
|
||||
}
|
||||
|
||||
if ($this->hasRelationship($record)) {
|
||||
$record = $record->getRelationValue($this->getRelationshipName());
|
||||
$record = $this->getRelationshipResults($record);
|
||||
}
|
||||
|
||||
if (! $record) {
|
||||
return [];
|
||||
$records = Arr::wrap($record);
|
||||
|
||||
$state = [];
|
||||
|
||||
foreach ($records as $record) {
|
||||
/** @var Model $record */
|
||||
if (! (method_exists($record, 'tags') && method_exists($record, 'tagsWithType'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $this->getType();
|
||||
|
||||
if ($this->isAnyTagTypeAllowed()) {
|
||||
$tags = $record->getRelationValue('tags');
|
||||
} else {
|
||||
$tags = $record->tagsWithType($type);
|
||||
}
|
||||
|
||||
$state = [
|
||||
...$state,
|
||||
...$tags->pluck('name')->all(),
|
||||
];
|
||||
}
|
||||
|
||||
if (! (method_exists($record, 'tags') && method_exists($record, 'tagsWithType'))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$type = $this->getType();
|
||||
|
||||
if ($this->isAnyTagTypeAllowed()) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$tags = $record->tags;
|
||||
} else {
|
||||
$tags = $record->tagsWithType($type);
|
||||
}
|
||||
|
||||
return $tags->pluck('name')->all();
|
||||
return array_unique($state);
|
||||
}
|
||||
|
||||
public function type(string | Closure | AllTagTypes | null $type): static
|
||||
|
||||
@@ -5,10 +5,12 @@ namespace Filament\Tables\Columns;
|
||||
use Closure;
|
||||
use Filament\SpatieLaravelTagsPlugin\Types\AllTagTypes;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class SpatieTagsColumn extends TagsColumn
|
||||
class SpatieTagsColumn extends TextColumn
|
||||
{
|
||||
protected string | Closure | AllTagTypes | null $type;
|
||||
|
||||
@@ -17,6 +19,8 @@ class SpatieTagsColumn extends TagsColumn
|
||||
parent::setUp();
|
||||
|
||||
$this->type(new AllTagTypes());
|
||||
|
||||
$this->badge();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,27 +37,34 @@ class SpatieTagsColumn extends TagsColumn
|
||||
$record = $this->getRecord();
|
||||
|
||||
if ($this->hasRelationship($record)) {
|
||||
$record = $record->getRelationValue($this->getRelationshipName());
|
||||
$record = $this->getRelationshipResults($record);
|
||||
}
|
||||
|
||||
if (! $record) {
|
||||
return [];
|
||||
$records = Arr::wrap($record);
|
||||
|
||||
$state = [];
|
||||
|
||||
foreach ($records as $record) {
|
||||
/** @var Model $record */
|
||||
if (! (method_exists($record, 'tags') && method_exists($record, 'tagsWithType'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $this->getType();
|
||||
|
||||
if ($this->isAnyTagTypeAllowed()) {
|
||||
$tags = $record->getRelationValue('tags');
|
||||
} else {
|
||||
$tags = $record->tagsWithType($type);
|
||||
}
|
||||
|
||||
$state = [
|
||||
...$state,
|
||||
...$tags->pluck('name')->all(),
|
||||
];
|
||||
}
|
||||
|
||||
if (! (method_exists($record, 'tags') && method_exists($record, 'tagsWithType'))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$type = $this->getType();
|
||||
|
||||
if ($this->isAnyTagTypeAllowed()) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$tags = $record->tags;
|
||||
} else {
|
||||
$tags = $record->tagsWithType($type);
|
||||
}
|
||||
|
||||
return $tags->pluck('name')->all();
|
||||
return array_unique($state);
|
||||
}
|
||||
|
||||
public function type(string | Closure | AllTagTypes | null $type): static
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"filament/support": "self.version",
|
||||
"illuminate/support": "^10.0",
|
||||
"illuminate/support": "^10.45|^11.0",
|
||||
"spatie/laravel-translatable": "^6.0"
|
||||
},
|
||||
"autoload": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user