mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-08 05:43:18 +01:00
Compare commits
140 Commits
feature/ex
...
feature/go
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4ccf5b688 | ||
|
|
489ffcb4b4 | ||
|
|
3a7a354551 | ||
|
|
f9519d0d5a | ||
|
|
349623d537 | ||
|
|
a9d016d773 | ||
|
|
2ebbbceefb | ||
|
|
e85058aefd | ||
|
|
a86c18ad2d | ||
|
|
f683c03ff9 | ||
|
|
fa0bbc8cfd | ||
|
|
fff3502e53 | ||
|
|
e801c4311c | ||
|
|
2dadf1ef02 | ||
|
|
12cc3f27f8 | ||
|
|
06a2048771 | ||
|
|
a12ee1b029 | ||
|
|
54dc0dbabc | ||
|
|
08f9edbc96 | ||
|
|
0551c633a3 | ||
|
|
37579b1e5c | ||
|
|
b33cf00788 | ||
|
|
c23f09fb2d | ||
|
|
a0262addb7 | ||
|
|
b4911ebddd | ||
|
|
3ade20887b | ||
|
|
4943a38ffe | ||
|
|
12e355ff26 | ||
|
|
01b60c0f6a | ||
|
|
927da7dce9 | ||
|
|
c89fa87b0f | ||
|
|
02cf600f43 | ||
|
|
98a460725f | ||
|
|
95645ddd91 | ||
|
|
1de985b577 | ||
|
|
d54296e66a | ||
|
|
95ddbf9ead | ||
|
|
70646a0dd4 | ||
|
|
5b12c09747 | ||
|
|
24023353f2 | ||
|
|
720d20c10e | ||
|
|
82ea9af8b5 | ||
|
|
169d522da0 | ||
|
|
45c7377802 | ||
|
|
8e57275cef | ||
|
|
efc6b55628 | ||
|
|
c12789376d | ||
|
|
4795812b60 | ||
|
|
0e00979ab8 | ||
|
|
f1426fcb5e | ||
|
|
23f512d4a4 | ||
|
|
637475e669 | ||
|
|
3ec2abb309 | ||
|
|
3e36b1cc01 | ||
|
|
7831bc697e | ||
|
|
602a8daa1f | ||
|
|
38b448a729 | ||
|
|
e6f071f87f | ||
|
|
693a1fa7e0 | ||
|
|
77f14b696e | ||
|
|
593372bae5 | ||
|
|
db9ca51fc4 | ||
|
|
28aba7b27a | ||
|
|
453d5ed066 | ||
|
|
ed3ba0135b | ||
|
|
fb7d945c50 | ||
|
|
9958e1c09c | ||
|
|
f00d48f1c3 | ||
|
|
7edbfd5d76 | ||
|
|
1edb940557 | ||
|
|
c9330e6cb8 | ||
|
|
0e9f652d84 | ||
|
|
d29f5706ce | ||
|
|
b2849ec04a | ||
|
|
bd69cf478c | ||
|
|
5197135d00 | ||
|
|
b09b8649b8 | ||
|
|
99fafdb8d6 | ||
|
|
b02e49c7e0 | ||
|
|
8609635d74 | ||
|
|
5a07f798b3 | ||
|
|
3af69830ee | ||
|
|
a050153bcd | ||
|
|
9d9731c7ce | ||
|
|
dfe3206614 | ||
|
|
97fd882878 | ||
|
|
111e12df12 | ||
|
|
de13c07855 | ||
|
|
29a2e994cd | ||
|
|
f6d886b218 | ||
|
|
80d98b30a1 | ||
|
|
32f2f1431b | ||
|
|
8f6d584ee9 | ||
|
|
1905cbf40c | ||
|
|
c8f668238e | ||
|
|
bf11bacdee | ||
|
|
f3c6a0b8ae | ||
|
|
619c602571 | ||
|
|
600daf44d9 | ||
|
|
a1d6c92806 | ||
|
|
ce1bd6a435 | ||
|
|
79d85227c2 | ||
|
|
885abf3333 | ||
|
|
c0c8fee6be | ||
|
|
cbcd1e51f6 | ||
|
|
114a32536d | ||
|
|
ff8a0f065b | ||
|
|
44fd0ffb91 | ||
|
|
c07c62bfab | ||
|
|
f599757761 | ||
|
|
e54fd2460c | ||
|
|
e997a2b39f | ||
|
|
f0de89b0e8 | ||
|
|
024283273a | ||
|
|
f01ab5fd9d | ||
|
|
4fb18f343f | ||
|
|
ba374c0371 | ||
|
|
e302421765 | ||
|
|
c90c1ed8fa | ||
|
|
75f5144b45 | ||
|
|
53ecc58509 | ||
|
|
ac68091d3e | ||
|
|
a97c02542b | ||
|
|
a56942abcf | ||
|
|
61a0a7db5a | ||
|
|
bc5af14a75 | ||
|
|
95bfb45978 | ||
|
|
2207f676ee | ||
|
|
981bcbe091 | ||
|
|
ccf3cec8c0 | ||
|
|
23ea1500b0 | ||
|
|
4bbde04e28 | ||
|
|
caa36d2875 | ||
|
|
58d7b33366 | ||
|
|
50f57f6997 | ||
|
|
ab9f6e6afb | ||
|
|
2a40bb7edb | ||
|
|
365f672cfc | ||
|
|
25f30fa820 | ||
|
|
37a8f43b23 |
54
.dockerignore
Normal file
54
.dockerignore
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
.git
|
||||||
|
**/.git
|
||||||
|
.gitmodules
|
||||||
|
**/.gitmodules
|
||||||
|
.github
|
||||||
|
.DS_Store
|
||||||
|
.fleet
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.log
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
k8s
|
||||||
|
docs
|
||||||
|
e2e
|
||||||
|
tests
|
||||||
|
|
||||||
|
docker-compose.yml
|
||||||
|
docker/local
|
||||||
|
|
||||||
|
.phpunit.cache
|
||||||
|
.phpunit.result.cache
|
||||||
|
coverage
|
||||||
|
test-results
|
||||||
|
playwright-report
|
||||||
|
blob-report
|
||||||
|
playwright/.cache
|
||||||
|
openapi.json
|
||||||
|
playwright
|
||||||
|
playwright.config.ts
|
||||||
|
vitest.config.ts
|
||||||
|
phpunit.xml
|
||||||
|
phpstan.neon
|
||||||
|
pint.json
|
||||||
|
eslint.config.mjs
|
||||||
|
tsconfig.json
|
||||||
|
jsconfig.json
|
||||||
|
postcss.config.js
|
||||||
|
tailwind.config.js
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
extensions/*/node_modules
|
||||||
|
|
||||||
|
Homestead.json
|
||||||
|
Homestead.yaml
|
||||||
|
auth.json
|
||||||
|
.env.backup
|
||||||
|
.rnd
|
||||||
|
|
||||||
|
_ide_helper.php
|
||||||
|
.phpstorm.meta.php
|
||||||
|
|
||||||
|
storage/logs/*
|
||||||
|
storage/*.key
|
||||||
4
.env.ci
4
.env.ci
@@ -6,6 +6,7 @@ APP_DEBUG=true
|
|||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
APP_FORCE_HTTPS=false
|
APP_FORCE_HTTPS=false
|
||||||
APP_ENABLE_REGISTRATION=true
|
APP_ENABLE_REGISTRATION=true
|
||||||
|
SUPER_ADMINS=admin@example.com
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
LOG_CHANNEL=stack
|
LOG_CHANNEL=stack
|
||||||
@@ -64,3 +65,6 @@ GOTENBERG_URL=http://localhost:3000
|
|||||||
|
|
||||||
# Octane
|
# Octane
|
||||||
OCTANE_SERVER=frankenphp
|
OCTANE_SERVER=frankenphp
|
||||||
|
|
||||||
|
# Inertia
|
||||||
|
INERTIA_SSR_ENABLED=false
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ APP_KEY=base64:UNQNf1SXeASNkWux01Rj8EnHYx8FO0kAxWNDwktclkk=
|
|||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
APP_URL=https://solidtime.test
|
APP_URL=https://solidtime.test
|
||||||
APP_FORCE_HTTPS=false
|
APP_FORCE_HTTPS=false
|
||||||
APP_ENABLE_REGISTRATION=true
|
# Supported values: on, off, invite-only (true/false are supported for backwards compatibility)
|
||||||
|
APP_ENABLE_REGISTRATION=on
|
||||||
SUPER_ADMINS=admin@example.com
|
SUPER_ADMINS=admin@example.com
|
||||||
PAGINATION_PER_PAGE_DEFAULT=500
|
PAGINATION_PER_PAGE_DEFAULT=500
|
||||||
|
|
||||||
@@ -87,3 +88,6 @@ FORWARD_DB_PORT=54329
|
|||||||
VITE_HOST_NAME=vite.solidtime.test
|
VITE_HOST_NAME=vite.solidtime.test
|
||||||
VITE_APP_NAME="${APP_NAME}"
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
#SAIL_XDEBUG_MODE=develop,debug,coverage
|
#SAIL_XDEBUG_MODE=develop,debug,coverage
|
||||||
|
|
||||||
|
# This app does not ship a Node SSR bundle, so Inertia SSR must stay disabled.
|
||||||
|
INERTIA_SSR_ENABLED=false
|
||||||
|
|||||||
@@ -13,3 +13,5 @@ LOG_DEPRECATIONS_CHANNEL=null
|
|||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
DB_CONNECTION=pgsql
|
DB_CONNECTION=pgsql
|
||||||
|
|
||||||
|
INERTIA_SSR_ENABLED=false
|
||||||
|
|||||||
27
.github/VOUCHED.td
vendored
Normal file
27
.github/VOUCHED.td
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Vouched contributors for solidtime.
|
||||||
|
#
|
||||||
|
# One handle per line, without the leading @, sorted alphabetically.
|
||||||
|
# Prefix a handle with - to denounce them, optionally followed by a reason.
|
||||||
|
# Format reference: https://github.com/mitchellh/vouch
|
||||||
|
#
|
||||||
|
# Maintainers do not need to edit this file by hand. Comment "vouch @user",
|
||||||
|
# "unvouch @user" or "denounce @user <reason>" on any issue, pull request or
|
||||||
|
# discussion and the vouch workflows will update this file.
|
||||||
|
#
|
||||||
|
# Collaborators with write access and bots are always allowed and do not need
|
||||||
|
# an entry here.
|
||||||
|
#
|
||||||
|
# Seeded 2026-07-25 from the authors of every merged pull request.
|
||||||
|
|
||||||
|
agross
|
||||||
|
akolenda
|
||||||
|
bufferhead-code
|
||||||
|
candideu
|
||||||
|
kasparrosin
|
||||||
|
korridor
|
||||||
|
nikbucher tasks table sorting
|
||||||
|
onatcer
|
||||||
|
shrootbuck
|
||||||
|
smilebeda
|
||||||
|
thespyder
|
||||||
|
utlark
|
||||||
42
.github/workflows/build-onpremise.yml
vendored
42
.github/workflows/build-onpremise.yml
vendored
@@ -8,6 +8,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- '.github/workflows/build-onpremise.yml'
|
- '.github/workflows/build-onpremise.yml'
|
||||||
|
- '.dockerignore'
|
||||||
|
- 'extensions/manifest.json'
|
||||||
- 'docker/prod/**'
|
- 'docker/prod/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Check out code"
|
- name: "Check out code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required for WyriHaximus/github-action-get-previous-tag
|
fetch-depth: 0 # Required for WyriHaximus/github-action-get-previous-tag
|
||||||
|
|
||||||
@@ -91,14 +93,25 @@ jobs:
|
|||||||
if: steps.cache-vendor.outputs.cache-hit != 'true' # Skip if cache hit
|
if: steps.cache-vendor.outputs.cache-hit != 'true' # Skip if cache hit
|
||||||
|
|
||||||
- name: "Use Node.js"
|
- name: "Use Node.js"
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
|
- name: "Read extension manifest"
|
||||||
|
id: extension-manifest
|
||||||
|
run: |
|
||||||
|
{
|
||||||
|
echo "invoicing_repository=$(jq -r '.Invoicing.repository' extensions/manifest.json)"
|
||||||
|
echo "invoicing_ref=$(jq -r '.Invoicing.ref' extensions/manifest.json)"
|
||||||
|
echo "auditing_repository=$(jq -r '.Auditing.repository' extensions/manifest.json)"
|
||||||
|
echo "auditing_ref=$(jq -r '.Auditing.ref' extensions/manifest.json)"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: "Checkout invoicing extension"
|
- name: "Checkout invoicing extension"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
repository: solidtime-io/extension-invoicing
|
repository: ${{ steps.extension-manifest.outputs.invoicing_repository }}
|
||||||
|
ref: ${{ steps.extension-manifest.outputs.invoicing_ref }}
|
||||||
path: extensions/Invoicing
|
path: extensions/Invoicing
|
||||||
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_INVOICING_EXTENSION }}
|
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_INVOICING_EXTENSION }}
|
||||||
|
|
||||||
@@ -108,9 +121,26 @@ jobs:
|
|||||||
- name: "Install npm dependencies in invoicing extension"
|
- name: "Install npm dependencies in invoicing extension"
|
||||||
run: cd extensions/Invoicing && npm ci
|
run: cd extensions/Invoicing && npm ci
|
||||||
|
|
||||||
|
- name: "Checkout auditing extension"
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
repository: ${{ steps.extension-manifest.outputs.auditing_repository }}
|
||||||
|
ref: ${{ steps.extension-manifest.outputs.auditing_ref }}
|
||||||
|
path: extensions/Auditing
|
||||||
|
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_AUDITING_EXTENSION }}
|
||||||
|
|
||||||
|
- name: "Install composer dependencies in auditing extension"
|
||||||
|
run: cd extensions/Auditing && composer install --no-dev --no-ansi --no-interaction --prefer-dist --ignore-platform-reqs --classmap-authoritative
|
||||||
|
|
||||||
|
- name: "Install npm dependencies in auditing extension"
|
||||||
|
run: cd extensions/Auditing && npm ci
|
||||||
|
|
||||||
- name: "Activate invoicing extension"
|
- name: "Activate invoicing extension"
|
||||||
run: php artisan module:enable Invoicing
|
run: php artisan module:enable Invoicing
|
||||||
|
|
||||||
|
- name: "Activate auditing extension"
|
||||||
|
run: php artisan module:enable Auditing
|
||||||
|
|
||||||
- name: "Install npm dependencies"
|
- name: "Install npm dependencies"
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
@@ -130,7 +160,7 @@ jobs:
|
|||||||
${{ env.DOCKER_REPO }}
|
${{ env.DOCKER_REPO }}
|
||||||
|
|
||||||
- name: "Login to solidtime OnPremise Registry"
|
- name: "Login to solidtime OnPremise Registry"
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v4.6.0
|
||||||
with:
|
with:
|
||||||
registry: registry.on-premise.solidtime.io
|
registry: registry.on-premise.solidtime.io
|
||||||
username: ${{ secrets.ONPREMISE_USERNAME }}
|
username: ${{ secrets.ONPREMISE_USERNAME }}
|
||||||
@@ -184,7 +214,7 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: "Login to solidtime OnPremise Registry"
|
- name: "Login to solidtime OnPremise Registry"
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v4.6.0
|
||||||
with:
|
with:
|
||||||
registry: registry.on-premise.solidtime.io
|
registry: registry.on-premise.solidtime.io
|
||||||
username: ${{ secrets.ONPREMISE_USERNAME }}
|
username: ${{ secrets.ONPREMISE_USERNAME }}
|
||||||
|
|||||||
61
.github/workflows/build-private.yml
vendored
61
.github/workflows/build-private.yml
vendored
@@ -8,6 +8,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- '.github/workflows/build-private.yml'
|
- '.github/workflows/build-private.yml'
|
||||||
|
- '.dockerignore'
|
||||||
|
- 'extensions/manifest.json'
|
||||||
- 'docker/prod/**'
|
- 'docker/prod/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
permissions:
|
permissions:
|
||||||
@@ -22,7 +24,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Check out code"
|
- name: "Check out code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required for WyriHaximus/github-action-get-previous-tag
|
fetch-depth: 0 # Required for WyriHaximus/github-action-get-previous-tag
|
||||||
|
|
||||||
@@ -68,21 +70,36 @@ jobs:
|
|||||||
run: cat .env
|
run: cat .env
|
||||||
|
|
||||||
- name: "Use Node.js"
|
- name: "Use Node.js"
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
|
- name: "Read extension manifest"
|
||||||
|
id: extension-manifest
|
||||||
|
run: |
|
||||||
|
{
|
||||||
|
echo "billing_repository=$(jq -r '.Billing.repository' extensions/manifest.json)"
|
||||||
|
echo "billing_ref=$(jq -r '.Billing.ref' extensions/manifest.json)"
|
||||||
|
echo "services_repository=$(jq -r '.Services.repository' extensions/manifest.json)"
|
||||||
|
echo "services_ref=$(jq -r '.Services.ref' extensions/manifest.json)"
|
||||||
|
echo "invoicing_repository=$(jq -r '.Invoicing.repository' extensions/manifest.json)"
|
||||||
|
echo "invoicing_ref=$(jq -r '.Invoicing.ref' extensions/manifest.json)"
|
||||||
|
echo "auditing_repository=$(jq -r '.Auditing.repository' extensions/manifest.json)"
|
||||||
|
echo "auditing_ref=$(jq -r '.Auditing.ref' extensions/manifest.json)"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: "Checkout billing extension"
|
- name: "Checkout billing extension"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
repository: solidtime-io/extension-billing
|
repository: ${{ steps.extension-manifest.outputs.billing_repository }}
|
||||||
|
ref: ${{ steps.extension-manifest.outputs.billing_ref }}
|
||||||
path: extensions/Billing
|
path: extensions/Billing
|
||||||
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_BILLING_EXTENSION }}
|
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_BILLING_EXTENSION }}
|
||||||
|
|
||||||
- name: "Install dependencies in billing extension"
|
- name: "Install dependencies in billing extension"
|
||||||
uses: php-actions/composer@v6
|
uses: php-actions/composer@v6
|
||||||
env:
|
env:
|
||||||
COMPOSER_AUTH: '{"http-basic": {"spark.laravel.com": {"username": "gregor@vostrak.at", "password": "${{ secrets.LARAVEL_SPARK_API_KEY }}"}}}'
|
COMPOSER_AUTH: '{"http-basic": {"spark.laravel.com": {"username": "${{ secrets.LARAVEL_SPARK_USERNAME }}", "password": "${{ secrets.LARAVEL_SPARK_API_KEY }}"}}}'
|
||||||
with:
|
with:
|
||||||
working_dir: "extensions/Billing"
|
working_dir: "extensions/Billing"
|
||||||
command: install
|
command: install
|
||||||
@@ -93,9 +110,10 @@ jobs:
|
|||||||
run: cd extensions/Billing && npm ci
|
run: cd extensions/Billing && npm ci
|
||||||
|
|
||||||
- name: "Checkout services extension"
|
- name: "Checkout services extension"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
repository: solidtime-io/extension-services
|
repository: ${{ steps.extension-manifest.outputs.services_repository }}
|
||||||
|
ref: ${{ steps.extension-manifest.outputs.services_ref }}
|
||||||
path: extensions/Services
|
path: extensions/Services
|
||||||
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_SERVICES_EXTENSION }}
|
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_SERVICES_EXTENSION }}
|
||||||
|
|
||||||
@@ -111,9 +129,10 @@ jobs:
|
|||||||
run: cd extensions/Services && npm ci
|
run: cd extensions/Services && npm ci
|
||||||
|
|
||||||
- name: "Checkout invoicing extension"
|
- name: "Checkout invoicing extension"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
repository: solidtime-io/extension-invoicing
|
repository: ${{ steps.extension-manifest.outputs.invoicing_repository }}
|
||||||
|
ref: ${{ steps.extension-manifest.outputs.invoicing_ref }}
|
||||||
path: extensions/Invoicing
|
path: extensions/Invoicing
|
||||||
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_INVOICING_EXTENSION }}
|
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_INVOICING_EXTENSION }}
|
||||||
|
|
||||||
@@ -128,6 +147,25 @@ jobs:
|
|||||||
- name: "Install npm dependencies in invoicing extension"
|
- name: "Install npm dependencies in invoicing extension"
|
||||||
run: cd extensions/Invoicing && npm ci
|
run: cd extensions/Invoicing && npm ci
|
||||||
|
|
||||||
|
- name: "Checkout auditing extension"
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
repository: ${{ steps.extension-manifest.outputs.auditing_repository }}
|
||||||
|
ref: ${{ steps.extension-manifest.outputs.auditing_ref }}
|
||||||
|
path: extensions/Auditing
|
||||||
|
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_AUDITING_EXTENSION }}
|
||||||
|
|
||||||
|
- name: "Install composer dependencies in auditing extension"
|
||||||
|
uses: php-actions/composer@v6
|
||||||
|
with:
|
||||||
|
working_dir: "extensions/Auditing"
|
||||||
|
command: install
|
||||||
|
only_args: --no-dev --no-ansi --no-interaction --prefer-dist --ignore-platform-reqs --classmap-authoritative
|
||||||
|
php_version: 8.3
|
||||||
|
|
||||||
|
- name: "Install npm dependencies in auditing extension"
|
||||||
|
run: cd extensions/Auditing && npm ci
|
||||||
|
|
||||||
- name: "Setup PHP with PECL extension"
|
- name: "Setup PHP with PECL extension"
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
@@ -151,6 +189,9 @@ jobs:
|
|||||||
- name: "Activate invoicing extension"
|
- name: "Activate invoicing extension"
|
||||||
run: php artisan module:enable Invoicing
|
run: php artisan module:enable Invoicing
|
||||||
|
|
||||||
|
- name: "Activate auditing extension"
|
||||||
|
run: php artisan module:enable Auditing
|
||||||
|
|
||||||
- name: "Install npm dependencies"
|
- name: "Install npm dependencies"
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
@@ -160,7 +201,7 @@ jobs:
|
|||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
||||||
- name: "Login to GitHub Container Registry"
|
- name: "Login to GitHub Container Registry"
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v4.6.0
|
||||||
with:
|
with:
|
||||||
registry: rg.fr-par.scw.cloud/solidtime
|
registry: rg.fr-par.scw.cloud/solidtime
|
||||||
username: nologin
|
username: nologin
|
||||||
|
|||||||
13
.github/workflows/build-public.yml
vendored
13
.github/workflows/build-public.yml
vendored
@@ -8,6 +8,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- '.github/workflows/build-public.yml'
|
- '.github/workflows/build-public.yml'
|
||||||
|
- '.dockerignore'
|
||||||
- 'docker/prod/**'
|
- 'docker/prod/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Check out code"
|
- name: "Check out code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required for WyriHaximus/github-action-get-previous-tag
|
fetch-depth: 0 # Required for WyriHaximus/github-action-get-previous-tag
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ jobs:
|
|||||||
if: steps.cache-vendor.outputs.cache-hit != 'true' # Skip if cache hit
|
if: steps.cache-vendor.outputs.cache-hit != 'true' # Skip if cache hit
|
||||||
|
|
||||||
- name: "Use Node.js"
|
- name: "Use Node.js"
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
@@ -116,13 +117,13 @@ jobs:
|
|||||||
${{ env.GHCR_REPO }}
|
${{ env.GHCR_REPO }}
|
||||||
|
|
||||||
- name: "Login to Docker Hub Container Registry"
|
- name: "Login to Docker Hub Container Registry"
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v4.6.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: "Login to GitHub Container Registry"
|
- name: "Login to GitHub Container Registry"
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v4.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -176,13 +177,13 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: "Login to Docker Hub"
|
- name: "Login to Docker Hub"
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v4.6.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: "Login to GHCR"
|
- name: "Login to GHCR"
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v4.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
|||||||
31
.github/workflows/extension-manifest-check.yml
vendored
Normal file
31
.github/workflows/extension-manifest-check.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: Extension Manifest Check
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- extensions/manifest.json
|
||||||
|
- .github/workflows/extension-manifest-check.yml
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
jobs:
|
||||||
|
check-refs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
# Extension refs must be pinned to a release tag (vX.Y.Z) or a full commit
|
||||||
|
# SHA. Branch names (main, feature/foo, ...) are only acceptable while
|
||||||
|
# developing and must be replaced before the pull request is merged.
|
||||||
|
- name: "Check extension refs are pinned"
|
||||||
|
run: |
|
||||||
|
failed=0
|
||||||
|
while IFS=$'\t' read -r name ref; do
|
||||||
|
if [[ "$ref" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.]+)?$ ]] || [[ "$ref" =~ ^[0-9a-f]{40}$ ]]; then
|
||||||
|
echo "✅ $name: $ref"
|
||||||
|
else
|
||||||
|
echo "::error file=extensions/manifest.json::Extension \"$name\" uses ref \"$ref\", which is not a release tag or commit SHA. Pin it before merging."
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
done < <(jq -r 'to_entries[] | [.key, .value.ref] | @tsv' extensions/manifest.json)
|
||||||
|
exit $failed
|
||||||
2
.github/workflows/generate-api-docs.yml
vendored
2
.github/workflows/generate-api-docs.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: "Setup PHP"
|
- name: "Setup PHP"
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
|
|||||||
4
.github/workflows/npm-build.yml
vendored
4
.github/workflows/npm-build.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: "Setup PHP (for Ziggy)"
|
- name: "Setup PHP (for Ziggy)"
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
run: composer install -n --prefer-dist
|
run: composer install -n --prefer-dist
|
||||||
|
|
||||||
- name: "Use Node.js"
|
- name: "Use Node.js"
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/npm-format-check.yml
vendored
6
.github/workflows/npm-format-check.yml
vendored
@@ -1,6 +1,8 @@
|
|||||||
name: NPM Format Check
|
name: NPM Format Check
|
||||||
|
|
||||||
on: [push]
|
on: [push]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
format-check:
|
format-check:
|
||||||
@@ -9,10 +11,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: "Use Node.js"
|
- name: "Use Node.js"
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/npm-lint.yml
vendored
4
.github/workflows/npm-lint.yml
vendored
@@ -11,10 +11,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: "Use Node.js"
|
- name: "Use Node.js"
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/npm-publish-api.yml
vendored
4
.github/workflows/npm-publish-api.yml
vendored
@@ -11,11 +11,11 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
# Setup .npmrc file to publish to npm
|
# Setup .npmrc file to publish to npm
|
||||||
- name: Install root project dependencies
|
- name: Install root project dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|||||||
4
.github/workflows/npm-publish-ui.yml
vendored
4
.github/workflows/npm-publish-ui.yml
vendored
@@ -11,9 +11,9 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
# Setup .npmrc file to publish to npm
|
# Setup .npmrc file to publish to npm
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|||||||
4
.github/workflows/npm-test-unit.yml
vendored
4
.github/workflows/npm-test-unit.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: "Use Node.js"
|
- name: "Use Node.js"
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/npm-typecheck.yml
vendored
4
.github/workflows/npm-typecheck.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: "Setup PHP (for Ziggy)"
|
- name: "Setup PHP (for Ziggy)"
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
run: composer install -n --prefer-dist
|
run: composer install -n --prefer-dist
|
||||||
|
|
||||||
- name: "Use Node.js"
|
- name: "Use Node.js"
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/phpstan.yml
vendored
2
.github/workflows/phpstan.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: "Setup PHP"
|
- name: "Setup PHP"
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
|
|||||||
136
.github/workflows/phpunit-extensions.yml
vendored
Normal file
136
.github/workflows/phpunit-extensions.yml
vendored
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
name: PHPUnit Tests - Extensions
|
||||||
|
on: push
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
jobs:
|
||||||
|
phpunit-extensions:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
postgres_version: [ 15, 16, 17 ]
|
||||||
|
|
||||||
|
services:
|
||||||
|
pgsql_test:
|
||||||
|
image: postgres:${{ matrix.postgres_version }}
|
||||||
|
env:
|
||||||
|
PGPASSWORD: 'root'
|
||||||
|
POSTGRES_DB: 'laravel'
|
||||||
|
POSTGRES_USER: 'root'
|
||||||
|
POSTGRES_PASSWORD: 'root'
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
gotenberg:
|
||||||
|
image: gotenberg/gotenberg:8
|
||||||
|
ports:
|
||||||
|
- 3000:3000
|
||||||
|
options: >-
|
||||||
|
--health-cmd "curl --silent --fail http://localhost:3000/health"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: "Setup PHP"
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.3'
|
||||||
|
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv
|
||||||
|
coverage: pcov
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v7
|
||||||
|
with:
|
||||||
|
node-version: '20.x'
|
||||||
|
|
||||||
|
- name: "Read extension manifest"
|
||||||
|
id: extension-manifest
|
||||||
|
run: |
|
||||||
|
{
|
||||||
|
echo "billing_repository=$(jq -r '.Billing.repository' extensions/manifest.json)"
|
||||||
|
echo "billing_ref=$(jq -r '.Billing.ref' extensions/manifest.json)"
|
||||||
|
echo "services_repository=$(jq -r '.Services.repository' extensions/manifest.json)"
|
||||||
|
echo "services_ref=$(jq -r '.Services.ref' extensions/manifest.json)"
|
||||||
|
echo "invoicing_repository=$(jq -r '.Invoicing.repository' extensions/manifest.json)"
|
||||||
|
echo "invoicing_ref=$(jq -r '.Invoicing.ref' extensions/manifest.json)"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: "Checkout billing extension"
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
repository: ${{ steps.extension-manifest.outputs.billing_repository }}
|
||||||
|
ref: ${{ steps.extension-manifest.outputs.billing_ref }}
|
||||||
|
path: extensions/Billing
|
||||||
|
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_BILLING_EXTENSION }}
|
||||||
|
|
||||||
|
- name: "Install composer dependencies in billing extension"
|
||||||
|
working-directory: extensions/Billing
|
||||||
|
env:
|
||||||
|
COMPOSER_AUTH: '{"http-basic": {"spark.laravel.com": {"username": "contact@solidtime.io", "password": "${{ secrets.LARAVEL_SPARK_API_KEY }}"}}}'
|
||||||
|
run: composer install -n --prefer-dist --ignore-platform-reqs
|
||||||
|
|
||||||
|
- name: "Install npm dependencies in billing extension"
|
||||||
|
run: cd extensions/Billing && npm ci
|
||||||
|
|
||||||
|
- name: "Checkout services extension"
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
repository: ${{ steps.extension-manifest.outputs.services_repository }}
|
||||||
|
ref: ${{ steps.extension-manifest.outputs.services_ref }}
|
||||||
|
path: extensions/Services
|
||||||
|
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_SERVICES_EXTENSION }}
|
||||||
|
|
||||||
|
- name: "Install composer dependencies in services extension"
|
||||||
|
working-directory: extensions/Services
|
||||||
|
run: composer install -n --prefer-dist --ignore-platform-reqs
|
||||||
|
|
||||||
|
- name: "Install npm dependencies in services extension"
|
||||||
|
run: cd extensions/Services && npm ci
|
||||||
|
|
||||||
|
- name: "Checkout invoicing extension"
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
repository: ${{ steps.extension-manifest.outputs.invoicing_repository }}
|
||||||
|
ref: ${{ steps.extension-manifest.outputs.invoicing_ref }}
|
||||||
|
path: extensions/Invoicing
|
||||||
|
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_INVOICING_EXTENSION }}
|
||||||
|
|
||||||
|
- name: "Install composer dependencies in invoicing extension"
|
||||||
|
working-directory: extensions/Invoicing
|
||||||
|
run: composer install -n --prefer-dist --ignore-platform-reqs
|
||||||
|
|
||||||
|
- name: "Install npm dependencies in invoicing extension"
|
||||||
|
run: cd extensions/Invoicing && npm ci
|
||||||
|
|
||||||
|
- name: "Run composer install"
|
||||||
|
run: composer install -n --prefer-dist --ignore-platform-reqs
|
||||||
|
|
||||||
|
- name: "Activate billing extension"
|
||||||
|
run: php artisan module:enable Billing
|
||||||
|
|
||||||
|
- name: "Activate services extension"
|
||||||
|
run: php artisan module:enable Services
|
||||||
|
|
||||||
|
- name: "Activate invoicing extension"
|
||||||
|
run: php artisan module:enable Invoicing
|
||||||
|
|
||||||
|
- name: "Install dependencies"
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: "Build Frontend"
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: "Prepare Laravel Application"
|
||||||
|
run: |
|
||||||
|
cp .env.ci .env
|
||||||
|
php artisan key:generate
|
||||||
|
php artisan passport:keys
|
||||||
|
|
||||||
|
- name: "Run PHPUnit"
|
||||||
|
run: php artisan test extensions/Billing/tests extensions/Services/tests extensions/Invoicing/tests --stop-on-failure
|
||||||
6
.github/workflows/phpunit.yml
vendored
6
.github/workflows/phpunit.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
--health-retries 5
|
--health-retries 5
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: "Setup PHP"
|
- name: "Setup PHP"
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
- name: "Run composer install"
|
- name: "Run composer install"
|
||||||
run: composer install -n --prefer-dist
|
run: composer install -n --prefer-dist
|
||||||
|
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
run: php artisan test --stop-on-failure --coverage-text --coverage-clover=coverage.xml
|
run: php artisan test --stop-on-failure --coverage-text --coverage-clover=coverage.xml
|
||||||
|
|
||||||
- name: "Upload coverage reports to Codecov"
|
- name: "Upload coverage reports to Codecov"
|
||||||
uses: codecov/codecov-action@v7.0.0
|
uses: codecov/codecov-action@v7.1.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
slug: solidtime-io/solidtime
|
slug: solidtime-io/solidtime
|
||||||
|
|||||||
2
.github/workflows/pint.yml
vendored
2
.github/workflows/pint.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: "Check code style"
|
- name: "Check code style"
|
||||||
uses: aglipanci/laravel-pint-action@2.6
|
uses: aglipanci/laravel-pint-action@2.6
|
||||||
|
|||||||
8
.github/workflows/playwright.yml
vendored
8
.github/workflows/playwright.yml
vendored
@@ -35,10 +35,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: "Setup node"
|
- name: "Setup node"
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
@@ -99,10 +99,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: "Setup node"
|
- name: "Setup node"
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
|
|||||||
75
.github/workflows/vouch-check-pr.yml
vendored
Normal file
75
.github/workflows/vouch-check-pr.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
name: Vouch (check PR)
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, reopened, synchronize]
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
if: >-
|
||||||
|
github.event_name == 'pull_request_target' ||
|
||||||
|
(github.event_name == 'issue_comment' &&
|
||||||
|
github.event.issue.pull_request &&
|
||||||
|
contains(github.event.comment.body, '/recheck'))
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Pull requests of 50 changed lines or fewer skip the vouch requirement.
|
||||||
|
- name: "Measure diff size"
|
||||||
|
id: size
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
PR: ${{ github.event.pull_request.number || github.event.issue.number }}
|
||||||
|
|
||||||
|
# Changes to these files do not count towards the 50-line limit.
|
||||||
|
# One extended regex per line, matched against the whole repo-relative
|
||||||
|
# path, so use a leading .* to match a file in any directory.
|
||||||
|
IGNORED: |
|
||||||
|
package-lock\.json
|
||||||
|
composer\.lock
|
||||||
|
tests/.*
|
||||||
|
e2e/.*
|
||||||
|
.*\.(test|spec)\.(ts|js|vue)
|
||||||
|
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# An empty list yields "^()$", which matches no filename. grep exits
|
||||||
|
# 1 on an empty list, so swallow that rather than fail the step.
|
||||||
|
join() { { grep -vE '^[[:space:]]*$' || true; } | paste -sd'|' -; }
|
||||||
|
ignored="^($(join <<<"$IGNORED"))$"
|
||||||
|
|
||||||
|
total=$(gh api --paginate "repos/$REPO/pulls/$PR/files" \
|
||||||
|
--jq '.[] | [.filename, .additions + .deletions] | @tsv' |
|
||||||
|
awk -F'\t' -v ignored="$ignored" '
|
||||||
|
$1 ~ ignored { next }
|
||||||
|
{ n += $2 }
|
||||||
|
END { print n+0 }')
|
||||||
|
echo "total=$total" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Countable diff size: $total line(s)"
|
||||||
|
|
||||||
|
- name: "Small patch (denounced users still blocked)"
|
||||||
|
if: fromJSON(steps.size.outputs.total) <= 50
|
||||||
|
uses: mitchellh/vouch/action/check-pr@v1.5.0
|
||||||
|
with:
|
||||||
|
pr-number: ${{ github.event.pull_request.number || github.event.issue.number }}
|
||||||
|
auto-close: true
|
||||||
|
require-vouch: false
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: "Full vouch required"
|
||||||
|
if: fromJSON(steps.size.outputs.total) > 50
|
||||||
|
uses: mitchellh/vouch/action/check-pr@v1.5.0
|
||||||
|
with:
|
||||||
|
pr-number: ${{ github.event.pull_request.number || github.event.issue.number }}
|
||||||
|
auto-close: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
33
.github/workflows/vouch-manage-by-discussion.yml
vendored
Normal file
33
.github/workflows/vouch-manage-by-discussion.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
name: Vouch (manage by discussion)
|
||||||
|
|
||||||
|
# Same commands as vouch-manage-by-issue.yml, but for discussion comments.
|
||||||
|
|
||||||
|
on:
|
||||||
|
discussion_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: vouch-manage
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
discussions: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
manage:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: "Apply vouch command"
|
||||||
|
uses: mitchellh/vouch/action/manage-by-discussion@v1.5.0
|
||||||
|
with:
|
||||||
|
discussion-number: ${{ github.event.discussion.number }}
|
||||||
|
comment-node-id: ${{ github.event.comment.node_id }}
|
||||||
|
roles: admin,maintain,write
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
35
.github/workflows/vouch-manage-by-issue.yml
vendored
Normal file
35
.github/workflows/vouch-manage-by-issue.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: Vouch (manage by issue)
|
||||||
|
|
||||||
|
# Maintainers comment "vouch @user", "unvouch @user" or "denounce @user <reason>"
|
||||||
|
# on any issue or pull request, and this workflow updates .github/VOUCHED.td.
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: vouch-manage
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
manage:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: "Apply vouch command"
|
||||||
|
uses: mitchellh/vouch/action/manage-by-issue@v1.5.0
|
||||||
|
with:
|
||||||
|
issue-id: ${{ github.event.issue.number }}
|
||||||
|
comment-id: ${{ github.event.comment.id }}
|
||||||
|
roles: admin,maintain,write
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -6,6 +6,7 @@ dist
|
|||||||
/public/storage
|
/public/storage
|
||||||
/public/css
|
/public/css
|
||||||
/public/js
|
/public/js
|
||||||
|
/public/fonts/filament
|
||||||
/public/vendor
|
/public/vendor
|
||||||
/lang/vendor
|
/lang/vendor
|
||||||
/storage/*.key
|
/storage/*.key
|
||||||
@@ -26,9 +27,10 @@ yarn-error.log
|
|||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
/coverage
|
/coverage
|
||||||
/extensions
|
/extensions/*
|
||||||
!/extensions/.gitkeep
|
!/extensions/.gitkeep
|
||||||
!/extensions/extensions_autoload.php
|
!/extensions/extensions_autoload.php
|
||||||
|
!/extensions/manifest.json
|
||||||
/auth.json
|
/auth.json
|
||||||
/modules_statuses.json
|
/modules_statuses.json
|
||||||
/k8s
|
/k8s
|
||||||
@@ -42,4 +44,5 @@ yarn-error.log
|
|||||||
/data
|
/data
|
||||||
/config/caddy
|
/config/caddy
|
||||||
/config/composer
|
/config/composer
|
||||||
|
/config/psysh
|
||||||
/AGENTS.md
|
/AGENTS.md
|
||||||
|
|||||||
@@ -12,6 +12,22 @@ In order to keep the issues of the repository clean we decided to only use them
|
|||||||
|
|
||||||
To respect your time and help us manage contributions effectively, please open an issue or start a discussion and wait for approval before submitting a pull request (PR). This does not apply to tiny fixes or changes however, please keep in mind that we might not merge PRs for various reasons.
|
To respect your time and help us manage contributions effectively, please open an issue or start a discussion and wait for approval before submitting a pull request (PR). This does not apply to tiny fixes or changes however, please keep in mind that we might not merge PRs for various reasons.
|
||||||
|
|
||||||
|
### Vouched contributors
|
||||||
|
|
||||||
|
Pull requests from authors who are not vouched are closed automatically. This lets us keep up with the volume of AI slop pull requests without a maintainer having to triage every one of them by hand.
|
||||||
|
|
||||||
|
Your pull request is not affected if any of the following applies:
|
||||||
|
|
||||||
|
- You have write access to this repository.
|
||||||
|
- Someone with write access has vouched for you. The list lives in [.github/VOUCHED.td](.github/VOUCHED.td).
|
||||||
|
- Your pull request changes 50 lines or fewer. Test files and lockfiles do not count towards that number, so a small fix that comes with tests still qualifies.
|
||||||
|
|
||||||
|
To get vouched, open an issue or discussion before you start and explain how you intend to implement the change. We will discuss the approach with you, and only once we have agreed on the implementation does a maintainer comment `vouch @your-handle`, which puts you on the list from then on.
|
||||||
|
|
||||||
|
Being vouched only stops your pull requests from being closed automatically. [Only work on approved issues](#only-work-on-approved-issues) still applies to every pull request you send.
|
||||||
|
|
||||||
|
Contributors who abuse this are denounced, and their pull requests are closed regardless of size.
|
||||||
|
|
||||||
### Contributor License Agreement
|
### Contributor License Agreement
|
||||||
|
|
||||||
You'll also notice that we’ve set up a [Contributor License Agreement (CLA)](https://cla-assistant.io/solidtime-io/solidtime), which must be signed before any PR can be merged. Don’t worry - the process is quick and only takes a few clicks.
|
You'll also notice that we’ve set up a [Contributor License Agreement (CLA)](https://cla-assistant.io/solidtime-io/solidtime), which must be signed before any PR can be merged. Don’t worry - the process is quick and only takes a few clicks.
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ Please open an issue or start a discussion and wait for approval before submitti
|
|||||||
|
|
||||||
**If you submit an AI slop pull request (especially without following the proper procedure), you will be banned from future contributions to solidtime.**
|
**If you submit an AI slop pull request (especially without following the proper procedure), you will be banned from future contributions to solidtime.**
|
||||||
|
|
||||||
|
To keep that manageable, pull requests from authors who are not vouched are closed automatically, unless they change 50 lines or fewer. To get vouched, open an issue or discussion first and explain how you intend to implement the change. Once we have agreed on the approach, we vouch for you. See [Vouched contributors](./CONTRIBUTING.md#vouched-contributors).
|
||||||
|
|
||||||
Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) before sumbitting a Pull Request.
|
Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) before sumbitting a Pull Request.
|
||||||
|
|
||||||
We do accept contributions in the [documentation repository](https://github.com/solidtime-io/docs) f.e. to add new self-hosting guides.
|
We do accept contributions in the [documentation repository](https://github.com/solidtime-io/docs) f.e. to add new self-hosting guides.
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Actions\Fortify;
|
namespace App\Actions\Fortify;
|
||||||
|
|
||||||
|
use App\Enums\RegistrationMode;
|
||||||
use App\Enums\Weekday;
|
use App\Enums\Weekday;
|
||||||
use App\Events\NewsletterRegistered;
|
use App\Events\NewsletterRegistered;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Service\InvitationService;
|
||||||
use App\Service\IpLookup\IpLookupServiceContract;
|
use App\Service\IpLookup\IpLookupServiceContract;
|
||||||
use App\Service\TimezoneService;
|
use App\Service\TimezoneService;
|
||||||
use App\Service\UserService;
|
use App\Service\UserService;
|
||||||
@@ -31,13 +33,14 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
*/
|
*/
|
||||||
public function create(array $input): User
|
public function create(array $input): User
|
||||||
{
|
{
|
||||||
if (! config('app.enable_registration')) {
|
$registrationMode = RegistrationMode::fromConfig(config('app.enable_registration'));
|
||||||
|
if ($registrationMode === RegistrationMode::Off) {
|
||||||
throw ValidationException::withMessages([
|
throw ValidationException::withMessages([
|
||||||
'email' => [__('Registration is disabled.')],
|
'email' => [__('Registration is disabled.')],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Validator::make($input, [
|
$validated = Validator::make($input, [
|
||||||
'name' => [
|
'name' => [
|
||||||
'required',
|
'required',
|
||||||
'string',
|
'string',
|
||||||
@@ -60,6 +63,20 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
],
|
],
|
||||||
])->validate();
|
])->validate();
|
||||||
|
|
||||||
|
if ($registrationMode === RegistrationMode::InviteOnly) {
|
||||||
|
$invitationService = app(InvitationService::class);
|
||||||
|
$email = (string) $validated['email'];
|
||||||
|
if (! $invitationService->hasAcceptedInvitationForEmail($email)) {
|
||||||
|
$message = $invitationService->hasPendingInvitationForEmail($email)
|
||||||
|
? __('Please accept the organization invitation sent to your email address before registering.')
|
||||||
|
: __('Registration is only available to invited users.');
|
||||||
|
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'email' => [$message],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$timezone = null;
|
$timezone = null;
|
||||||
if (array_key_exists('timezone', $input) && is_string($input['timezone'])) {
|
if (array_key_exists('timezone', $input) && is_string($input['timezone'])) {
|
||||||
if (app(TimezoneService::class)->isValid($input['timezone'])) {
|
if (app(TimezoneService::class)->isValid($input['timezone'])) {
|
||||||
|
|||||||
@@ -5,12 +5,8 @@ declare(strict_types=1);
|
|||||||
namespace App\Actions\Fortify;
|
namespace App\Actions\Fortify;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Providers\FortifyServiceProvider;
|
|
||||||
use Illuminate\Auth\Passwords\PasswordBroker;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Facades\Password;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
use Laravel\Fortify\Contracts\ResetsUserPasswords;
|
use Laravel\Fortify\Contracts\ResetsUserPasswords;
|
||||||
|
|
||||||
class ResetUserPassword implements ResetsUserPasswords
|
class ResetUserPassword implements ResetsUserPasswords
|
||||||
@@ -24,16 +20,6 @@ class ResetUserPassword implements ResetsUserPasswords
|
|||||||
*/
|
*/
|
||||||
public function reset(User $user, array $input): void
|
public function reset(User $user, array $input): void
|
||||||
{
|
{
|
||||||
if (! FortifyServiceProvider::canResetPassword($user, $input)) {
|
|
||||||
/** @var PasswordBroker $broker */
|
|
||||||
$broker = Password::broker(config('fortify.passwords'));
|
|
||||||
$broker->deleteToken($user);
|
|
||||||
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
'email' => [__('This password reset link is invalid.')],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Validator::make($input, [
|
Validator::make($input, [
|
||||||
'password' => $this->passwordRules(),
|
'password' => $this->passwordRules(),
|
||||||
])->validate();
|
])->validate();
|
||||||
|
|||||||
38
app/Auth/ActiveUserProvider.php
Normal file
38
app/Auth/ActiveUserProvider.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Auth;
|
||||||
|
|
||||||
|
use Illuminate\Auth\EloquentUserProvider;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User provider that only resolves non-placeholder users.
|
||||||
|
*
|
||||||
|
* Placeholder users are created by imports and when members are removed from an
|
||||||
|
* organization. They can share an email address with a real user, so resolving a user by
|
||||||
|
* email can return a placeholder instead of the real account. The login flow filters them
|
||||||
|
* out explicitly, but the password broker and the guard credential checks (for example the
|
||||||
|
* password confirmation) resolve users through the configured user provider.
|
||||||
|
*
|
||||||
|
* Registered as the "eloquent" provider driver in the AuthServiceProvider, so it replaces the
|
||||||
|
* built-in one for every provider in config/auth.php.
|
||||||
|
*/
|
||||||
|
class ActiveUserProvider extends EloquentUserProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Model|null $model
|
||||||
|
* @return Builder<Model>
|
||||||
|
*/
|
||||||
|
#[Override]
|
||||||
|
protected function newModelQuery($model = null): Builder
|
||||||
|
{
|
||||||
|
$query = parent::newModelQuery($model);
|
||||||
|
$query->getQuery()->where('is_placeholder', '=', false);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ use Illuminate\Database\Query\JoinClause;
|
|||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
class SelfHostDatabaseConsistency extends Command
|
class SelfHostDatabaseConsistency extends Command
|
||||||
{
|
{
|
||||||
@@ -101,7 +102,7 @@ class SelfHostDatabaseConsistency extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Collection<int, \stdClass> $problems
|
* @param Collection<int, stdClass> $problems
|
||||||
*/
|
*/
|
||||||
private function logProblems(Collection $problems, string $message, bool &$hadAProblem): void
|
private function logProblems(Collection $problems, string $message, bool &$hadAProblem): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace App\Console\Commands\SelfHost;
|
|||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Encryption\Encrypter;
|
use Illuminate\Encryption\Encrypter;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use phpseclib3\Crypt\RSA;
|
use phpseclib4\Crypt\RSA;
|
||||||
|
|
||||||
class SelfHostGenerateKeysCommand extends Command
|
class SelfHostGenerateKeysCommand extends Command
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ class TimeEntrySendStillRunningMailsCommand extends Command
|
|||||||
])
|
])
|
||||||
->whereHas('user', function (Builder $query): void {
|
->whereHas('user', function (Builder $query): void {
|
||||||
/** @var Builder<User> $query */
|
/** @var Builder<User> $query */
|
||||||
$query->where('is_placeholder', '=', false);
|
$query->where('is_placeholder', '=', false)
|
||||||
|
->where('send_time_entry_still_running_email', '=', true);
|
||||||
})
|
})
|
||||||
->orderBy('created_at', 'asc')
|
->orderBy('created_at', 'asc')
|
||||||
->chunk(500, function (Collection $timeEntries) use ($dryRun, &$sentMails): void {
|
->chunk(500, function (Collection $timeEntries) use ($dryRun, &$sentMails): void {
|
||||||
|
|||||||
22
app/Enums/GoalComparison.php
Normal file
22
app/Enums/GoalComparison.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Datomatic\LaravelEnumHelper\LaravelEnumHelper;
|
||||||
|
|
||||||
|
enum GoalComparison: string
|
||||||
|
{
|
||||||
|
use LaravelEnumHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The goal is reached when at least the target amount of time was tracked in the period.
|
||||||
|
*/
|
||||||
|
case AtLeast = 'at_least';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The goal is reached when less than the target amount of time was tracked in the period.
|
||||||
|
*/
|
||||||
|
case LessThan = 'less_than';
|
||||||
|
}
|
||||||
16
app/Enums/GoalPeriod.php
Normal file
16
app/Enums/GoalPeriod.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Datomatic\LaravelEnumHelper\LaravelEnumHelper;
|
||||||
|
|
||||||
|
enum GoalPeriod: string
|
||||||
|
{
|
||||||
|
use LaravelEnumHelper;
|
||||||
|
|
||||||
|
case Day = 'day';
|
||||||
|
case Week = 'week';
|
||||||
|
case Month = 'month';
|
||||||
|
}
|
||||||
32
app/Enums/GoalStatus.php
Normal file
32
app/Enums/GoalStatus.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Datomatic\LaravelEnumHelper\LaravelEnumHelper;
|
||||||
|
|
||||||
|
enum GoalStatus: string
|
||||||
|
{
|
||||||
|
use LaravelEnumHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "At least" goal, the target has not been reached yet in the current period.
|
||||||
|
*/
|
||||||
|
case InProgress = 'in_progress';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "At least" goal, the target has been reached in the current period.
|
||||||
|
*/
|
||||||
|
case Achieved = 'achieved';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Less than" goal, the tracked time is still below the limit.
|
||||||
|
*/
|
||||||
|
case OnTrack = 'on_track';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Less than" goal, the tracked time reached or exceeded the limit.
|
||||||
|
*/
|
||||||
|
case Exceeded = 'exceeded';
|
||||||
|
}
|
||||||
24
app/Enums/GoalType.php
Normal file
24
app/Enums/GoalType.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Datomatic\LaravelEnumHelper\LaravelEnumHelper;
|
||||||
|
|
||||||
|
enum GoalType: string
|
||||||
|
{
|
||||||
|
use LaravelEnumHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A goal a member set for themselves. Only that member can see and manage it, no permission reaches it.
|
||||||
|
*/
|
||||||
|
case Personal = 'personal';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A goal the organization set for a member or for the whole organization.
|
||||||
|
* Managed by the members that may manage goals, visible to them and to the member the goal is for.
|
||||||
|
* Requires the team goals extension.
|
||||||
|
*/
|
||||||
|
case Organization = 'organization';
|
||||||
|
}
|
||||||
29
app/Enums/RegistrationMode.php
Normal file
29
app/Enums/RegistrationMode.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum RegistrationMode: string
|
||||||
|
{
|
||||||
|
case On = 'on';
|
||||||
|
case InviteOnly = 'invite-only';
|
||||||
|
case Off = 'off';
|
||||||
|
|
||||||
|
public static function fromConfig(mixed $value): self
|
||||||
|
{
|
||||||
|
if ($value === true) {
|
||||||
|
return self::On;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value === false || $value === null) {
|
||||||
|
return self::Off;
|
||||||
|
}
|
||||||
|
|
||||||
|
return match (strtolower(trim((string) $value))) {
|
||||||
|
'1', 'on', 'true' => self::On,
|
||||||
|
'invite-only' => self::InviteOnly,
|
||||||
|
default => self::Off,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/Enums/TagMatchType.php
Normal file
16
app/Enums/TagMatchType.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Datomatic\LaravelEnumHelper\LaravelEnumHelper;
|
||||||
|
|
||||||
|
enum TagMatchType: string
|
||||||
|
{
|
||||||
|
use LaravelEnumHelper;
|
||||||
|
|
||||||
|
case Contains = 'contains';
|
||||||
|
|
||||||
|
case NotContains = 'not_contains';
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ enum TimeEntryAggregationType: string
|
|||||||
case Billable = 'billable';
|
case Billable = 'billable';
|
||||||
case Description = 'description';
|
case Description = 'description';
|
||||||
case Tag = 'tag';
|
case Tag = 'tag';
|
||||||
|
case Type = 'type';
|
||||||
|
|
||||||
public static function fromInterval(TimeEntryAggregationTypeInterval $timeEntryAggregationTypeInterval): TimeEntryAggregationType
|
public static function fromInterval(TimeEntryAggregationTypeInterval $timeEntryAggregationTypeInterval): TimeEntryAggregationType
|
||||||
{
|
{
|
||||||
|
|||||||
15
app/Enums/TimeEntryType.php
Normal file
15
app/Enums/TimeEntryType.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Datomatic\LaravelEnumHelper\LaravelEnumHelper;
|
||||||
|
|
||||||
|
enum TimeEntryType: string
|
||||||
|
{
|
||||||
|
use LaravelEnumHelper;
|
||||||
|
|
||||||
|
case Work = 'work';
|
||||||
|
case Break = 'break';
|
||||||
|
}
|
||||||
@@ -60,7 +60,6 @@ abstract class ApiException extends Exception
|
|||||||
*/
|
*/
|
||||||
public function report(): bool
|
public function report(): bool
|
||||||
{
|
{
|
||||||
// TODO: temporary activated
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers extends ApiException
|
class CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'can_not_delete_user_who_is_owner_of_organization_with_multiple_members';
|
public const string KEY = 'can_not_delete_user_who_is_owner_of_organization_with_multiple_members';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class CanNotRemoveOwnerFromOrganization extends ApiException
|
class CanNotRemoveOwnerFromOrganization extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'can_not_remove_owner_from_organization';
|
public const string KEY = 'can_not_remove_owner_from_organization';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class ChangingRoleOfPlaceholderIsNotAllowed extends ApiException
|
class ChangingRoleOfPlaceholderIsNotAllowed extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'changing_role_of_placeholder_is_not_allowed';
|
public const string KEY = 'changing_role_of_placeholder_is_not_allowed';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class ChangingRoleToPlaceholderIsNotAllowed extends ApiException
|
class ChangingRoleToPlaceholderIsNotAllowed extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'changing_role_to_placeholder_is_not_allowed';
|
public const string KEY = 'changing_role_to_placeholder_is_not_allowed';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Exceptions\Api;
|
namespace App\Exceptions\Api;
|
||||||
|
|
||||||
|
use Override;
|
||||||
|
|
||||||
class EntityStillInUseApiException extends ApiException
|
class EntityStillInUseApiException extends ApiException
|
||||||
{
|
{
|
||||||
private string $modelToDelete;
|
private string $modelToDelete;
|
||||||
@@ -19,10 +21,20 @@ class EntityStillInUseApiException extends ApiException
|
|||||||
|
|
||||||
public const string KEY = 'entity_still_in_use';
|
public const string KEY = 'entity_still_in_use';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report the exception.
|
||||||
|
*
|
||||||
|
* @return bool true means the exception handler will not report it again
|
||||||
|
*/
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the translated message for the exception.
|
* Get the translated message for the exception.
|
||||||
*/
|
*/
|
||||||
#[\Override]
|
#[Override]
|
||||||
public function getTranslatedMessage(): string
|
public function getTranslatedMessage(): string
|
||||||
{
|
{
|
||||||
return __('exceptions.api.'.$this->getKey(), [
|
return __('exceptions.api.'.$this->getKey(), [
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class FeatureIsNotAvailableInFreePlanApiException extends ApiException
|
class FeatureIsNotAvailableInFreePlanApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'feature_is_not_available_in_free_plan';
|
public const string KEY = 'feature_is_not_available_in_free_plan';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class InactiveUserCanNotBeUsedApiException extends ApiException
|
class InactiveUserCanNotBeUsedApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'inactive_user_can_not_be_used';
|
public const string KEY = 'inactive_user_can_not_be_used';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class InvitationForTheEmailAlreadyExistsApiException extends ApiException
|
class InvitationForTheEmailAlreadyExistsApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'invitation_for_the_email_already_exists';
|
public const string KEY = 'invitation_for_the_email_already_exists';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class OnlyOwnerCanChangeOwnership extends ApiException
|
class OnlyOwnerCanChangeOwnership extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'only_owner_can_change_ownership';
|
public const string KEY = 'only_owner_can_change_ownership';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class OnlyPlaceholdersCanBeMergedIntoAnotherMember extends ApiException
|
class OnlyPlaceholdersCanBeMergedIntoAnotherMember extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'only_placeholders_can_be_merged_into_another_member';
|
public const string KEY = 'only_placeholders_can_be_merged_into_another_member';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class OrganizationHasNoSubscriptionButMultipleMembersException extends ApiException
|
class OrganizationHasNoSubscriptionButMultipleMembersException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'organization_has_no_subscription_but_multiple_members';
|
public const string KEY = 'organization_has_no_subscription_but_multiple_members';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class OrganizationNeedsAtLeastOneOwner extends ApiException
|
class OrganizationNeedsAtLeastOneOwner extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'organization_needs_at_least_one_owner';
|
public const string KEY = 'organization_needs_at_least_one_owner';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class OverlappingTimeEntryApiException extends ApiException
|
class OverlappingTimeEntryApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'overlapping_time_entry';
|
public const string KEY = 'overlapping_time_entry';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class ThisPlaceholderCanNotBeInvitedUseTheMergeToolInsteadException extends ApiException
|
class ThisPlaceholderCanNotBeInvitedUseTheMergeToolInsteadException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'this_placeholder_can_not_be_invited_use_the_merge_tool_instead_api_exception';
|
public const string KEY = 'this_placeholder_can_not_be_invited_use_the_merge_tool_instead_api_exception';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class TimeEntryCanNotBeRestartedApiException extends ApiException
|
class TimeEntryCanNotBeRestartedApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'time_entry_can_not_be_restarted';
|
public const string KEY = 'time_entry_can_not_be_restarted';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,14 @@ namespace App\Exceptions\Api;
|
|||||||
class TimeEntryStillRunningApiException extends ApiException
|
class TimeEntryStillRunningApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'time_entry_still_running';
|
public const string KEY = 'time_entry_still_running';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report the exception.
|
||||||
|
*
|
||||||
|
* @return bool true means the exception handler will not report it again
|
||||||
|
*/
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class UserIsAlreadyMemberOfOrganizationApiException extends ApiException
|
class UserIsAlreadyMemberOfOrganizationApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'user_is_already_member_of_organization';
|
public const string KEY = 'user_is_already_member_of_organization';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class UserIsAlreadyMemberOfProjectApiException extends ApiException
|
class UserIsAlreadyMemberOfProjectApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'user_is_already_member_of_project';
|
public const string KEY = 'user_is_already_member_of_project';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class UserNotPlaceholderApiException extends ApiException
|
class UserNotPlaceholderApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'user_not_placeholder';
|
public const string KEY = 'user_not_placeholder';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class UserResendEmailVerificationNoPendingEmailApiException extends ApiException
|
class UserResendEmailVerificationNoPendingEmailApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'user_resend_email_verification_no_pending_email';
|
public const string KEY = 'user_resend_email_verification_no_pending_email';
|
||||||
|
|
||||||
|
public function report(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ namespace App\Exceptions;
|
|||||||
|
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
|
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class Handler extends ExceptionHandler
|
class Handler extends ExceptionHandler
|
||||||
@@ -30,6 +34,33 @@ class Handler extends ExceptionHandler
|
|||||||
$this->reportable(function (Throwable $e): void {
|
$this->reportable(function (Throwable $e): void {
|
||||||
//
|
//
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->dontReportWhen(fn (Throwable $e): bool => $e instanceof OAuthServerException
|
||||||
|
&& $e->getErrorType() === 'access_denied'
|
||||||
|
&& $e->getHttpStatusCode() === 401);
|
||||||
|
|
||||||
|
// A request on an untrusted host (see App\Http\Middleware\TrustHosts)
|
||||||
|
// otherwise renders as a bare "Bad request." 400. Show a message that
|
||||||
|
// says how to fix it instead. The framework has already converted the
|
||||||
|
// SuspiciousOperationException into a BadRequestHttpException by the time
|
||||||
|
// renderables run, so we match that and inspect the original.
|
||||||
|
$this->renderable(function (BadRequestHttpException $e, Request $request): ?Response {
|
||||||
|
$previous = $e->getPrevious();
|
||||||
|
|
||||||
|
if (! $previous instanceof SuspiciousOperationException
|
||||||
|
|| ! str_starts_with($previous->getMessage(), 'Untrusted Host')) {
|
||||||
|
return null; // any other bad request keeps the default response
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = 'This hostname is not configured for this instance. '
|
||||||
|
.'Set APP_URL, or add the host to TRUSTED_HOSTS.';
|
||||||
|
|
||||||
|
if ($request->expectsJson()) {
|
||||||
|
return response()->json(['message' => $message], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->view('errors.untrusted-host', ['message' => $message], 400);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render($request, Throwable $e): Response|RedirectResponse
|
public function render($request, Throwable $e): Response|RedirectResponse
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Filament\Resources;
|
|
||||||
|
|
||||||
use App\Filament\Resources\AuditResource\Pages;
|
|
||||||
use App\Models\Audit;
|
|
||||||
use Filament\Forms;
|
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Resources\Resource;
|
|
||||||
use Filament\Tables;
|
|
||||||
use Filament\Tables\Columns\IconColumn;
|
|
||||||
use Filament\Tables\Table;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Novadaemon\FilamentPrettyJson\Form\PrettyJsonField;
|
|
||||||
|
|
||||||
class AuditResource extends Resource
|
|
||||||
{
|
|
||||||
protected static ?string $model = Audit::class;
|
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'heroicon-o-archive-box';
|
|
||||||
|
|
||||||
protected static ?string $navigationGroup = 'System';
|
|
||||||
|
|
||||||
public static function form(Form $form): Form
|
|
||||||
{
|
|
||||||
return $form
|
|
||||||
->schema([
|
|
||||||
Forms\Components\TextInput::make('user_type')
|
|
||||||
->maxLength(255),
|
|
||||||
Forms\Components\TextInput::make('user_id'),
|
|
||||||
Forms\Components\TextInput::make('event')
|
|
||||||
->required()
|
|
||||||
->maxLength(255),
|
|
||||||
Forms\Components\TextInput::make('auditable_type')
|
|
||||||
->required()
|
|
||||||
->maxLength(255),
|
|
||||||
Forms\Components\TextInput::make('auditable_id')
|
|
||||||
->required(),
|
|
||||||
PrettyJsonField::make('old_values'),
|
|
||||||
PrettyJsonField::make('new_values'),
|
|
||||||
Forms\Components\Textarea::make('url'),
|
|
||||||
Forms\Components\TextInput::make('ip_address'),
|
|
||||||
Forms\Components\TextInput::make('user_agent')
|
|
||||||
->maxLength(1023),
|
|
||||||
Forms\Components\TextInput::make('tags')
|
|
||||||
->maxLength(255),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function table(Table $table): Table
|
|
||||||
{
|
|
||||||
return $table
|
|
||||||
->columns([
|
|
||||||
Tables\Columns\TextColumn::make('user.name'),
|
|
||||||
Tables\Columns\TextColumn::make('event'),
|
|
||||||
Tables\Columns\TextColumn::make('auditable_type'),
|
|
||||||
Tables\Columns\TextColumn::make('auditable_id'),
|
|
||||||
IconColumn::make('was_command')
|
|
||||||
->getStateUsing(fn (Audit $record) => Str::startsWith($record->url, 'artisan '))
|
|
||||||
->boolean(),
|
|
||||||
Tables\Columns\TextColumn::make('created_at')
|
|
||||||
->sortable()
|
|
||||||
->dateTime(),
|
|
||||||
Tables\Columns\TextColumn::make('updated_at')
|
|
||||||
->sortable()
|
|
||||||
->dateTime(),
|
|
||||||
])
|
|
||||||
->filters([
|
|
||||||
//
|
|
||||||
])
|
|
||||||
->actions([
|
|
||||||
Tables\Actions\ViewAction::make(),
|
|
||||||
])
|
|
||||||
->bulkActions([
|
|
||||||
])
|
|
||||||
->defaultSort('created_at', 'desc');
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getRelations(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getPages(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'index' => Pages\ListAudits::route('/'),
|
|
||||||
'create' => Pages\CreateAudit::route('/create'),
|
|
||||||
'view' => Pages\ViewAudit::route('/{record}'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Filament\Resources\AuditResource\Pages;
|
|
||||||
|
|
||||||
use App\Filament\Resources\AuditResource;
|
|
||||||
use Filament\Resources\Pages\CreateRecord;
|
|
||||||
|
|
||||||
class CreateAudit extends CreateRecord
|
|
||||||
{
|
|
||||||
protected static string $resource = AuditResource::class;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Filament\Resources\AuditResource\Pages;
|
|
||||||
|
|
||||||
use App\Filament\Resources\AuditResource;
|
|
||||||
use Filament\Resources\Pages\ListRecords;
|
|
||||||
|
|
||||||
class ListAudits extends ListRecords
|
|
||||||
{
|
|
||||||
protected static string $resource = AuditResource::class;
|
|
||||||
|
|
||||||
protected function getHeaderActions(): array
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Filament\Resources\AuditResource\Pages;
|
|
||||||
|
|
||||||
use App\Filament\Resources\AuditResource;
|
|
||||||
use Filament\Resources\Pages\ViewRecord;
|
|
||||||
|
|
||||||
class ViewAudit extends ViewRecord
|
|
||||||
{
|
|
||||||
protected static string $resource = AuditResource::class;
|
|
||||||
}
|
|
||||||
@@ -4,13 +4,18 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\ClientResource\Pages;
|
use App\Filament\Resources\ClientResource\Pages\CreateClient;
|
||||||
|
use App\Filament\Resources\ClientResource\Pages\EditClient;
|
||||||
|
use App\Filament\Resources\ClientResource\Pages\ListClients;
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
|
use Filament\Actions\BulkActionGroup;
|
||||||
|
use Filament\Actions\DeleteBulkAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Schemas\Schema;
|
||||||
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
@@ -18,16 +23,16 @@ class ClientResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = Client::class;
|
protected static ?string $model = Client::class;
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'heroicon-o-briefcase';
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-briefcase';
|
||||||
|
|
||||||
protected static ?string $navigationGroup = 'Timetracking';
|
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 4;
|
protected static ?int $navigationSort = 4;
|
||||||
|
|
||||||
public static function form(Form $form): Form
|
public static function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
TextInput::make('name')
|
TextInput::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->required(),
|
->required(),
|
||||||
@@ -43,17 +48,17 @@ class ClientResource extends Resource
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
Tables\Columns\TextColumn::make('name')
|
TextColumn::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('organization.name')
|
TextColumn::make('organization.name')
|
||||||
->sortable()
|
->sortable()
|
||||||
->label('Organization'),
|
->label('Organization'),
|
||||||
Tables\Columns\TextColumn::make('created_at')
|
TextColumn::make('created_at')
|
||||||
->label('Created at')
|
->label('Created at')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('updated_at')
|
TextColumn::make('updated_at')
|
||||||
->label('Updated at')
|
->label('Updated at')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
])
|
])
|
||||||
@@ -68,12 +73,12 @@ class ClientResource extends Resource
|
|||||||
->relationship('organization', 'id')
|
->relationship('organization', 'id')
|
||||||
->searchable(),
|
->searchable(),
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
Tables\Actions\EditAction::make(),
|
EditAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->toolbarActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
BulkActionGroup::make([
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
DeleteBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -88,9 +93,9 @@ class ClientResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListClients::route('/'),
|
'index' => ListClients::route('/'),
|
||||||
'create' => Pages\CreateClient::route('/create'),
|
'create' => CreateClient::route('/create'),
|
||||||
'edit' => Pages\EditClient::route('/{record}/edit'),
|
'edit' => EditClient::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\ClientResource\Pages;
|
namespace App\Filament\Resources\ClientResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\ClientResource;
|
use App\Filament\Resources\ClientResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
class EditClient extends EditRecord
|
class EditClient extends EditRecord
|
||||||
@@ -15,7 +15,7 @@ class EditClient extends EditRecord
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\DeleteAction::make()
|
DeleteAction::make()
|
||||||
->icon('heroicon-m-trash'),
|
->icon('heroicon-m-trash'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\ClientResource\Pages;
|
namespace App\Filament\Resources\ClientResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\ClientResource;
|
use App\Filament\Resources\ClientResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
class ListClients extends ListRecords
|
class ListClients extends ListRecords
|
||||||
@@ -15,7 +15,7 @@ class ListClients extends ListRecords
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\CreateAction::make()
|
CreateAction::make()
|
||||||
->icon('heroicon-s-plus'),
|
->icon('heroicon-s-plus'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ namespace App\Filament\Resources;
|
|||||||
use App\Filament\Resources\FailedJobResource\Pages\ListFailedJobs;
|
use App\Filament\Resources\FailedJobResource\Pages\ListFailedJobs;
|
||||||
use App\Filament\Resources\FailedJobResource\Pages\ViewFailedJobs;
|
use App\Filament\Resources\FailedJobResource\Pages\ViewFailedJobs;
|
||||||
use App\Models\FailedJob;
|
use App\Models\FailedJob;
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Actions\BulkAction;
|
||||||
|
use Filament\Actions\DeleteAction;
|
||||||
|
use Filament\Actions\DeleteBulkAction;
|
||||||
|
use Filament\Actions\ViewAction;
|
||||||
use Filament\Forms\Components\Textarea;
|
use Filament\Forms\Components\Textarea;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables\Actions\Action;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Tables\Actions\BulkAction;
|
|
||||||
use Filament\Tables\Actions\DeleteAction;
|
|
||||||
use Filament\Tables\Actions\DeleteBulkAction;
|
|
||||||
use Filament\Tables\Actions\ViewAction;
|
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
@@ -30,19 +30,19 @@ class FailedJobResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = FailedJob::class;
|
protected static ?string $model = FailedJob::class;
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'heroicon-o-exclamation-circle';
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-exclamation-circle';
|
||||||
|
|
||||||
protected static ?string $navigationGroup = 'System';
|
protected static string|\UnitEnum|null $navigationGroup = 'System';
|
||||||
|
|
||||||
public static function getNavigationBadge(): ?string
|
public static function getNavigationBadge(): ?string
|
||||||
{
|
{
|
||||||
return (string) FailedJob::query()->count();
|
return (string) FailedJob::query()->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function form(Form $form): Form
|
public static function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
TextInput::make('uuid')->disabled()->columnSpan(4),
|
TextInput::make('uuid')->disabled()->columnSpan(4),
|
||||||
TextInput::make('failed_at')->disabled(),
|
TextInput::make('failed_at')->disabled(),
|
||||||
TextInput::make('id')->disabled(),
|
TextInput::make('id')->disabled(),
|
||||||
@@ -74,7 +74,7 @@ class FailedJobResource extends Resource
|
|||||||
TextColumn::make('queue')->sortable()->searchable()->toggleable(isToggledHiddenByDefault: true),
|
TextColumn::make('queue')->sortable()->searchable()->toggleable(isToggledHiddenByDefault: true),
|
||||||
])
|
])
|
||||||
->filters([])
|
->filters([])
|
||||||
->bulkActions([
|
->toolbarActions([
|
||||||
BulkAction::make('retry')
|
BulkAction::make('retry')
|
||||||
->icon('heroicon-o-arrow-path')
|
->icon('heroicon-o-arrow-path')
|
||||||
->label('Retry selected')
|
->label('Retry selected')
|
||||||
@@ -91,7 +91,7 @@ class FailedJobResource extends Resource
|
|||||||
}),
|
}),
|
||||||
DeleteBulkAction::make(),
|
DeleteBulkAction::make(),
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
DeleteAction::make(),
|
DeleteAction::make(),
|
||||||
ViewAction::make(),
|
ViewAction::make(),
|
||||||
Action::make('retry')
|
Action::make('retry')
|
||||||
|
|||||||
174
app/Filament/Resources/GoalResource.php
Normal file
174
app/Filament/Resources/GoalResource.php
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
|
use App\Enums\GoalComparison;
|
||||||
|
use App\Enums\GoalPeriod;
|
||||||
|
use App\Enums\GoalType;
|
||||||
|
use App\Enums\Weekday;
|
||||||
|
use App\Filament\Resources\GoalResource\Pages;
|
||||||
|
use App\Models\Goal;
|
||||||
|
use App\Service\Dto\GoalFiltersDto;
|
||||||
|
use Filament\Actions\DeleteAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Actions\ViewAction;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Components\DateTimePicker;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
|
use Filament\Tables\Columns\TextColumn;
|
||||||
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use Novadaemon\FilamentPrettyJson\Form\PrettyJsonField;
|
||||||
|
|
||||||
|
class GoalResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = Goal::class;
|
||||||
|
|
||||||
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-viewfinder-circle';
|
||||||
|
|
||||||
|
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
||||||
|
|
||||||
|
protected static ?int $navigationSort = 8;
|
||||||
|
|
||||||
|
public static function form(Schema $schema): Schema
|
||||||
|
{
|
||||||
|
return $schema
|
||||||
|
->columns(1)
|
||||||
|
->components([
|
||||||
|
Forms\Components\TextInput::make('name')
|
||||||
|
->label('Name')
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\Select::make('type')
|
||||||
|
->label('Type')
|
||||||
|
->options(collect(GoalType::cases())->mapWithKeys(fn (GoalType $type): array => [$type->value => $type->name])->all())
|
||||||
|
->disabled()
|
||||||
|
->required(),
|
||||||
|
Forms\Components\Select::make('comparison')
|
||||||
|
->label('Comparison')
|
||||||
|
->options(collect(GoalComparison::cases())->mapWithKeys(fn (GoalComparison $comparison): array => [$comparison->value => $comparison->name])->all())
|
||||||
|
->required(),
|
||||||
|
Forms\Components\TextInput::make('target_seconds')
|
||||||
|
->label('Target (seconds)')
|
||||||
|
->numeric()
|
||||||
|
->minValue(1)
|
||||||
|
->required(),
|
||||||
|
Forms\Components\Select::make('period')
|
||||||
|
->label('Period')
|
||||||
|
->options(collect(GoalPeriod::cases())->mapWithKeys(fn (GoalPeriod $period): array => [$period->value => $period->name])->all())
|
||||||
|
->required(),
|
||||||
|
Forms\Components\Select::make('timezone')
|
||||||
|
->label('Timezone')
|
||||||
|
->options(fn (): array => collect(\DateTimeZone::listIdentifiers())->mapWithKeys(fn (string $timezone): array => [$timezone => $timezone])->all())
|
||||||
|
->searchable()
|
||||||
|
->required(),
|
||||||
|
Forms\Components\Select::make('week_start')
|
||||||
|
->label('Week start')
|
||||||
|
->options(collect(Weekday::cases())->mapWithKeys(fn (Weekday $weekday): array => [$weekday->value => $weekday->name])->all())
|
||||||
|
->required(),
|
||||||
|
DateTimePicker::make('archived_at')
|
||||||
|
->label('Archived At'),
|
||||||
|
Forms\Components\Select::make('organization_id')
|
||||||
|
->label('Organization')
|
||||||
|
->relationship(name: 'organization', titleAttribute: 'name')
|
||||||
|
->searchable(['name'])
|
||||||
|
->disabled()
|
||||||
|
->required(),
|
||||||
|
Forms\Components\Select::make('member_id')
|
||||||
|
->label('Member (empty = every member)')
|
||||||
|
->relationship(name: 'member', titleAttribute: 'id')
|
||||||
|
->disabled(),
|
||||||
|
PrettyJsonField::make('filters')
|
||||||
|
->formatStateUsing(function (GoalFiltersDto $state, Goal $record): string {
|
||||||
|
return $record->getRawOriginal('filters');
|
||||||
|
})
|
||||||
|
->disabled(),
|
||||||
|
DateTimePicker::make('created_at')
|
||||||
|
->label('Created At')
|
||||||
|
->hiddenOn(['create'])
|
||||||
|
->disabled(),
|
||||||
|
DateTimePicker::make('updated_at')
|
||||||
|
->label('Updated At')
|
||||||
|
->hiddenOn(['create'])
|
||||||
|
->disabled(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
TextColumn::make('name')
|
||||||
|
->searchable()
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('comparison')
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('target_seconds')
|
||||||
|
->label('Target')
|
||||||
|
->formatStateUsing(fn (int $state): string => sprintf('%dh %02dm', intdiv($state, 3600), intdiv($state % 3600, 60)))
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('period')
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('type')
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('member.user.email')
|
||||||
|
->label('Member')
|
||||||
|
->placeholder('Every member')
|
||||||
|
->searchable(),
|
||||||
|
TextColumn::make('archived_at')
|
||||||
|
->dateTime()
|
||||||
|
->placeholder('Not archived')
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
TextColumn::make('organization.name')
|
||||||
|
->searchable()
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('created_at')
|
||||||
|
->dateTime()
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('updated_at')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
])
|
||||||
|
->defaultSort('created_at', 'desc')
|
||||||
|
->filters([
|
||||||
|
SelectFilter::make('type')
|
||||||
|
->label('Type')
|
||||||
|
->options(collect(GoalType::cases())->mapWithKeys(fn (GoalType $type): array => [$type->value => $type->name])->all()),
|
||||||
|
SelectFilter::make('organization')
|
||||||
|
->label('Organization')
|
||||||
|
->relationship('organization', 'name')
|
||||||
|
->searchable(),
|
||||||
|
SelectFilter::make('organization_id')
|
||||||
|
->label('Organization ID')
|
||||||
|
->relationship('organization', 'id')
|
||||||
|
->searchable(),
|
||||||
|
])
|
||||||
|
->recordActions([
|
||||||
|
ViewAction::make(),
|
||||||
|
EditAction::make(),
|
||||||
|
DeleteAction::make(),
|
||||||
|
])
|
||||||
|
->toolbarActions([
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListGoals::route('/'),
|
||||||
|
'edit' => Pages\EditGoal::route('/{record}/edit'),
|
||||||
|
'view' => Pages\ViewGoal::route('/{record}'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/Filament/Resources/GoalResource/Pages/EditGoal.php
Normal file
22
app/Filament/Resources/GoalResource/Pages/EditGoal.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\GoalResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\GoalResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditGoal extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = GoalResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make()
|
||||||
|
->icon('heroicon-m-trash'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/Filament/Resources/GoalResource/Pages/ListGoals.php
Normal file
19
app/Filament/Resources/GoalResource/Pages/ListGoals.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\GoalResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\GoalResource;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListGoals extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = GoalResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/Filament/Resources/GoalResource/Pages/ViewGoal.php
Normal file
22
app/Filament/Resources/GoalResource/Pages/ViewGoal.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\GoalResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\GoalResource;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Resources\Pages\ViewRecord;
|
||||||
|
|
||||||
|
class ViewGoal extends ViewRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = GoalResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
EditAction::make('edit')
|
||||||
|
->icon('heroicon-s-pencil'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,14 +5,21 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Enums\Role;
|
use App\Enums\Role;
|
||||||
use App\Filament\Resources\OrganizationInvitationResource\Pages;
|
use App\Filament\Resources\OrganizationInvitationResource\Pages\EditOrganizationInvitation;
|
||||||
|
use App\Filament\Resources\OrganizationInvitationResource\Pages\ListOrganizationInvitations;
|
||||||
|
use App\Filament\Resources\OrganizationInvitationResource\Pages\ViewOrganizationInvitation;
|
||||||
use App\Models\OrganizationInvitation;
|
use App\Models\OrganizationInvitation;
|
||||||
use App\Service\OrganizationInvitationService;
|
use App\Service\OrganizationInvitationService;
|
||||||
use Filament\Forms;
|
use Filament\Actions\BulkAction;
|
||||||
|
use Filament\Actions\BulkActionGroup;
|
||||||
|
use Filament\Actions\DeleteAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Forms\Components\DateTimePicker;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Form;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Schemas\Schema;
|
||||||
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
@@ -22,18 +29,18 @@ class OrganizationInvitationResource extends Resource
|
|||||||
|
|
||||||
protected static ?string $label = 'Invitations';
|
protected static ?string $label = 'Invitations';
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'heroicon-o-user-plus';
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-user-plus';
|
||||||
|
|
||||||
protected static ?string $navigationGroup = 'Users';
|
protected static string|\UnitEnum|null $navigationGroup = 'Users';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 9;
|
protected static ?int $navigationSort = 9;
|
||||||
|
|
||||||
public static function form(Form $form): Form
|
public static function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->columns(1)
|
->columns(1)
|
||||||
->schema([
|
->components([
|
||||||
Forms\Components\TextInput::make('email')
|
TextInput::make('email')
|
||||||
->label('Email')
|
->label('Email')
|
||||||
->disabledOn(['edit'])
|
->disabledOn(['edit'])
|
||||||
->required(),
|
->required(),
|
||||||
@@ -45,11 +52,11 @@ class OrganizationInvitationResource extends Resource
|
|||||||
->searchable(['name'])
|
->searchable(['name'])
|
||||||
->disabledOn(['edit'])
|
->disabledOn(['edit'])
|
||||||
->required(),
|
->required(),
|
||||||
Forms\Components\DateTimePicker::make('created_at')
|
DateTimePicker::make('created_at')
|
||||||
->label('Created At')
|
->label('Created At')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->disabled(),
|
->disabled(),
|
||||||
Forms\Components\DateTimePicker::make('updated_at')
|
DateTimePicker::make('updated_at')
|
||||||
->label('Updated At')
|
->label('Updated At')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->disabled(),
|
->disabled(),
|
||||||
@@ -60,17 +67,17 @@ class OrganizationInvitationResource extends Resource
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
Tables\Columns\TextColumn::make('organization.name')
|
TextColumn::make('organization.name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('email')
|
TextColumn::make('email')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('role'),
|
TextColumn::make('role'),
|
||||||
Tables\Columns\TextColumn::make('created_at')
|
TextColumn::make('created_at')
|
||||||
->label('Created At')
|
->label('Created At')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('updated_at')
|
TextColumn::make('updated_at')
|
||||||
->label('Updated At')
|
->label('Updated At')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable()
|
->sortable()
|
||||||
@@ -80,13 +87,13 @@ class OrganizationInvitationResource extends Resource
|
|||||||
->filters([
|
->filters([
|
||||||
//
|
//
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
Tables\Actions\EditAction::make(),
|
EditAction::make(),
|
||||||
Tables\Actions\DeleteAction::make(),
|
DeleteAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->toolbarActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
BulkActionGroup::make([
|
||||||
Tables\Actions\BulkAction::make('resend')
|
BulkAction::make('resend')
|
||||||
->label('Resend')
|
->label('Resend')
|
||||||
->action(function (Collection $records): void {
|
->action(function (Collection $records): void {
|
||||||
foreach ($records as $organizationInvite) {
|
foreach ($records as $organizationInvite) {
|
||||||
@@ -106,9 +113,9 @@ class OrganizationInvitationResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListOrganizationInvitations::route('/'),
|
'index' => ListOrganizationInvitations::route('/'),
|
||||||
'edit' => Pages\EditOrganizationInvitation::route('/{record}/edit'),
|
'edit' => EditOrganizationInvitation::route('/{record}/edit'),
|
||||||
'view' => Pages\ViewOrganizationInvitation::route('/{record}'),
|
'view' => ViewOrganizationInvitation::route('/{record}'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\OrganizationInvitationResource\Pages;
|
namespace App\Filament\Resources\OrganizationInvitationResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\OrganizationInvitationResource;
|
use App\Filament\Resources\OrganizationInvitationResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
class EditOrganizationInvitation extends EditRecord
|
class EditOrganizationInvitation extends EditRecord
|
||||||
@@ -15,7 +15,7 @@ class EditOrganizationInvitation extends EditRecord
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\DeleteAction::make()
|
DeleteAction::make()
|
||||||
->icon('heroicon-m-trash'),
|
->icon('heroicon-m-trash'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ use App\Enums\DateFormat;
|
|||||||
use App\Enums\IntervalFormat;
|
use App\Enums\IntervalFormat;
|
||||||
use App\Enums\NumberFormat;
|
use App\Enums\NumberFormat;
|
||||||
use App\Enums\TimeFormat;
|
use App\Enums\TimeFormat;
|
||||||
use App\Filament\Resources\OrganizationResource\Pages;
|
use App\Filament\Resources\OrganizationResource\Pages\CreateOrganization;
|
||||||
|
use App\Filament\Resources\OrganizationResource\Pages\EditOrganization;
|
||||||
|
use App\Filament\Resources\OrganizationResource\Pages\ListOrganizations;
|
||||||
|
use App\Filament\Resources\OrganizationResource\Pages\ViewOrganization;
|
||||||
use App\Filament\Resources\OrganizationResource\RelationManagers\InvitationsRelationManager;
|
use App\Filament\Resources\OrganizationResource\RelationManagers\InvitationsRelationManager;
|
||||||
use App\Filament\Resources\OrganizationResource\RelationManagers\UsersRelationManager;
|
use App\Filament\Resources\OrganizationResource\RelationManagers\UsersRelationManager;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
@@ -20,38 +23,45 @@ use App\Service\Import\Importers\ImportException;
|
|||||||
use App\Service\Import\Importers\ReportDto;
|
use App\Service\Import\Importers\ReportDto;
|
||||||
use App\Service\Import\ImportService;
|
use App\Service\Import\ImportService;
|
||||||
use App\Service\TimezoneService;
|
use App\Service\TimezoneService;
|
||||||
use Brick\Money\ISOCurrencyProvider;
|
use Brick\Money\IsoCurrencyProvider;
|
||||||
use Filament\Forms;
|
use Exception;
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Actions\DeleteAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Forms\Components\DateTimePicker;
|
||||||
|
use Filament\Forms\Components\FileUpload;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Form;
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Forms\Components\Toggle;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Tables\Actions\Action;
|
use Filament\Tables\Columns\IconColumn;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use League\Flysystem\UnableToReadFile;
|
||||||
|
|
||||||
class OrganizationResource extends Resource
|
class OrganizationResource extends Resource
|
||||||
{
|
{
|
||||||
protected static ?string $model = Organization::class;
|
protected static ?string $model = Organization::class;
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'heroicon-o-building-office-2';
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-building-office-2';
|
||||||
|
|
||||||
protected static ?string $navigationGroup = 'Users';
|
protected static string|\UnitEnum|null $navigationGroup = 'Users';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 7;
|
protected static ?int $navigationSort = 7;
|
||||||
|
|
||||||
public static function form(Form $form): Form
|
public static function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->columns(1)
|
->columns(1)
|
||||||
->schema([
|
->components([
|
||||||
Forms\Components\TextInput::make('name')
|
TextInput::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->required()
|
->required()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
Forms\Components\Toggle::make('personal_team')
|
Toggle::make('personal_team')
|
||||||
->label('Is personal?')
|
->label('Is personal?')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->required(),
|
->required(),
|
||||||
@@ -79,7 +89,7 @@ class OrganizationResource extends Resource
|
|||||||
Select::make('currency')
|
Select::make('currency')
|
||||||
->label('Currency')
|
->label('Currency')
|
||||||
->options(function (): array {
|
->options(function (): array {
|
||||||
$currencies = ISOCurrencyProvider::getInstance()->getAvailableCurrencies();
|
$currencies = IsoCurrencyProvider::getInstance()->getAvailableCurrencies();
|
||||||
$select = [];
|
$select = [];
|
||||||
foreach ($currencies as $currency) {
|
foreach ($currencies as $currency) {
|
||||||
$select[$currency->getCurrencyCode()] = $currency->getName().' ('.$currency->getCurrencyCode().')';
|
$select[$currency->getCurrencyCode()] = $currency->getName().' ('.$currency->getCurrencyCode().')';
|
||||||
@@ -89,7 +99,7 @@ class OrganizationResource extends Resource
|
|||||||
})
|
})
|
||||||
->required()
|
->required()
|
||||||
->searchable(),
|
->searchable(),
|
||||||
Forms\Components\TextInput::make('billable_rate')
|
TextInput::make('billable_rate')
|
||||||
->label('Billable rate (in Cents)')
|
->label('Billable rate (in Cents)')
|
||||||
->nullable()
|
->nullable()
|
||||||
->rules([
|
->rules([
|
||||||
@@ -99,11 +109,11 @@ class OrganizationResource extends Resource
|
|||||||
'max:2147483647',
|
'max:2147483647',
|
||||||
])
|
])
|
||||||
->numeric(),
|
->numeric(),
|
||||||
Forms\Components\DateTimePicker::make('created_at')
|
DateTimePicker::make('created_at')
|
||||||
->label('Created At')
|
->label('Created At')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->disabled(),
|
->disabled(),
|
||||||
Forms\Components\DateTimePicker::make('updated_at')
|
DateTimePicker::make('updated_at')
|
||||||
->label('Updated At')
|
->label('Updated At')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->disabled(),
|
->disabled(),
|
||||||
@@ -117,7 +127,7 @@ class OrganizationResource extends Resource
|
|||||||
TextColumn::make('name')
|
TextColumn::make('name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\IconColumn::make('personal_team')
|
IconColumn::make('personal_team')
|
||||||
->boolean()
|
->boolean()
|
||||||
->label('Is personal?')
|
->label('Is personal?')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
@@ -138,9 +148,9 @@ class OrganizationResource extends Resource
|
|||||||
->filters([
|
->filters([
|
||||||
//
|
//
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
Tables\Actions\EditAction::make(),
|
EditAction::make(),
|
||||||
Tables\Actions\DeleteAction::make()
|
DeleteAction::make()
|
||||||
->using(function (Organization $record): void {
|
->using(function (Organization $record): void {
|
||||||
app(DeletionService::class)->deleteOrganization($record);
|
app(DeletionService::class)->deleteOrganization($record);
|
||||||
}),
|
}),
|
||||||
@@ -158,7 +168,7 @@ class OrganizationResource extends Resource
|
|||||||
return response()->streamDownload(function () use ($file): void {
|
return response()->streamDownload(function () use ($file): void {
|
||||||
echo Storage::disk(config('filesystems.private'))->get($file);
|
echo Storage::disk(config('filesystems.private'))->get($file);
|
||||||
}, 'export.zip');
|
}, 'export.zip');
|
||||||
} catch (\Exception $exception) {
|
} catch (Exception $exception) {
|
||||||
report($exception);
|
report($exception);
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title('Export failed')
|
->title('Export failed')
|
||||||
@@ -172,9 +182,13 @@ class OrganizationResource extends Resource
|
|||||||
->icon('heroicon-o-inbox-arrow-down')
|
->icon('heroicon-o-inbox-arrow-down')
|
||||||
->action(function (Organization $record, array $data): void {
|
->action(function (Organization $record, array $data): void {
|
||||||
try {
|
try {
|
||||||
$file = Storage::disk(config('filament.default_filesystem_disk'))->get($data['file']);
|
try {
|
||||||
|
$file = Storage::disk(config('filament.default_filesystem_disk'))->get($data['file']);
|
||||||
|
} catch (UnableToReadFile) {
|
||||||
|
$file = null;
|
||||||
|
}
|
||||||
if ($file === null) {
|
if ($file === null) {
|
||||||
throw new \Exception('File not found');
|
throw new ImportException('File not found');
|
||||||
}
|
}
|
||||||
/** @var string $timezone */
|
/** @var string $timezone */
|
||||||
$timezone = $data['timezone'];
|
$timezone = $data['timezone'];
|
||||||
@@ -209,8 +223,8 @@ class OrganizationResource extends Resource
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
->tooltip(fn (Organization $record): string => 'Import into '.$record->name)
|
->tooltip(fn (Organization $record): string => 'Import into '.$record->name)
|
||||||
->form([
|
->schema([
|
||||||
Forms\Components\FileUpload::make('file')
|
FileUpload::make('file')
|
||||||
->label('File')
|
->label('File')
|
||||||
->required(),
|
->required(),
|
||||||
Select::make('type')
|
Select::make('type')
|
||||||
@@ -230,7 +244,7 @@ class OrganizationResource extends Resource
|
|||||||
->required(),
|
->required(),
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->toolbarActions([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,10 +259,10 @@ class OrganizationResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListOrganizations::route('/'),
|
'index' => ListOrganizations::route('/'),
|
||||||
'create' => Pages\CreateOrganization::route('/create'),
|
'create' => CreateOrganization::route('/create'),
|
||||||
'edit' => Pages\EditOrganization::route('/{record}/edit'),
|
'edit' => EditOrganization::route('/{record}/edit'),
|
||||||
'view' => Pages\ViewOrganization::route('/{record}'),
|
'view' => ViewOrganization::route('/{record}'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\OrganizationResource\Pages;
|
namespace App\Filament\Resources\OrganizationResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\OrganizationResource;
|
use App\Filament\Resources\OrganizationResource;
|
||||||
|
use App\Filament\Resources\OrganizationResource\Actions\DeleteOrganization;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
class EditOrganization extends EditRecord
|
class EditOrganization extends EditRecord
|
||||||
@@ -14,7 +15,7 @@ class EditOrganization extends EditRecord
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
OrganizationResource\Actions\DeleteOrganization::make(),
|
DeleteOrganization::make(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\OrganizationResource\Pages;
|
namespace App\Filament\Resources\OrganizationResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\OrganizationResource;
|
use App\Filament\Resources\OrganizationResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
class ListOrganizations extends ListRecords
|
class ListOrganizations extends ListRecords
|
||||||
@@ -15,7 +15,7 @@ class ListOrganizations extends ListRecords
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\CreateAction::make()
|
CreateAction::make()
|
||||||
->icon('heroicon-s-plus'),
|
->icon('heroicon-s-plus'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,17 @@ use App\Filament\Resources\OrganizationInvitationResource;
|
|||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
use App\Models\OrganizationInvitation;
|
use App\Models\OrganizationInvitation;
|
||||||
use App\Service\InvitationService;
|
use App\Service\InvitationService;
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Actions\BulkActionGroup;
|
||||||
|
use Filament\Actions\CreateAction;
|
||||||
|
use Filament\Actions\DeleteAction;
|
||||||
|
use Filament\Actions\DetachBulkAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Tables;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Tables\Actions\Action;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
@@ -25,10 +30,10 @@ class InvitationsRelationManager extends RelationManager
|
|||||||
|
|
||||||
protected static ?string $title = 'Invitations';
|
protected static ?string $title = 'Invitations';
|
||||||
|
|
||||||
public function form(Form $form): Form
|
public function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
TextInput::make('email')
|
TextInput::make('email')
|
||||||
->label('Email')
|
->label('Email')
|
||||||
->disabledOn(['edit'])
|
->disabledOn(['edit'])
|
||||||
@@ -53,33 +58,33 @@ class InvitationsRelationManager extends RelationManager
|
|||||||
->modelLabel('Invitation')
|
->modelLabel('Invitation')
|
||||||
->pluralModelLabel('Invitations')
|
->pluralModelLabel('Invitations')
|
||||||
->columns([
|
->columns([
|
||||||
Tables\Columns\TextColumn::make('email'),
|
TextColumn::make('email'),
|
||||||
Tables\Columns\TextColumn::make('role'),
|
TextColumn::make('role'),
|
||||||
])
|
])
|
||||||
->headerActions([
|
->headerActions([
|
||||||
Tables\Actions\CreateAction::make()
|
CreateAction::make()
|
||||||
->icon('heroicon-s-plus')
|
->icon('heroicon-s-plus')
|
||||||
->using(function (array $data, string $model): Model {
|
->using(function (array $data, string $model): Model {
|
||||||
/** @var Organization $ownerRecord */
|
/** @var Organization $ownerRecord */
|
||||||
$ownerRecord = $this->getOwnerRecord();
|
$ownerRecord = $this->getOwnerRecord();
|
||||||
|
|
||||||
return app(InvitationService::class)
|
return app(InvitationService::class)
|
||||||
->inviteUser($ownerRecord, $data['email'], Role::from($data['role']), auth()->user());
|
->inviteUser($ownerRecord, $data['email'], ($data['role'] instanceof Role ? $data['role'] : Role::from($data['role'])), auth()->user());
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
Action::make('view')
|
Action::make('view')
|
||||||
->icon('heroicon-o-eye')
|
->icon('heroicon-o-eye')
|
||||||
->color('gray')
|
->color('gray')
|
||||||
->url(fn (OrganizationInvitation $record): string => OrganizationInvitationResource::getUrl('view', [
|
->url(fn (OrganizationInvitation $record): string => OrganizationInvitationResource::getUrl('view', [
|
||||||
'record' => $record->getKey(),
|
'record' => $record->getKey(),
|
||||||
])),
|
])),
|
||||||
Tables\Actions\EditAction::make(),
|
EditAction::make(),
|
||||||
Tables\Actions\DeleteAction::make(),
|
DeleteAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->toolbarActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
BulkActionGroup::make([
|
||||||
Tables\Actions\DetachBulkAction::make(),
|
DetachBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,14 +12,15 @@ use App\Models\Organization;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Service\BillableRateService;
|
use App\Service\BillableRateService;
|
||||||
use App\Service\MemberService;
|
use App\Service\MemberService;
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Actions\AttachAction;
|
||||||
|
use Filament\Actions\DetachAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Tables;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Tables\Actions\Action;
|
|
||||||
use Filament\Tables\Actions\AttachAction;
|
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
@@ -28,10 +29,10 @@ class UsersRelationManager extends RelationManager
|
|||||||
{
|
{
|
||||||
protected static string $relationship = 'users';
|
protected static string $relationship = 'users';
|
||||||
|
|
||||||
public function form(Form $form): Form
|
public function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
Select::make('role')
|
Select::make('role')
|
||||||
->options(Role::class),
|
->options(Role::class),
|
||||||
TextInput::make('billable_rate')
|
TextInput::make('billable_rate')
|
||||||
@@ -75,17 +76,17 @@ class UsersRelationManager extends RelationManager
|
|||||||
->using(function (User $record, array $data): void {
|
->using(function (User $record, array $data): void {
|
||||||
/** @var Organization $organization */
|
/** @var Organization $organization */
|
||||||
$organization = $this->getOwnerRecord();
|
$organization = $this->getOwnerRecord();
|
||||||
app(MemberService::class)->addMember($record, $organization, Role::from($data['role']), true);
|
app(MemberService::class)->addMember($record, $organization, ($data['role'] instanceof Role ? $data['role'] : Role::from($data['role'])), true);
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
Action::make('view')
|
Action::make('view')
|
||||||
->icon('heroicon-o-eye')
|
->icon('heroicon-o-eye')
|
||||||
->color('gray')
|
->color('gray')
|
||||||
->url(fn (User $record): string => UserResource::getUrl('view', [
|
->url(fn (User $record): string => UserResource::getUrl('view', [
|
||||||
'record' => $record->getKey(),
|
'record' => $record->getKey(),
|
||||||
])),
|
])),
|
||||||
Tables\Actions\EditAction::make()
|
EditAction::make()
|
||||||
->using(function (User $record, array $data): User {
|
->using(function (User $record, array $data): User {
|
||||||
/** @var Organization $organization */
|
/** @var Organization $organization */
|
||||||
$organization = $this->getOwnerRecord();
|
$organization = $this->getOwnerRecord();
|
||||||
@@ -97,9 +98,10 @@ class UsersRelationManager extends RelationManager
|
|||||||
app(BillableRateService::class)->updateTimeEntriesBillableRateForMember($member);
|
app(BillableRateService::class)->updateTimeEntriesBillableRateForMember($member);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($data['role'] !== $member->role) {
|
$newRole = $data['role'] instanceof Role ? $data['role'] : Role::from($data['role']);
|
||||||
|
if ($newRole->value !== $member->role) {
|
||||||
try {
|
try {
|
||||||
app(MemberService::class)->changeRole($member, $organization, Role::from($data['role']), true);
|
app(MemberService::class)->changeRole($member, $organization, $newRole, true);
|
||||||
} catch (ApiException $exception) {
|
} catch (ApiException $exception) {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->danger()
|
->danger()
|
||||||
@@ -113,7 +115,7 @@ class UsersRelationManager extends RelationManager
|
|||||||
|
|
||||||
return $record;
|
return $record;
|
||||||
}),
|
}),
|
||||||
Tables\Actions\DetachAction::make()
|
DetachAction::make()
|
||||||
->using(function (User $record): void {
|
->using(function (User $record): void {
|
||||||
/** @var Organization $organization */
|
/** @var Organization $organization */
|
||||||
$organization = $this->getOwnerRecord();
|
$organization = $this->getOwnerRecord();
|
||||||
@@ -133,7 +135,7 @@ class UsersRelationManager extends RelationManager
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->toolbarActions([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,19 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\ProjectMemberResource\Pages;
|
use App\Filament\Resources\ProjectMemberResource\Pages\CreateProjectMember;
|
||||||
|
use App\Filament\Resources\ProjectMemberResource\Pages\EditProjectMember;
|
||||||
|
use App\Filament\Resources\ProjectMemberResource\Pages\ListProjectMembers;
|
||||||
|
use App\Filament\Resources\ProjectMemberResource\Pages\ViewProjectMembers;
|
||||||
use App\Models\ProjectMember;
|
use App\Models\ProjectMember;
|
||||||
use Filament\Forms;
|
use Filament\Actions\BulkActionGroup;
|
||||||
use Filament\Forms\Form;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Schemas\Schema;
|
||||||
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
class ProjectMemberResource extends Resource
|
class ProjectMemberResource extends Resource
|
||||||
@@ -18,11 +25,11 @@ class ProjectMemberResource extends Resource
|
|||||||
|
|
||||||
protected static bool $shouldRegisterNavigation = false;
|
protected static bool $shouldRegisterNavigation = false;
|
||||||
|
|
||||||
public static function form(Form $form): Form
|
public static function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
Forms\Components\TextInput::make('billable_rate')
|
TextInput::make('billable_rate')
|
||||||
->label('Billable rate (in Cents)')
|
->label('Billable rate (in Cents)')
|
||||||
->nullable()
|
->nullable()
|
||||||
->rules([
|
->rules([
|
||||||
@@ -32,10 +39,10 @@ class ProjectMemberResource extends Resource
|
|||||||
'max:2147483647',
|
'max:2147483647',
|
||||||
])
|
])
|
||||||
->numeric(),
|
->numeric(),
|
||||||
Forms\Components\Select::make('user_id')
|
Select::make('user_id')
|
||||||
->relationship('user', 'name')
|
->relationship('user', 'name')
|
||||||
->required(),
|
->required(),
|
||||||
Forms\Components\Select::make('member_id')
|
Select::make('member_id')
|
||||||
->relationship('member', 'id')
|
->relationship('member', 'id')
|
||||||
->required(),
|
->required(),
|
||||||
]);
|
]);
|
||||||
@@ -45,17 +52,17 @@ class ProjectMemberResource extends Resource
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
Tables\Columns\TextColumn::make('id')
|
TextColumn::make('id')
|
||||||
->label('ID'),
|
->label('ID'),
|
||||||
Tables\Columns\TextColumn::make('billable_rate')
|
TextColumn::make('billable_rate')
|
||||||
->numeric()
|
->numeric()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('project.name'),
|
TextColumn::make('project.name'),
|
||||||
Tables\Columns\TextColumn::make('user.name'),
|
TextColumn::make('user.name'),
|
||||||
Tables\Columns\TextColumn::make('created_at')
|
TextColumn::make('created_at')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('updated_at')
|
TextColumn::make('updated_at')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable()
|
->sortable()
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
@@ -63,12 +70,12 @@ class ProjectMemberResource extends Resource
|
|||||||
->filters([
|
->filters([
|
||||||
//
|
//
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
Tables\Actions\EditAction::make(),
|
EditAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->toolbarActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
BulkActionGroup::make([
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
DeleteBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -83,10 +90,10 @@ class ProjectMemberResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListProjectMembers::route('/'),
|
'index' => ListProjectMembers::route('/'),
|
||||||
'create' => Pages\CreateProjectMember::route('/create'),
|
'create' => CreateProjectMember::route('/create'),
|
||||||
'edit' => Pages\EditProjectMember::route('/{record}/edit'),
|
'edit' => EditProjectMember::route('/{record}/edit'),
|
||||||
'view' => Pages\ViewProjectMembers::route('/{record}'),
|
'view' => ViewProjectMembers::route('/{record}'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\ProjectMemberResource\Pages;
|
namespace App\Filament\Resources\ProjectMemberResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\ProjectMemberResource;
|
use App\Filament\Resources\ProjectMemberResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
class EditProjectMember extends EditRecord
|
class EditProjectMember extends EditRecord
|
||||||
@@ -15,7 +15,7 @@ class EditProjectMember extends EditRecord
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\DeleteAction::make()
|
DeleteAction::make()
|
||||||
->icon('heroicon-m-trash'),
|
->icon('heroicon-m-trash'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\ProjectMemberResource\Pages;
|
namespace App\Filament\Resources\ProjectMemberResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\ProjectMemberResource;
|
use App\Filament\Resources\ProjectMemberResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
class ListProjectMembers extends ListRecords
|
class ListProjectMembers extends ListRecords
|
||||||
@@ -15,7 +15,7 @@ class ListProjectMembers extends ListRecords
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\CreateAction::make()
|
CreateAction::make()
|
||||||
->icon('heroicon-s-plus'),
|
->icon('heroicon-s-plus'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,19 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\ProjectResource\Pages;
|
use App\Filament\Resources\ProjectResource\Pages\CreateProject;
|
||||||
|
use App\Filament\Resources\ProjectResource\Pages\EditProject;
|
||||||
|
use App\Filament\Resources\ProjectResource\Pages\ListProjects;
|
||||||
use App\Filament\Resources\ProjectResource\RelationManagers\ProjectMembersRelationManager;
|
use App\Filament\Resources\ProjectResource\RelationManagers\ProjectMembersRelationManager;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use Filament\Forms;
|
use Filament\Actions\BulkActionGroup;
|
||||||
|
use Filament\Actions\DeleteBulkAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Forms\Components\ColorPicker;
|
use Filament\Forms\Components\ColorPicker;
|
||||||
use Filament\Forms\Form;
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Tables\Columns\ColorColumn;
|
use Filament\Tables\Columns\ColorColumn;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
@@ -21,24 +26,24 @@ class ProjectResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = Project::class;
|
protected static ?string $model = Project::class;
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'heroicon-o-folder';
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-folder';
|
||||||
|
|
||||||
protected static ?string $navigationGroup = 'Timetracking';
|
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 2;
|
protected static ?int $navigationSort = 2;
|
||||||
|
|
||||||
public static function form(Form $form): Form
|
public static function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
Forms\Components\TextInput::make('name')
|
TextInput::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->required()
|
->required()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
ColorPicker::make('color')
|
ColorPicker::make('color')
|
||||||
->label('Color')
|
->label('Color')
|
||||||
->required(),
|
->required(),
|
||||||
Forms\Components\TextInput::make('billable_rate')
|
TextInput::make('billable_rate')
|
||||||
->label('Billable rate (in Cents)')
|
->label('Billable rate (in Cents)')
|
||||||
->nullable()
|
->nullable()
|
||||||
->rules([
|
->rules([
|
||||||
@@ -48,7 +53,7 @@ class ProjectResource extends Resource
|
|||||||
'max:2147483647',
|
'max:2147483647',
|
||||||
])
|
])
|
||||||
->numeric(),
|
->numeric(),
|
||||||
Forms\Components\Select::make('organization_id')
|
Select::make('organization_id')
|
||||||
->relationship(name: 'organization', titleAttribute: 'name')
|
->relationship(name: 'organization', titleAttribute: 'name')
|
||||||
->searchable(['name'])
|
->searchable(['name'])
|
||||||
->required(),
|
->required(),
|
||||||
@@ -81,12 +86,12 @@ class ProjectResource extends Resource
|
|||||||
->searchable(),
|
->searchable(),
|
||||||
])
|
])
|
||||||
->defaultSort('created_at', 'desc')
|
->defaultSort('created_at', 'desc')
|
||||||
->actions([
|
->recordActions([
|
||||||
Tables\Actions\EditAction::make(),
|
EditAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->toolbarActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
BulkActionGroup::make([
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
DeleteBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -101,9 +106,9 @@ class ProjectResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListProjects::route('/'),
|
'index' => ListProjects::route('/'),
|
||||||
'create' => Pages\CreateProject::route('/create'),
|
'create' => CreateProject::route('/create'),
|
||||||
'edit' => Pages\EditProject::route('/{record}/edit'),
|
'edit' => EditProject::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\ProjectResource\Pages;
|
namespace App\Filament\Resources\ProjectResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\ProjectResource;
|
use App\Filament\Resources\ProjectResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
class EditProject extends EditRecord
|
class EditProject extends EditRecord
|
||||||
@@ -15,7 +15,7 @@ class EditProject extends EditRecord
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\DeleteAction::make()
|
DeleteAction::make()
|
||||||
->icon('heroicon-m-trash'),
|
->icon('heroicon-m-trash'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\ProjectResource\Pages;
|
namespace App\Filament\Resources\ProjectResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\ProjectResource;
|
use App\Filament\Resources\ProjectResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
class ListProjects extends ListRecords
|
class ListProjects extends ListRecords
|
||||||
@@ -15,7 +15,7 @@ class ListProjects extends ListRecords
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\CreateAction::make()
|
CreateAction::make()
|
||||||
->icon('heroicon-s-plus'),
|
->icon('heroicon-s-plus'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ namespace App\Filament\Resources\ProjectResource\RelationManagers;
|
|||||||
|
|
||||||
use App\Filament\Resources\ProjectMemberResource;
|
use App\Filament\Resources\ProjectMemberResource;
|
||||||
use App\Models\ProjectMember;
|
use App\Models\ProjectMember;
|
||||||
use Filament\Forms\Form;
|
use Filament\Actions\Action;
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Tables;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Tables\Actions\Action;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
class ProjectMembersRelationManager extends RelationManager
|
class ProjectMembersRelationManager extends RelationManager
|
||||||
@@ -18,10 +18,10 @@ class ProjectMembersRelationManager extends RelationManager
|
|||||||
|
|
||||||
protected static string $relationship = 'members';
|
protected static string $relationship = 'members';
|
||||||
|
|
||||||
public function form(Form $form): Form
|
public function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,8 +30,8 @@ class ProjectMembersRelationManager extends RelationManager
|
|||||||
return $table
|
return $table
|
||||||
->recordTitleAttribute('name')
|
->recordTitleAttribute('name')
|
||||||
->columns([
|
->columns([
|
||||||
Tables\Columns\TextColumn::make('user.name'),
|
TextColumn::make('user.name'),
|
||||||
Tables\Columns\TextColumn::make('billable_rate')
|
TextColumn::make('billable_rate')
|
||||||
->numeric()
|
->numeric()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
])
|
])
|
||||||
@@ -40,7 +40,7 @@ class ProjectMembersRelationManager extends RelationManager
|
|||||||
])
|
])
|
||||||
->headerActions([
|
->headerActions([
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
Action::make('view')
|
Action::make('view')
|
||||||
->icon('heroicon-o-eye')
|
->icon('heroicon-o-eye')
|
||||||
->color('gray')
|
->color('gray')
|
||||||
@@ -54,7 +54,7 @@ class ProjectMembersRelationManager extends RelationManager
|
|||||||
]))
|
]))
|
||||||
->openUrlInNewTab(),
|
->openUrlInNewTab(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->toolbarActions([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,21 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\ReportResource\Pages;
|
use App\Filament\Resources\ReportResource\Pages\EditReport;
|
||||||
|
use App\Filament\Resources\ReportResource\Pages\ListReports;
|
||||||
|
use App\Filament\Resources\ReportResource\Pages\ViewReport;
|
||||||
use App\Models\Report;
|
use App\Models\Report;
|
||||||
use App\Service\Dto\ReportPropertiesDto;
|
use App\Service\Dto\ReportPropertiesDto;
|
||||||
use Filament\Forms;
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Actions\DeleteAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Actions\ViewAction;
|
||||||
use Filament\Forms\Components\DateTimePicker;
|
use Filament\Forms\Components\DateTimePicker;
|
||||||
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Components\Toggle;
|
use Filament\Forms\Components\Toggle;
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Tables\Actions\Action;
|
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Columns\ToggleColumn;
|
use Filament\Tables\Columns\ToggleColumn;
|
||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
@@ -24,22 +29,22 @@ class ReportResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = Report::class;
|
protected static ?string $model = Report::class;
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'heroicon-o-document-chart-bar';
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-document-chart-bar';
|
||||||
|
|
||||||
protected static ?string $navigationGroup = 'Timetracking';
|
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 7;
|
protected static ?int $navigationSort = 7;
|
||||||
|
|
||||||
public static function form(Form $form): Form
|
public static function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->columns(1)
|
->columns(1)
|
||||||
->schema([
|
->components([
|
||||||
Forms\Components\TextInput::make('name')
|
TextInput::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->required()
|
->required()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
Forms\Components\TextInput::make('description')
|
TextInput::make('description')
|
||||||
->label('Description')
|
->label('Description')
|
||||||
->nullable()
|
->nullable()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
@@ -49,13 +54,13 @@ class ReportResource extends Resource
|
|||||||
DateTimePicker::make('public_until')
|
DateTimePicker::make('public_until')
|
||||||
->label('Public until')
|
->label('Public until')
|
||||||
->nullable(),
|
->nullable(),
|
||||||
Forms\Components\Select::make('organization_id')
|
Select::make('organization_id')
|
||||||
->label('Organization')
|
->label('Organization')
|
||||||
->relationship(name: 'organization', titleAttribute: 'name')
|
->relationship(name: 'organization', titleAttribute: 'name')
|
||||||
->searchable(['name'])
|
->searchable(['name'])
|
||||||
->disabled()
|
->disabled()
|
||||||
->required(),
|
->required(),
|
||||||
Forms\Components\TextInput::make('share_secret')
|
TextInput::make('share_secret')
|
||||||
->label('Share Secret')
|
->label('Share Secret')
|
||||||
->nullable(),
|
->nullable(),
|
||||||
PrettyJsonField::make('properties')
|
PrettyJsonField::make('properties')
|
||||||
@@ -109,18 +114,18 @@ class ReportResource extends Resource
|
|||||||
->relationship('organization', 'id')
|
->relationship('organization', 'id')
|
||||||
->searchable(),
|
->searchable(),
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
Action::make('public-view')
|
Action::make('public-view')
|
||||||
->label('Public')
|
->label('Public')
|
||||||
->icon('heroicon-o-eye')
|
->icon('heroicon-o-eye')
|
||||||
->color('gray')
|
->color('gray')
|
||||||
->hidden(fn (Report $record): bool => $record->getShareableLink() === null)
|
->hidden(fn (Report $record): bool => $record->getShareableLink() === null)
|
||||||
->url(fn (Report $record): string => $record->getShareableLink(), true),
|
->url(fn (Report $record): string => $record->getShareableLink(), true),
|
||||||
Tables\Actions\ViewAction::make(),
|
ViewAction::make(),
|
||||||
Tables\Actions\EditAction::make(),
|
EditAction::make(),
|
||||||
Tables\Actions\DeleteAction::make(),
|
DeleteAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->toolbarActions([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,9 +138,9 @@ class ReportResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListReports::route('/'),
|
'index' => ListReports::route('/'),
|
||||||
'edit' => Pages\EditReport::route('/{record}/edit'),
|
'edit' => EditReport::route('/{record}/edit'),
|
||||||
'view' => Pages\ViewReport::route('/{record}'),
|
'view' => ViewReport::route('/{record}'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\ReportResource\Pages;
|
namespace App\Filament\Resources\ReportResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\ReportResource;
|
use App\Filament\Resources\ReportResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
class EditReport extends EditRecord
|
class EditReport extends EditRecord
|
||||||
@@ -15,7 +15,7 @@ class EditReport extends EditRecord
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\DeleteAction::make()
|
DeleteAction::make()
|
||||||
->icon('heroicon-m-trash'),
|
->icon('heroicon-m-trash'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,18 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\TagResource\Pages;
|
use App\Filament\Resources\TagResource\Pages\CreateTag;
|
||||||
|
use App\Filament\Resources\TagResource\Pages\EditTag;
|
||||||
|
use App\Filament\Resources\TagResource\Pages\ListTags;
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use Filament\Forms;
|
use Filament\Actions\BulkActionGroup;
|
||||||
|
use Filament\Actions\DeleteBulkAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Schemas\Schema;
|
||||||
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
@@ -18,20 +23,20 @@ class TagResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = Tag::class;
|
protected static ?string $model = Tag::class;
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'heroicon-o-tag';
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-tag';
|
||||||
|
|
||||||
protected static ?string $navigationGroup = 'Timetracking';
|
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 5;
|
protected static ?int $navigationSort = 5;
|
||||||
|
|
||||||
public static function form(Form $form): Form
|
public static function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
TextInput::make('name')
|
TextInput::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->required(),
|
->required(),
|
||||||
Forms\Components\Select::make('organization_id')
|
Select::make('organization_id')
|
||||||
->relationship(name: 'organization', titleAttribute: 'name')
|
->relationship(name: 'organization', titleAttribute: 'name')
|
||||||
->label('Organization')
|
->label('Organization')
|
||||||
->searchable(['name'])
|
->searchable(['name'])
|
||||||
@@ -43,17 +48,17 @@ class TagResource extends Resource
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
Tables\Columns\TextColumn::make('name')
|
TextColumn::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('organization.name')
|
TextColumn::make('organization.name')
|
||||||
->sortable()
|
->sortable()
|
||||||
->label('Organization'),
|
->label('Organization'),
|
||||||
Tables\Columns\TextColumn::make('created_at')
|
TextColumn::make('created_at')
|
||||||
->label('Created at')
|
->label('Created at')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('updated_at')
|
TextColumn::make('updated_at')
|
||||||
->label('Updated at')
|
->label('Updated at')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
])
|
])
|
||||||
@@ -68,12 +73,12 @@ class TagResource extends Resource
|
|||||||
->relationship('organization', 'id')
|
->relationship('organization', 'id')
|
||||||
->searchable(),
|
->searchable(),
|
||||||
])
|
])
|
||||||
->actions([
|
->recordActions([
|
||||||
Tables\Actions\EditAction::make(),
|
EditAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->toolbarActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
BulkActionGroup::make([
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
DeleteBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -88,9 +93,9 @@ class TagResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListTags::route('/'),
|
'index' => ListTags::route('/'),
|
||||||
'create' => Pages\CreateTag::route('/create'),
|
'create' => CreateTag::route('/create'),
|
||||||
'edit' => Pages\EditTag::route('/{record}/edit'),
|
'edit' => EditTag::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\TagResource\Pages;
|
namespace App\Filament\Resources\TagResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\TagResource;
|
use App\Filament\Resources\TagResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
class EditTag extends EditRecord
|
class EditTag extends EditRecord
|
||||||
@@ -15,7 +15,7 @@ class EditTag extends EditRecord
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\DeleteAction::make()
|
DeleteAction::make()
|
||||||
->icon('heroicon-m-trash'),
|
->icon('heroicon-m-trash'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\TagResource\Pages;
|
namespace App\Filament\Resources\TagResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\TagResource;
|
use App\Filament\Resources\TagResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
class ListTags extends ListRecords
|
class ListTags extends ListRecords
|
||||||
@@ -15,7 +15,7 @@ class ListTags extends ListRecords
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\CreateAction::make()
|
CreateAction::make()
|
||||||
->icon('heroicon-s-plus'),
|
->icon('heroicon-s-plus'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,18 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\TaskResource\Pages;
|
use App\Filament\Resources\TaskResource\Pages\CreateTask;
|
||||||
|
use App\Filament\Resources\TaskResource\Pages\EditTask;
|
||||||
|
use App\Filament\Resources\TaskResource\Pages\ListTasks;
|
||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
use Filament\Forms;
|
use Filament\Actions\BulkActionGroup;
|
||||||
|
use Filament\Actions\DeleteBulkAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Form;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Schemas\Schema;
|
||||||
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
@@ -18,17 +23,17 @@ class TaskResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = Task::class;
|
protected static ?string $model = Task::class;
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'heroicon-o-list-bullet';
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-list-bullet';
|
||||||
|
|
||||||
protected static ?string $navigationGroup = 'Timetracking';
|
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 3;
|
protected static ?int $navigationSort = 3;
|
||||||
|
|
||||||
public static function form(Form $form): Form
|
public static function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
Forms\Components\TextInput::make('name')
|
TextInput::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->required()
|
->required()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
@@ -47,16 +52,16 @@ class TaskResource extends Resource
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
Tables\Columns\TextColumn::make('name')
|
TextColumn::make('name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('project.name')
|
TextColumn::make('project.name')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('organization.name')
|
TextColumn::make('organization.name')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('created_at')
|
TextColumn::make('created_at')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('updated_at')
|
TextColumn::make('updated_at')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
])
|
])
|
||||||
->filters([
|
->filters([
|
||||||
@@ -70,12 +75,12 @@ class TaskResource extends Resource
|
|||||||
->searchable(),
|
->searchable(),
|
||||||
])
|
])
|
||||||
->defaultSort('created_at', 'desc')
|
->defaultSort('created_at', 'desc')
|
||||||
->actions([
|
->recordActions([
|
||||||
Tables\Actions\EditAction::make(),
|
EditAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->toolbarActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
BulkActionGroup::make([
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
DeleteBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -90,9 +95,9 @@ class TaskResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListTasks::route('/'),
|
'index' => ListTasks::route('/'),
|
||||||
'create' => Pages\CreateTask::route('/create'),
|
'create' => CreateTask::route('/create'),
|
||||||
'edit' => Pages\EditTask::route('/{record}/edit'),
|
'edit' => EditTask::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\TaskResource\Pages;
|
namespace App\Filament\Resources\TaskResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\TaskResource;
|
use App\Filament\Resources\TaskResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
class EditTask extends EditRecord
|
class EditTask extends EditRecord
|
||||||
@@ -15,7 +15,7 @@ class EditTask extends EditRecord
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\DeleteAction::make()
|
DeleteAction::make()
|
||||||
->icon('heroicon-m-trash'),
|
->icon('heroicon-m-trash'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\TaskResource\Pages;
|
namespace App\Filament\Resources\TaskResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\TaskResource;
|
use App\Filament\Resources\TaskResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
class ListTasks extends ListRecords
|
class ListTasks extends ListRecords
|
||||||
@@ -15,7 +15,7 @@ class ListTasks extends ListRecords
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\CreateAction::make()
|
CreateAction::make()
|
||||||
->icon('heroicon-s-plus'),
|
->icon('heroicon-s-plus'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,20 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\TimeEntryResource\Pages;
|
use App\Filament\Resources\TimeEntryResource\Pages\CreateTimeEntry;
|
||||||
|
use App\Filament\Resources\TimeEntryResource\Pages\EditTimeEntry;
|
||||||
|
use App\Filament\Resources\TimeEntryResource\Pages\ListTimeEntries;
|
||||||
use App\Models\Member;
|
use App\Models\Member;
|
||||||
use App\Models\TimeEntry;
|
use App\Models\TimeEntry;
|
||||||
|
use Filament\Actions\BulkActionGroup;
|
||||||
|
use Filament\Actions\DeleteBulkAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Forms\Components\DateTimePicker;
|
use Filament\Forms\Components\DateTimePicker;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Components\Toggle;
|
use Filament\Forms\Components\Toggle;
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
@@ -23,16 +27,16 @@ class TimeEntryResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = TimeEntry::class;
|
protected static ?string $model = TimeEntry::class;
|
||||||
|
|
||||||
protected static ?string $navigationIcon = 'heroicon-o-clock';
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-clock';
|
||||||
|
|
||||||
protected static ?string $navigationGroup = 'Timetracking';
|
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 1;
|
protected static ?int $navigationSort = 1;
|
||||||
|
|
||||||
public static function form(Form $form): Form
|
public static function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $form
|
return $schema
|
||||||
->schema([
|
->components([
|
||||||
TextInput::make('id')
|
TextInput::make('id')
|
||||||
->label('ID')
|
->label('ID')
|
||||||
->readOnly()
|
->readOnly()
|
||||||
@@ -111,12 +115,12 @@ class TimeEntryResource extends Resource
|
|||||||
->searchable(),
|
->searchable(),
|
||||||
])
|
])
|
||||||
->defaultSort('created_at', 'desc')
|
->defaultSort('created_at', 'desc')
|
||||||
->actions([
|
->recordActions([
|
||||||
Tables\Actions\EditAction::make(),
|
EditAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->toolbarActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
BulkActionGroup::make([
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
DeleteBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -131,9 +135,9 @@ class TimeEntryResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListTimeEntries::route('/'),
|
'index' => ListTimeEntries::route('/'),
|
||||||
'create' => Pages\CreateTimeEntry::route('/create'),
|
'create' => CreateTimeEntry::route('/create'),
|
||||||
'edit' => Pages\EditTimeEntry::route('/{record}/edit'),
|
'edit' => EditTimeEntry::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace App\Filament\Resources\TimeEntryResource\Pages;
|
|||||||
|
|
||||||
use App\Filament\Resources\TimeEntryResource;
|
use App\Filament\Resources\TimeEntryResource;
|
||||||
use App\Models\Member;
|
use App\Models\Member;
|
||||||
use Filament\Actions;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
class EditTimeEntry extends EditRecord
|
class EditTimeEntry extends EditRecord
|
||||||
@@ -16,7 +16,7 @@ class EditTimeEntry extends EditRecord
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\DeleteAction::make()
|
DeleteAction::make()
|
||||||
->icon('heroicon-m-trash'),
|
->icon('heroicon-m-trash'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user