mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-08 05:43:18 +01:00
Compare commits
1 Commits
main
...
feature/do
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd05c37d86 |
4
.env.ci
4
.env.ci
@@ -6,7 +6,6 @@ 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
|
||||||
@@ -65,6 +64,3 @@ GOTENBERG_URL=http://localhost:3000
|
|||||||
|
|
||||||
# Octane
|
# Octane
|
||||||
OCTANE_SERVER=frankenphp
|
OCTANE_SERVER=frankenphp
|
||||||
|
|
||||||
# Inertia
|
|
||||||
INERTIA_SSR_ENABLED=false
|
|
||||||
|
|||||||
@@ -88,6 +88,3 @@ 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,5 +13,3 @@ LOG_DEPRECATIONS_CHANNEL=null
|
|||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
DB_CONNECTION=pgsql
|
DB_CONNECTION=pgsql
|
||||||
|
|
||||||
INERTIA_SSR_ENABLED=false
|
|
||||||
|
|||||||
23
.github/workflows/build-onpremise.yml
vendored
23
.github/workflows/build-onpremise.yml
vendored
@@ -103,8 +103,6 @@ jobs:
|
|||||||
{
|
{
|
||||||
echo "invoicing_repository=$(jq -r '.Invoicing.repository' extensions/manifest.json)"
|
echo "invoicing_repository=$(jq -r '.Invoicing.repository' extensions/manifest.json)"
|
||||||
echo "invoicing_ref=$(jq -r '.Invoicing.ref' 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"
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: "Checkout invoicing extension"
|
- name: "Checkout invoicing extension"
|
||||||
@@ -121,26 +119,9 @@ 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
|
||||||
|
|
||||||
@@ -160,7 +141,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.6.0
|
uses: docker/login-action@v4.5.2
|
||||||
with:
|
with:
|
||||||
registry: registry.on-premise.solidtime.io
|
registry: registry.on-premise.solidtime.io
|
||||||
username: ${{ secrets.ONPREMISE_USERNAME }}
|
username: ${{ secrets.ONPREMISE_USERNAME }}
|
||||||
@@ -214,7 +195,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.6.0
|
uses: docker/login-action@v4.5.2
|
||||||
with:
|
with:
|
||||||
registry: registry.on-premise.solidtime.io
|
registry: registry.on-premise.solidtime.io
|
||||||
username: ${{ secrets.ONPREMISE_USERNAME }}
|
username: ${{ secrets.ONPREMISE_USERNAME }}
|
||||||
|
|||||||
28
.github/workflows/build-private.yml
vendored
28
.github/workflows/build-private.yml
vendored
@@ -84,8 +84,6 @@ jobs:
|
|||||||
echo "services_ref=$(jq -r '.Services.ref' 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_repository=$(jq -r '.Invoicing.repository' extensions/manifest.json)"
|
||||||
echo "invoicing_ref=$(jq -r '.Invoicing.ref' 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"
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: "Checkout billing extension"
|
- name: "Checkout billing extension"
|
||||||
@@ -99,7 +97,7 @@ jobs:
|
|||||||
- 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": "${{ secrets.LARAVEL_SPARK_USERNAME }}", "password": "${{ secrets.LARAVEL_SPARK_API_KEY }}"}}}'
|
COMPOSER_AUTH: '{"http-basic": {"spark.laravel.com": {"username": "gregor@vostrak.at", "password": "${{ secrets.LARAVEL_SPARK_API_KEY }}"}}}'
|
||||||
with:
|
with:
|
||||||
working_dir: "extensions/Billing"
|
working_dir: "extensions/Billing"
|
||||||
command: install
|
command: install
|
||||||
@@ -147,25 +145,6 @@ 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:
|
||||||
@@ -189,9 +168,6 @@ 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
|
||||||
|
|
||||||
@@ -201,7 +177,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.6.0
|
uses: docker/login-action@v4.5.2
|
||||||
with:
|
with:
|
||||||
registry: rg.fr-par.scw.cloud/solidtime
|
registry: rg.fr-par.scw.cloud/solidtime
|
||||||
username: nologin
|
username: nologin
|
||||||
|
|||||||
8
.github/workflows/build-public.yml
vendored
8
.github/workflows/build-public.yml
vendored
@@ -117,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.6.0
|
uses: docker/login-action@v4.5.2
|
||||||
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.6.0
|
uses: docker/login-action@v4.5.2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -177,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.6.0
|
uses: docker/login-action@v4.5.2
|
||||||
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.6.0
|
uses: docker/login-action@v4.5.2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
|||||||
31
.github/workflows/extension-manifest-check.yml
vendored
31
.github/workflows/extension-manifest-check.yml
vendored
@@ -1,31 +0,0 @@
|
|||||||
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/npm-format-check.yml
vendored
2
.github/workflows/npm-format-check.yml
vendored
@@ -1,8 +1,6 @@
|
|||||||
name: NPM Format Check
|
name: NPM Format Check
|
||||||
|
|
||||||
on: [push]
|
on: [push]
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
format-check:
|
format-check:
|
||||||
|
|||||||
136
.github/workflows/phpunit-extensions.yml
vendored
136
.github/workflows/phpunit-extensions.yml
vendored
@@ -1,136 +0,0 @@
|
|||||||
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
|
|
||||||
2
.github/workflows/phpunit.yml
vendored
2
.github/workflows/phpunit.yml
vendored
@@ -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.1.1
|
uses: codecov/codecov-action@v7.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
slug: solidtime-io/solidtime
|
slug: solidtime-io/solidtime
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,7 +6,6 @@ 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
|
||||||
@@ -44,5 +43,4 @@ yarn-error.log
|
|||||||
/data
|
/data
|
||||||
/config/caddy
|
/config/caddy
|
||||||
/config/composer
|
/config/composer
|
||||||
/config/psysh
|
|
||||||
/AGENTS.md
|
/AGENTS.md
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ namespace App\Auth;
|
|||||||
use Illuminate\Auth\EloquentUserProvider;
|
use Illuminate\Auth\EloquentUserProvider;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Override;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User provider that only resolves non-placeholder users.
|
* User provider that only resolves non-placeholder users.
|
||||||
@@ -27,7 +26,7 @@ class ActiveUserProvider extends EloquentUserProvider
|
|||||||
* @param Model|null $model
|
* @param Model|null $model
|
||||||
* @return Builder<Model>
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
#[Override]
|
#[\Override]
|
||||||
protected function newModelQuery($model = null): Builder
|
protected function newModelQuery($model = null): Builder
|
||||||
{
|
{
|
||||||
$query = parent::newModelQuery($model);
|
$query = parent::newModelQuery($model);
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ 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
|
||||||
{
|
{
|
||||||
@@ -102,7 +101,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 phpseclib4\Crypt\RSA;
|
use phpseclib3\Crypt\RSA;
|
||||||
|
|
||||||
class SelfHostGenerateKeysCommand extends Command
|
class SelfHostGenerateKeysCommand extends Command
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ 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;
|
||||||
@@ -34,7 +32,7 @@ class EntityStillInUseApiException extends ApiException
|
|||||||
/**
|
/**
|
||||||
* 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(), [
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
<?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}'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
13
app/Filament/Resources/AuditResource/Pages/CreateAudit.php
Normal file
13
app/Filament/Resources/AuditResource/Pages/CreateAudit.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
18
app/Filament/Resources/AuditResource/Pages/ListAudits.php
Normal file
18
app/Filament/Resources/AuditResource/Pages/ListAudits.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?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 [];
|
||||||
|
}
|
||||||
|
}
|
||||||
13
app/Filament/Resources/AuditResource/Pages/ViewAudit.php
Normal file
13
app/Filament/Resources/AuditResource/Pages/ViewAudit.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?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,18 +4,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\ClientResource\Pages\CreateClient;
|
use App\Filament\Resources\ClientResource\Pages;
|
||||||
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\Schemas\Schema;
|
use Filament\Tables;
|
||||||
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 +18,16 @@ class ClientResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = Client::class;
|
protected static ?string $model = Client::class;
|
||||||
|
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-briefcase';
|
protected static ?string $navigationIcon = 'heroicon-o-briefcase';
|
||||||
|
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
protected static ?string $navigationGroup = 'Timetracking';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 4;
|
protected static ?int $navigationSort = 4;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->components([
|
->schema([
|
||||||
TextInput::make('name')
|
TextInput::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->required(),
|
->required(),
|
||||||
@@ -48,17 +43,17 @@ class ClientResource extends Resource
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('name')
|
Tables\Columns\TextColumn::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('organization.name')
|
Tables\Columns\TextColumn::make('organization.name')
|
||||||
->sortable()
|
->sortable()
|
||||||
->label('Organization'),
|
->label('Organization'),
|
||||||
TextColumn::make('created_at')
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
->label('Created at')
|
->label('Created at')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('updated_at')
|
Tables\Columns\TextColumn::make('updated_at')
|
||||||
->label('Updated at')
|
->label('Updated at')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
])
|
])
|
||||||
@@ -73,12 +68,12 @@ class ClientResource extends Resource
|
|||||||
->relationship('organization', 'id')
|
->relationship('organization', 'id')
|
||||||
->searchable(),
|
->searchable(),
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
EditAction::make(),
|
Tables\Actions\EditAction::make(),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
BulkActionGroup::make([
|
Tables\Actions\BulkActionGroup::make([
|
||||||
DeleteBulkAction::make(),
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -93,9 +88,9 @@ class ClientResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => ListClients::route('/'),
|
'index' => Pages\ListClients::route('/'),
|
||||||
'create' => CreateClient::route('/create'),
|
'create' => Pages\CreateClient::route('/create'),
|
||||||
'edit' => EditClient::route('/{record}/edit'),
|
'edit' => Pages\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\DeleteAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
DeleteAction::make()
|
Actions\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\CreateAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
CreateAction::make()
|
Actions\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\Schemas\Schema;
|
use Filament\Tables\Actions\Action;
|
||||||
|
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|\BackedEnum|null $navigationIcon = 'heroicon-o-exclamation-circle';
|
protected static ?string $navigationIcon = 'heroicon-o-exclamation-circle';
|
||||||
|
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'System';
|
protected static ?string $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(Schema $schema): Schema
|
public static function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->components([
|
->schema([
|
||||||
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([])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
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(),
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
DeleteAction::make(),
|
DeleteAction::make(),
|
||||||
ViewAction::make(),
|
ViewAction::make(),
|
||||||
Action::make('retry')
|
Action::make('retry')
|
||||||
|
|||||||
@@ -5,21 +5,14 @@ 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\EditOrganizationInvitation;
|
use App\Filament\Resources\OrganizationInvitationResource\Pages;
|
||||||
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\Actions\BulkAction;
|
use Filament\Forms;
|
||||||
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\Components\TextInput;
|
use Filament\Forms\Form;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
@@ -29,18 +22,18 @@ class OrganizationInvitationResource extends Resource
|
|||||||
|
|
||||||
protected static ?string $label = 'Invitations';
|
protected static ?string $label = 'Invitations';
|
||||||
|
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-user-plus';
|
protected static ?string $navigationIcon = 'heroicon-o-user-plus';
|
||||||
|
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Users';
|
protected static ?string $navigationGroup = 'Users';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 9;
|
protected static ?int $navigationSort = 9;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->columns(1)
|
->columns(1)
|
||||||
->components([
|
->schema([
|
||||||
TextInput::make('email')
|
Forms\Components\TextInput::make('email')
|
||||||
->label('Email')
|
->label('Email')
|
||||||
->disabledOn(['edit'])
|
->disabledOn(['edit'])
|
||||||
->required(),
|
->required(),
|
||||||
@@ -52,11 +45,11 @@ class OrganizationInvitationResource extends Resource
|
|||||||
->searchable(['name'])
|
->searchable(['name'])
|
||||||
->disabledOn(['edit'])
|
->disabledOn(['edit'])
|
||||||
->required(),
|
->required(),
|
||||||
DateTimePicker::make('created_at')
|
Forms\Components\DateTimePicker::make('created_at')
|
||||||
->label('Created At')
|
->label('Created At')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->disabled(),
|
->disabled(),
|
||||||
DateTimePicker::make('updated_at')
|
Forms\Components\DateTimePicker::make('updated_at')
|
||||||
->label('Updated At')
|
->label('Updated At')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->disabled(),
|
->disabled(),
|
||||||
@@ -67,17 +60,17 @@ class OrganizationInvitationResource extends Resource
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('organization.name')
|
Tables\Columns\TextColumn::make('organization.name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('email')
|
Tables\Columns\TextColumn::make('email')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('role'),
|
Tables\Columns\TextColumn::make('role'),
|
||||||
TextColumn::make('created_at')
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
->label('Created At')
|
->label('Created At')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('updated_at')
|
Tables\Columns\TextColumn::make('updated_at')
|
||||||
->label('Updated At')
|
->label('Updated At')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable()
|
->sortable()
|
||||||
@@ -87,13 +80,13 @@ class OrganizationInvitationResource extends Resource
|
|||||||
->filters([
|
->filters([
|
||||||
//
|
//
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
EditAction::make(),
|
Tables\Actions\EditAction::make(),
|
||||||
DeleteAction::make(),
|
Tables\Actions\DeleteAction::make(),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
BulkActionGroup::make([
|
Tables\Actions\BulkActionGroup::make([
|
||||||
BulkAction::make('resend')
|
Tables\Actions\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) {
|
||||||
@@ -113,9 +106,9 @@ class OrganizationInvitationResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => ListOrganizationInvitations::route('/'),
|
'index' => Pages\ListOrganizationInvitations::route('/'),
|
||||||
'edit' => EditOrganizationInvitation::route('/{record}/edit'),
|
'edit' => Pages\EditOrganizationInvitation::route('/{record}/edit'),
|
||||||
'view' => ViewOrganizationInvitation::route('/{record}'),
|
'view' => Pages\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\DeleteAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
DeleteAction::make()
|
Actions\DeleteAction::make()
|
||||||
->icon('heroicon-m-trash'),
|
->icon('heroicon-m-trash'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ 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\CreateOrganization;
|
use App\Filament\Resources\OrganizationResource\Pages;
|
||||||
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;
|
||||||
@@ -23,45 +20,38 @@ 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 Exception;
|
use Filament\Forms;
|
||||||
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\Components\TextInput;
|
use Filament\Forms\Form;
|
||||||
use Filament\Forms\Components\Toggle;
|
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Columns\IconColumn;
|
use Filament\Tables\Actions\Action;
|
||||||
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|\BackedEnum|null $navigationIcon = 'heroicon-o-building-office-2';
|
protected static ?string $navigationIcon = 'heroicon-o-building-office-2';
|
||||||
|
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Users';
|
protected static ?string $navigationGroup = 'Users';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 7;
|
protected static ?int $navigationSort = 7;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->columns(1)
|
->columns(1)
|
||||||
->components([
|
->schema([
|
||||||
TextInput::make('name')
|
Forms\Components\TextInput::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->required()
|
->required()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
Toggle::make('personal_team')
|
Forms\Components\Toggle::make('personal_team')
|
||||||
->label('Is personal?')
|
->label('Is personal?')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->required(),
|
->required(),
|
||||||
@@ -89,7 +79,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().')';
|
||||||
@@ -99,7 +89,7 @@ class OrganizationResource extends Resource
|
|||||||
})
|
})
|
||||||
->required()
|
->required()
|
||||||
->searchable(),
|
->searchable(),
|
||||||
TextInput::make('billable_rate')
|
Forms\Components\TextInput::make('billable_rate')
|
||||||
->label('Billable rate (in Cents)')
|
->label('Billable rate (in Cents)')
|
||||||
->nullable()
|
->nullable()
|
||||||
->rules([
|
->rules([
|
||||||
@@ -109,11 +99,11 @@ class OrganizationResource extends Resource
|
|||||||
'max:2147483647',
|
'max:2147483647',
|
||||||
])
|
])
|
||||||
->numeric(),
|
->numeric(),
|
||||||
DateTimePicker::make('created_at')
|
Forms\Components\DateTimePicker::make('created_at')
|
||||||
->label('Created At')
|
->label('Created At')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->disabled(),
|
->disabled(),
|
||||||
DateTimePicker::make('updated_at')
|
Forms\Components\DateTimePicker::make('updated_at')
|
||||||
->label('Updated At')
|
->label('Updated At')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->disabled(),
|
->disabled(),
|
||||||
@@ -127,7 +117,7 @@ class OrganizationResource extends Resource
|
|||||||
TextColumn::make('name')
|
TextColumn::make('name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
IconColumn::make('personal_team')
|
Tables\Columns\IconColumn::make('personal_team')
|
||||||
->boolean()
|
->boolean()
|
||||||
->label('Is personal?')
|
->label('Is personal?')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
@@ -148,9 +138,9 @@ class OrganizationResource extends Resource
|
|||||||
->filters([
|
->filters([
|
||||||
//
|
//
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
EditAction::make(),
|
Tables\Actions\EditAction::make(),
|
||||||
DeleteAction::make()
|
Tables\Actions\DeleteAction::make()
|
||||||
->using(function (Organization $record): void {
|
->using(function (Organization $record): void {
|
||||||
app(DeletionService::class)->deleteOrganization($record);
|
app(DeletionService::class)->deleteOrganization($record);
|
||||||
}),
|
}),
|
||||||
@@ -168,7 +158,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')
|
||||||
@@ -182,13 +172,9 @@ 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 {
|
||||||
try {
|
$file = Storage::disk(config('filament.default_filesystem_disk'))->get($data['file']);
|
||||||
$file = Storage::disk(config('filament.default_filesystem_disk'))->get($data['file']);
|
|
||||||
} catch (UnableToReadFile) {
|
|
||||||
$file = null;
|
|
||||||
}
|
|
||||||
if ($file === null) {
|
if ($file === null) {
|
||||||
throw new ImportException('File not found');
|
throw new \Exception('File not found');
|
||||||
}
|
}
|
||||||
/** @var string $timezone */
|
/** @var string $timezone */
|
||||||
$timezone = $data['timezone'];
|
$timezone = $data['timezone'];
|
||||||
@@ -223,8 +209,8 @@ class OrganizationResource extends Resource
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
->tooltip(fn (Organization $record): string => 'Import into '.$record->name)
|
->tooltip(fn (Organization $record): string => 'Import into '.$record->name)
|
||||||
->schema([
|
->form([
|
||||||
FileUpload::make('file')
|
Forms\Components\FileUpload::make('file')
|
||||||
->label('File')
|
->label('File')
|
||||||
->required(),
|
->required(),
|
||||||
Select::make('type')
|
Select::make('type')
|
||||||
@@ -244,7 +230,7 @@ class OrganizationResource extends Resource
|
|||||||
->required(),
|
->required(),
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,10 +245,10 @@ class OrganizationResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => ListOrganizations::route('/'),
|
'index' => Pages\ListOrganizations::route('/'),
|
||||||
'create' => CreateOrganization::route('/create'),
|
'create' => Pages\CreateOrganization::route('/create'),
|
||||||
'edit' => EditOrganization::route('/{record}/edit'),
|
'edit' => Pages\EditOrganization::route('/{record}/edit'),
|
||||||
'view' => ViewOrganization::route('/{record}'),
|
'view' => Pages\ViewOrganization::route('/{record}'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ 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
|
||||||
@@ -15,7 +14,7 @@ class EditOrganization extends EditRecord
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
DeleteOrganization::make(),
|
OrganizationResource\Actions\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\CreateAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
CreateAction::make()
|
Actions\CreateAction::make()
|
||||||
->icon('heroicon-s-plus'),
|
->icon('heroicon-s-plus'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,17 +9,12 @@ 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\Schemas\Schema;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Actions\Action;
|
||||||
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;
|
||||||
@@ -30,10 +25,10 @@ class InvitationsRelationManager extends RelationManager
|
|||||||
|
|
||||||
protected static ?string $title = 'Invitations';
|
protected static ?string $title = 'Invitations';
|
||||||
|
|
||||||
public function form(Schema $schema): Schema
|
public function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->components([
|
->schema([
|
||||||
TextInput::make('email')
|
TextInput::make('email')
|
||||||
->label('Email')
|
->label('Email')
|
||||||
->disabledOn(['edit'])
|
->disabledOn(['edit'])
|
||||||
@@ -58,33 +53,33 @@ class InvitationsRelationManager extends RelationManager
|
|||||||
->modelLabel('Invitation')
|
->modelLabel('Invitation')
|
||||||
->pluralModelLabel('Invitations')
|
->pluralModelLabel('Invitations')
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('email'),
|
Tables\Columns\TextColumn::make('email'),
|
||||||
TextColumn::make('role'),
|
Tables\Columns\TextColumn::make('role'),
|
||||||
])
|
])
|
||||||
->headerActions([
|
->headerActions([
|
||||||
CreateAction::make()
|
Tables\Actions\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'], ($data['role'] instanceof Role ? $data['role'] : Role::from($data['role'])), auth()->user());
|
->inviteUser($ownerRecord, $data['email'], Role::from($data['role']), auth()->user());
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
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(),
|
||||||
])),
|
])),
|
||||||
EditAction::make(),
|
Tables\Actions\EditAction::make(),
|
||||||
DeleteAction::make(),
|
Tables\Actions\DeleteAction::make(),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
BulkActionGroup::make([
|
Tables\Actions\BulkActionGroup::make([
|
||||||
DetachBulkAction::make(),
|
Tables\Actions\DetachBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,14 @@ 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\Schemas\Schema;
|
use Filament\Tables;
|
||||||
|
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;
|
||||||
@@ -29,10 +28,10 @@ class UsersRelationManager extends RelationManager
|
|||||||
{
|
{
|
||||||
protected static string $relationship = 'users';
|
protected static string $relationship = 'users';
|
||||||
|
|
||||||
public function form(Schema $schema): Schema
|
public function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->components([
|
->schema([
|
||||||
Select::make('role')
|
Select::make('role')
|
||||||
->options(Role::class),
|
->options(Role::class),
|
||||||
TextInput::make('billable_rate')
|
TextInput::make('billable_rate')
|
||||||
@@ -76,17 +75,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, ($data['role'] instanceof Role ? $data['role'] : Role::from($data['role'])), true);
|
app(MemberService::class)->addMember($record, $organization, Role::from($data['role']), true);
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
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(),
|
||||||
])),
|
])),
|
||||||
EditAction::make()
|
Tables\Actions\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();
|
||||||
@@ -98,10 +97,9 @@ class UsersRelationManager extends RelationManager
|
|||||||
app(BillableRateService::class)->updateTimeEntriesBillableRateForMember($member);
|
app(BillableRateService::class)->updateTimeEntriesBillableRateForMember($member);
|
||||||
}
|
}
|
||||||
|
|
||||||
$newRole = $data['role'] instanceof Role ? $data['role'] : Role::from($data['role']);
|
if ($data['role'] !== $member->role) {
|
||||||
if ($newRole->value !== $member->role) {
|
|
||||||
try {
|
try {
|
||||||
app(MemberService::class)->changeRole($member, $organization, $newRole, true);
|
app(MemberService::class)->changeRole($member, $organization, Role::from($data['role']), true);
|
||||||
} catch (ApiException $exception) {
|
} catch (ApiException $exception) {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->danger()
|
->danger()
|
||||||
@@ -115,7 +113,7 @@ class UsersRelationManager extends RelationManager
|
|||||||
|
|
||||||
return $record;
|
return $record;
|
||||||
}),
|
}),
|
||||||
DetachAction::make()
|
Tables\Actions\DetachAction::make()
|
||||||
->using(function (User $record): void {
|
->using(function (User $record): void {
|
||||||
/** @var Organization $organization */
|
/** @var Organization $organization */
|
||||||
$organization = $this->getOwnerRecord();
|
$organization = $this->getOwnerRecord();
|
||||||
@@ -135,7 +133,7 @@ class UsersRelationManager extends RelationManager
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\ProjectMemberResource\Pages\CreateProjectMember;
|
use App\Filament\Resources\ProjectMemberResource\Pages;
|
||||||
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\Actions\BulkActionGroup;
|
use Filament\Forms;
|
||||||
use Filament\Actions\DeleteBulkAction;
|
use Filament\Forms\Form;
|
||||||
use Filament\Actions\EditAction;
|
|
||||||
use Filament\Forms\Components\Select;
|
|
||||||
use Filament\Forms\Components\TextInput;
|
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
class ProjectMemberResource extends Resource
|
class ProjectMemberResource extends Resource
|
||||||
@@ -25,11 +18,11 @@ class ProjectMemberResource extends Resource
|
|||||||
|
|
||||||
protected static bool $shouldRegisterNavigation = false;
|
protected static bool $shouldRegisterNavigation = false;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->components([
|
->schema([
|
||||||
TextInput::make('billable_rate')
|
Forms\Components\TextInput::make('billable_rate')
|
||||||
->label('Billable rate (in Cents)')
|
->label('Billable rate (in Cents)')
|
||||||
->nullable()
|
->nullable()
|
||||||
->rules([
|
->rules([
|
||||||
@@ -39,10 +32,10 @@ class ProjectMemberResource extends Resource
|
|||||||
'max:2147483647',
|
'max:2147483647',
|
||||||
])
|
])
|
||||||
->numeric(),
|
->numeric(),
|
||||||
Select::make('user_id')
|
Forms\Components\Select::make('user_id')
|
||||||
->relationship('user', 'name')
|
->relationship('user', 'name')
|
||||||
->required(),
|
->required(),
|
||||||
Select::make('member_id')
|
Forms\Components\Select::make('member_id')
|
||||||
->relationship('member', 'id')
|
->relationship('member', 'id')
|
||||||
->required(),
|
->required(),
|
||||||
]);
|
]);
|
||||||
@@ -52,17 +45,17 @@ class ProjectMemberResource extends Resource
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('id')
|
Tables\Columns\TextColumn::make('id')
|
||||||
->label('ID'),
|
->label('ID'),
|
||||||
TextColumn::make('billable_rate')
|
Tables\Columns\TextColumn::make('billable_rate')
|
||||||
->numeric()
|
->numeric()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('project.name'),
|
Tables\Columns\TextColumn::make('project.name'),
|
||||||
TextColumn::make('user.name'),
|
Tables\Columns\TextColumn::make('user.name'),
|
||||||
TextColumn::make('created_at')
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('updated_at')
|
Tables\Columns\TextColumn::make('updated_at')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable()
|
->sortable()
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
@@ -70,12 +63,12 @@ class ProjectMemberResource extends Resource
|
|||||||
->filters([
|
->filters([
|
||||||
//
|
//
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
EditAction::make(),
|
Tables\Actions\EditAction::make(),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
BulkActionGroup::make([
|
Tables\Actions\BulkActionGroup::make([
|
||||||
DeleteBulkAction::make(),
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -90,10 +83,10 @@ class ProjectMemberResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => ListProjectMembers::route('/'),
|
'index' => Pages\ListProjectMembers::route('/'),
|
||||||
'create' => CreateProjectMember::route('/create'),
|
'create' => Pages\CreateProjectMember::route('/create'),
|
||||||
'edit' => EditProjectMember::route('/{record}/edit'),
|
'edit' => Pages\EditProjectMember::route('/{record}/edit'),
|
||||||
'view' => ViewProjectMembers::route('/{record}'),
|
'view' => Pages\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\DeleteAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
DeleteAction::make()
|
Actions\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\CreateAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
CreateAction::make()
|
Actions\CreateAction::make()
|
||||||
->icon('heroicon-s-plus'),
|
->icon('heroicon-s-plus'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,14 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\ProjectResource\Pages\CreateProject;
|
use App\Filament\Resources\ProjectResource\Pages;
|
||||||
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\Actions\BulkActionGroup;
|
use Filament\Forms;
|
||||||
use Filament\Actions\DeleteBulkAction;
|
|
||||||
use Filament\Actions\EditAction;
|
|
||||||
use Filament\Forms\Components\ColorPicker;
|
use Filament\Forms\Components\ColorPicker;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Form;
|
||||||
use Filament\Forms\Components\TextInput;
|
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Tables;
|
||||||
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;
|
||||||
@@ -26,24 +21,24 @@ class ProjectResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = Project::class;
|
protected static ?string $model = Project::class;
|
||||||
|
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-folder';
|
protected static ?string $navigationIcon = 'heroicon-o-folder';
|
||||||
|
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
protected static ?string $navigationGroup = 'Timetracking';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 2;
|
protected static ?int $navigationSort = 2;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->components([
|
->schema([
|
||||||
TextInput::make('name')
|
Forms\Components\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(),
|
||||||
TextInput::make('billable_rate')
|
Forms\Components\TextInput::make('billable_rate')
|
||||||
->label('Billable rate (in Cents)')
|
->label('Billable rate (in Cents)')
|
||||||
->nullable()
|
->nullable()
|
||||||
->rules([
|
->rules([
|
||||||
@@ -53,7 +48,7 @@ class ProjectResource extends Resource
|
|||||||
'max:2147483647',
|
'max:2147483647',
|
||||||
])
|
])
|
||||||
->numeric(),
|
->numeric(),
|
||||||
Select::make('organization_id')
|
Forms\Components\Select::make('organization_id')
|
||||||
->relationship(name: 'organization', titleAttribute: 'name')
|
->relationship(name: 'organization', titleAttribute: 'name')
|
||||||
->searchable(['name'])
|
->searchable(['name'])
|
||||||
->required(),
|
->required(),
|
||||||
@@ -86,12 +81,12 @@ class ProjectResource extends Resource
|
|||||||
->searchable(),
|
->searchable(),
|
||||||
])
|
])
|
||||||
->defaultSort('created_at', 'desc')
|
->defaultSort('created_at', 'desc')
|
||||||
->recordActions([
|
->actions([
|
||||||
EditAction::make(),
|
Tables\Actions\EditAction::make(),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
BulkActionGroup::make([
|
Tables\Actions\BulkActionGroup::make([
|
||||||
DeleteBulkAction::make(),
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -106,9 +101,9 @@ class ProjectResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => ListProjects::route('/'),
|
'index' => Pages\ListProjects::route('/'),
|
||||||
'create' => CreateProject::route('/create'),
|
'create' => Pages\CreateProject::route('/create'),
|
||||||
'edit' => EditProject::route('/{record}/edit'),
|
'edit' => Pages\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\DeleteAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
DeleteAction::make()
|
Actions\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\CreateAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
CreateAction::make()
|
Actions\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\Actions\Action;
|
use Filament\Forms\Form;
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Actions\Action;
|
||||||
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(Schema $schema): Schema
|
public function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->components([
|
->schema([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,8 +30,8 @@ class ProjectMembersRelationManager extends RelationManager
|
|||||||
return $table
|
return $table
|
||||||
->recordTitleAttribute('name')
|
->recordTitleAttribute('name')
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('user.name'),
|
Tables\Columns\TextColumn::make('user.name'),
|
||||||
TextColumn::make('billable_rate')
|
Tables\Columns\TextColumn::make('billable_rate')
|
||||||
->numeric()
|
->numeric()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
])
|
])
|
||||||
@@ -40,7 +40,7 @@ class ProjectMembersRelationManager extends RelationManager
|
|||||||
])
|
])
|
||||||
->headerActions([
|
->headerActions([
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
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(),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,16 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\ReportResource\Pages\EditReport;
|
use App\Filament\Resources\ReportResource\Pages;
|
||||||
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\Actions\Action;
|
use Filament\Forms;
|
||||||
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\Schemas\Schema;
|
use Filament\Tables;
|
||||||
|
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;
|
||||||
@@ -29,22 +24,22 @@ class ReportResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = Report::class;
|
protected static ?string $model = Report::class;
|
||||||
|
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-document-chart-bar';
|
protected static ?string $navigationIcon = 'heroicon-o-document-chart-bar';
|
||||||
|
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
protected static ?string $navigationGroup = 'Timetracking';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 7;
|
protected static ?int $navigationSort = 7;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->columns(1)
|
->columns(1)
|
||||||
->components([
|
->schema([
|
||||||
TextInput::make('name')
|
Forms\Components\TextInput::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->required()
|
->required()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
TextInput::make('description')
|
Forms\Components\TextInput::make('description')
|
||||||
->label('Description')
|
->label('Description')
|
||||||
->nullable()
|
->nullable()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
@@ -54,13 +49,13 @@ class ReportResource extends Resource
|
|||||||
DateTimePicker::make('public_until')
|
DateTimePicker::make('public_until')
|
||||||
->label('Public until')
|
->label('Public until')
|
||||||
->nullable(),
|
->nullable(),
|
||||||
Select::make('organization_id')
|
Forms\Components\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(),
|
||||||
TextInput::make('share_secret')
|
Forms\Components\TextInput::make('share_secret')
|
||||||
->label('Share Secret')
|
->label('Share Secret')
|
||||||
->nullable(),
|
->nullable(),
|
||||||
PrettyJsonField::make('properties')
|
PrettyJsonField::make('properties')
|
||||||
@@ -114,18 +109,18 @@ class ReportResource extends Resource
|
|||||||
->relationship('organization', 'id')
|
->relationship('organization', 'id')
|
||||||
->searchable(),
|
->searchable(),
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
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),
|
||||||
ViewAction::make(),
|
Tables\Actions\ViewAction::make(),
|
||||||
EditAction::make(),
|
Tables\Actions\EditAction::make(),
|
||||||
DeleteAction::make(),
|
Tables\Actions\DeleteAction::make(),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,9 +133,9 @@ class ReportResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => ListReports::route('/'),
|
'index' => Pages\ListReports::route('/'),
|
||||||
'edit' => EditReport::route('/{record}/edit'),
|
'edit' => Pages\EditReport::route('/{record}/edit'),
|
||||||
'view' => ViewReport::route('/{record}'),
|
'view' => Pages\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\DeleteAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
DeleteAction::make()
|
Actions\DeleteAction::make()
|
||||||
->icon('heroicon-m-trash'),
|
->icon('heroicon-m-trash'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\TagResource\Pages\CreateTag;
|
use App\Filament\Resources\TagResource\Pages;
|
||||||
use App\Filament\Resources\TagResource\Pages\EditTag;
|
|
||||||
use App\Filament\Resources\TagResource\Pages\ListTags;
|
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use Filament\Actions\BulkActionGroup;
|
use Filament\Forms;
|
||||||
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\Schemas\Schema;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
|
||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
@@ -23,20 +18,20 @@ class TagResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = Tag::class;
|
protected static ?string $model = Tag::class;
|
||||||
|
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-tag';
|
protected static ?string $navigationIcon = 'heroicon-o-tag';
|
||||||
|
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
protected static ?string $navigationGroup = 'Timetracking';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 5;
|
protected static ?int $navigationSort = 5;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->components([
|
->schema([
|
||||||
TextInput::make('name')
|
TextInput::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->required(),
|
->required(),
|
||||||
Select::make('organization_id')
|
Forms\Components\Select::make('organization_id')
|
||||||
->relationship(name: 'organization', titleAttribute: 'name')
|
->relationship(name: 'organization', titleAttribute: 'name')
|
||||||
->label('Organization')
|
->label('Organization')
|
||||||
->searchable(['name'])
|
->searchable(['name'])
|
||||||
@@ -48,17 +43,17 @@ class TagResource extends Resource
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('name')
|
Tables\Columns\TextColumn::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('organization.name')
|
Tables\Columns\TextColumn::make('organization.name')
|
||||||
->sortable()
|
->sortable()
|
||||||
->label('Organization'),
|
->label('Organization'),
|
||||||
TextColumn::make('created_at')
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
->label('Created at')
|
->label('Created at')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('updated_at')
|
Tables\Columns\TextColumn::make('updated_at')
|
||||||
->label('Updated at')
|
->label('Updated at')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
])
|
])
|
||||||
@@ -73,12 +68,12 @@ class TagResource extends Resource
|
|||||||
->relationship('organization', 'id')
|
->relationship('organization', 'id')
|
||||||
->searchable(),
|
->searchable(),
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
EditAction::make(),
|
Tables\Actions\EditAction::make(),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
BulkActionGroup::make([
|
Tables\Actions\BulkActionGroup::make([
|
||||||
DeleteBulkAction::make(),
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -93,9 +88,9 @@ class TagResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => ListTags::route('/'),
|
'index' => Pages\ListTags::route('/'),
|
||||||
'create' => CreateTag::route('/create'),
|
'create' => Pages\CreateTag::route('/create'),
|
||||||
'edit' => EditTag::route('/{record}/edit'),
|
'edit' => Pages\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\DeleteAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
DeleteAction::make()
|
Actions\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\CreateAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
CreateAction::make()
|
Actions\CreateAction::make()
|
||||||
->icon('heroicon-s-plus'),
|
->icon('heroicon-s-plus'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\TaskResource\Pages\CreateTask;
|
use App\Filament\Resources\TaskResource\Pages;
|
||||||
use App\Filament\Resources\TaskResource\Pages\EditTask;
|
|
||||||
use App\Filament\Resources\TaskResource\Pages\ListTasks;
|
|
||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
use Filament\Actions\BulkActionGroup;
|
use Filament\Forms;
|
||||||
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\Form;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
|
||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
@@ -23,17 +18,17 @@ class TaskResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = Task::class;
|
protected static ?string $model = Task::class;
|
||||||
|
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-list-bullet';
|
protected static ?string $navigationIcon = 'heroicon-o-list-bullet';
|
||||||
|
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
protected static ?string $navigationGroup = 'Timetracking';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 3;
|
protected static ?int $navigationSort = 3;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->components([
|
->schema([
|
||||||
TextInput::make('name')
|
Forms\Components\TextInput::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->required()
|
->required()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
@@ -52,16 +47,16 @@ class TaskResource extends Resource
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('name')
|
Tables\Columns\TextColumn::make('name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('project.name')
|
Tables\Columns\TextColumn::make('project.name')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('organization.name')
|
Tables\Columns\TextColumn::make('organization.name')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('created_at')
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('updated_at')
|
Tables\Columns\TextColumn::make('updated_at')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
])
|
])
|
||||||
->filters([
|
->filters([
|
||||||
@@ -75,12 +70,12 @@ class TaskResource extends Resource
|
|||||||
->searchable(),
|
->searchable(),
|
||||||
])
|
])
|
||||||
->defaultSort('created_at', 'desc')
|
->defaultSort('created_at', 'desc')
|
||||||
->recordActions([
|
->actions([
|
||||||
EditAction::make(),
|
Tables\Actions\EditAction::make(),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
BulkActionGroup::make([
|
Tables\Actions\BulkActionGroup::make([
|
||||||
DeleteBulkAction::make(),
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -95,9 +90,9 @@ class TaskResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => ListTasks::route('/'),
|
'index' => Pages\ListTasks::route('/'),
|
||||||
'create' => CreateTask::route('/create'),
|
'create' => Pages\CreateTask::route('/create'),
|
||||||
'edit' => EditTask::route('/{record}/edit'),
|
'edit' => Pages\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\DeleteAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
DeleteAction::make()
|
Actions\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\CreateAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
CreateAction::make()
|
Actions\CreateAction::make()
|
||||||
->icon('heroicon-s-plus'),
|
->icon('heroicon-s-plus'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,16 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\TimeEntryResource\Pages\CreateTimeEntry;
|
use App\Filament\Resources\TimeEntryResource\Pages;
|
||||||
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\Schemas\Schema;
|
use Filament\Tables;
|
||||||
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;
|
||||||
@@ -27,16 +23,16 @@ class TimeEntryResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = TimeEntry::class;
|
protected static ?string $model = TimeEntry::class;
|
||||||
|
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-clock';
|
protected static ?string $navigationIcon = 'heroicon-o-clock';
|
||||||
|
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
protected static ?string $navigationGroup = 'Timetracking';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 1;
|
protected static ?int $navigationSort = 1;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->components([
|
->schema([
|
||||||
TextInput::make('id')
|
TextInput::make('id')
|
||||||
->label('ID')
|
->label('ID')
|
||||||
->readOnly()
|
->readOnly()
|
||||||
@@ -115,12 +111,12 @@ class TimeEntryResource extends Resource
|
|||||||
->searchable(),
|
->searchable(),
|
||||||
])
|
])
|
||||||
->defaultSort('created_at', 'desc')
|
->defaultSort('created_at', 'desc')
|
||||||
->recordActions([
|
->actions([
|
||||||
EditAction::make(),
|
Tables\Actions\EditAction::make(),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
BulkActionGroup::make([
|
Tables\Actions\BulkActionGroup::make([
|
||||||
DeleteBulkAction::make(),
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -135,9 +131,9 @@ class TimeEntryResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => ListTimeEntries::route('/'),
|
'index' => Pages\ListTimeEntries::route('/'),
|
||||||
'create' => CreateTimeEntry::route('/create'),
|
'create' => Pages\CreateTimeEntry::route('/create'),
|
||||||
'edit' => EditTimeEntry::route('/{record}/edit'),
|
'edit' => Pages\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\DeleteAction;
|
use Filament\Actions;
|
||||||
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 [
|
||||||
DeleteAction::make()
|
Actions\DeleteAction::make()
|
||||||
->icon('heroicon-m-trash'),
|
->icon('heroicon-m-trash'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\TimeEntryResource\Pages;
|
namespace App\Filament\Resources\TimeEntryResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\TimeEntryResource;
|
use App\Filament\Resources\TimeEntryResource;
|
||||||
use Filament\Actions\CreateAction;
|
use Filament\Actions;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
class ListTimeEntries extends ListRecords
|
class ListTimeEntries extends ListRecords
|
||||||
@@ -15,7 +15,7 @@ class ListTimeEntries extends ListRecords
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
CreateAction::make()
|
Actions\CreateAction::make()
|
||||||
->icon('heroicon-s-plus'),
|
->icon('heroicon-s-plus'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\TokenResource\Pages\ListTokens;
|
use App\Filament\Resources\TokenResource\Pages;
|
||||||
use App\Filament\Resources\TokenResource\Pages\ViewToken;
|
|
||||||
use App\Models\Passport\Token;
|
use App\Models\Passport\Token;
|
||||||
use Filament\Actions\ViewAction;
|
use Filament\Forms;
|
||||||
use Filament\Forms\Components\DateTimePicker;
|
use Filament\Forms\Form;
|
||||||
use Filament\Forms\Components\Select;
|
|
||||||
use Filament\Forms\Components\TextInput;
|
|
||||||
use Filament\Forms\Components\Toggle;
|
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Columns\IconColumn;
|
|
||||||
use Filament\Tables\Columns\TextColumn;
|
|
||||||
use Filament\Tables\Filters\TernaryFilter;
|
use Filament\Tables\Filters\TernaryFilter;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@@ -24,48 +18,48 @@ class TokenResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = Token::class;
|
protected static ?string $model = Token::class;
|
||||||
|
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-key';
|
protected static ?string $navigationIcon = 'heroicon-o-key';
|
||||||
|
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Auth';
|
protected static ?string $navigationGroup = 'Auth';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 6;
|
protected static ?int $navigationSort = 6;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->columns(1)
|
->columns(1)
|
||||||
->components([
|
->schema([
|
||||||
TextInput::make('id')
|
Forms\Components\TextInput::make('id')
|
||||||
->label('ID')
|
->label('ID')
|
||||||
->disabled()
|
->disabled()
|
||||||
->visibleOn(['update', 'show'])
|
->visibleOn(['update', 'show'])
|
||||||
->readOnly()
|
->readOnly()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
TextInput::make('name')
|
Forms\Components\TextInput::make('name')
|
||||||
->label('Name')
|
->label('Name')
|
||||||
->required()
|
->required()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
Select::make('owner_id')
|
Forms\Components\Select::make('owner_id')
|
||||||
->label('User')
|
->label('User')
|
||||||
->relationship(name: 'user', titleAttribute: 'name')
|
->relationship(name: 'user', titleAttribute: 'name')
|
||||||
->searchable(['name'])
|
->searchable(['name'])
|
||||||
->disabled()
|
->disabled()
|
||||||
->required(),
|
->required(),
|
||||||
Select::make('client_id')
|
Forms\Components\Select::make('client_id')
|
||||||
->label('Client')
|
->label('Client')
|
||||||
->relationship(name: 'client', titleAttribute: 'name')
|
->relationship(name: 'client', titleAttribute: 'name')
|
||||||
->searchable(['name'])
|
->searchable(['name'])
|
||||||
->required(),
|
->required(),
|
||||||
Toggle::make('revoked')
|
Forms\Components\Toggle::make('revoked')
|
||||||
->label('Revoked')
|
->label('Revoked')
|
||||||
->required(),
|
->required(),
|
||||||
DateTimePicker::make('expires_at')
|
Forms\Components\DateTimePicker::make('expires_at')
|
||||||
->label('Expires At')
|
->label('Expires At')
|
||||||
->disabled(),
|
->disabled(),
|
||||||
DateTimePicker::make('created_at')
|
Forms\Components\DateTimePicker::make('created_at')
|
||||||
->label('Created At')
|
->label('Created At')
|
||||||
->disabled(),
|
->disabled(),
|
||||||
DateTimePicker::make('updated_at')
|
Forms\Components\DateTimePicker::make('updated_at')
|
||||||
->label('Updated At')
|
->label('Updated At')
|
||||||
->disabled(),
|
->disabled(),
|
||||||
]);
|
]);
|
||||||
@@ -75,32 +69,32 @@ class TokenResource extends Resource
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('name')
|
Tables\Columns\TextColumn::make('name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('user.name')
|
Tables\Columns\TextColumn::make('user.name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('client.name')
|
Tables\Columns\TextColumn::make('client.name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
IconColumn::make('personal_access_client')
|
Tables\Columns\IconColumn::make('personal_access_client')
|
||||||
->state(function (Token $token): bool {
|
->state(function (Token $token): bool {
|
||||||
return in_array('personal_access', $token->client->grant_types ?? [], true);
|
return in_array('personal_access', $token->client->grant_types ?? [], true);
|
||||||
})
|
})
|
||||||
->boolean()
|
->boolean()
|
||||||
->label('API token?'),
|
->label('API token?'),
|
||||||
IconColumn::make('revoked')
|
Tables\Columns\IconColumn::make('revoked')
|
||||||
->boolean()
|
->boolean()
|
||||||
->label('Revoked?')
|
->label('Revoked?')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('expires_at')
|
Tables\Columns\TextColumn::make('expires_at')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('created_at')
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('updated_at')
|
Tables\Columns\TextColumn::make('updated_at')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable()
|
->sortable()
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
@@ -126,10 +120,10 @@ class TokenResource extends Resource
|
|||||||
TernaryFilter::make('revoked')
|
TernaryFilter::make('revoked')
|
||||||
->label('Revoked?'),
|
->label('Revoked?'),
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
ViewAction::make(),
|
Tables\Actions\ViewAction::make(),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,8 +136,8 @@ class TokenResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => ListTokens::route('/'),
|
'index' => Pages\ListTokens::route('/'),
|
||||||
'view' => ViewToken::route('/{record}'),
|
'view' => Pages\ViewToken::route('/{record}'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,30 +6,21 @@ namespace App\Filament\Resources;
|
|||||||
|
|
||||||
use App\Enums\Weekday;
|
use App\Enums\Weekday;
|
||||||
use App\Exceptions\Api\ApiException;
|
use App\Exceptions\Api\ApiException;
|
||||||
use App\Filament\Resources\UserResource\Pages\CreateUser;
|
use App\Filament\Resources\UserResource\Pages;
|
||||||
use App\Filament\Resources\UserResource\Pages\EditUser;
|
|
||||||
use App\Filament\Resources\UserResource\Pages\ListUsers;
|
|
||||||
use App\Filament\Resources\UserResource\Pages\ViewUser;
|
|
||||||
use App\Filament\Resources\UserResource\RelationManagers\OrganizationsRelationManager;
|
use App\Filament\Resources\UserResource\RelationManagers\OrganizationsRelationManager;
|
||||||
use App\Filament\Resources\UserResource\RelationManagers\OwnedOrganizationsRelationManager;
|
use App\Filament\Resources\UserResource\RelationManagers\OwnedOrganizationsRelationManager;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Service\DeletionService;
|
use App\Service\DeletionService;
|
||||||
use App\Service\TimezoneService;
|
use App\Service\TimezoneService;
|
||||||
use App\Service\UserService;
|
use App\Service\UserService;
|
||||||
use Brick\Money\IsoCurrencyProvider;
|
use Brick\Money\ISOCurrencyProvider;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Filament\Actions\BulkAction;
|
use Filament\Forms;
|
||||||
use Filament\Actions\DeleteAction;
|
|
||||||
use Filament\Actions\EditAction;
|
|
||||||
use Filament\Forms\Components\DateTimePicker;
|
|
||||||
use Filament\Forms\Components\Select;
|
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Components\Toggle;
|
use Filament\Forms\Form;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Columns\IconColumn;
|
|
||||||
use Filament\Tables\Columns\TextColumn;
|
|
||||||
use Filament\Tables\Filters\TernaryFilter;
|
use Filament\Tables\Filters\TernaryFilter;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@@ -37,26 +28,26 @@ use Illuminate\Support\Collection;
|
|||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Korridor\LaravelModelValidationRules\Rules\UniqueEloquent;
|
use Korridor\LaravelModelValidationRules\Rules\UniqueEloquent;
|
||||||
use STS\FilamentImpersonate\Actions\Impersonate;
|
use STS\FilamentImpersonate\Tables\Actions\Impersonate;
|
||||||
|
|
||||||
class UserResource extends Resource
|
class UserResource extends Resource
|
||||||
{
|
{
|
||||||
protected static ?string $model = User::class;
|
protected static ?string $model = User::class;
|
||||||
|
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-user';
|
protected static ?string $navigationIcon = 'heroicon-o-user';
|
||||||
|
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Users';
|
protected static ?string $navigationGroup = 'Users';
|
||||||
|
|
||||||
protected static ?int $navigationSort = 6;
|
protected static ?int $navigationSort = 6;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Form $form): Form
|
||||||
{
|
{
|
||||||
/** @var User|null $record */
|
/** @var User|null $record */
|
||||||
$record = $schema->getRecord();
|
$record = $form->getRecord();
|
||||||
|
|
||||||
return $schema
|
return $form
|
||||||
->columns(1)
|
->columns(1)
|
||||||
->components([
|
->schema([
|
||||||
TextInput::make('id')
|
TextInput::make('id')
|
||||||
->label('ID')
|
->label('ID')
|
||||||
->disabled()
|
->disabled()
|
||||||
@@ -78,23 +69,23 @@ class UserResource extends Resource
|
|||||||
'email:rfc,strict',
|
'email:rfc,strict',
|
||||||
])
|
])
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
Toggle::make('is_placeholder')
|
Forms\Components\Toggle::make('is_placeholder')
|
||||||
->label('Is Placeholder?')
|
->label('Is Placeholder?')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->disabledOn(['edit']),
|
->disabledOn(['edit']),
|
||||||
DateTimePicker::make('email_verified_at')
|
Forms\Components\DateTimePicker::make('email_verified_at')
|
||||||
->label('Email Verified At')
|
->label('Email Verified At')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->nullable(),
|
->nullable(),
|
||||||
Toggle::make('is_email_verified')
|
Forms\Components\Toggle::make('is_email_verified')
|
||||||
->label('Email Verified?')
|
->label('Email Verified?')
|
||||||
->visibleOn(['create']),
|
->visibleOn(['create']),
|
||||||
Select::make('timezone')
|
Forms\Components\Select::make('timezone')
|
||||||
->label('Timezone')
|
->label('Timezone')
|
||||||
->options(fn (): array => app(TimezoneService::class)->getSelectOptions())
|
->options(fn (): array => app(TimezoneService::class)->getSelectOptions())
|
||||||
->searchable()
|
->searchable()
|
||||||
->required(),
|
->required(),
|
||||||
Select::make('week_start')
|
Forms\Components\Select::make('week_start')
|
||||||
->label('Week Start')
|
->label('Week Start')
|
||||||
->options(Weekday::class)
|
->options(Weekday::class)
|
||||||
->required(),
|
->required(),
|
||||||
@@ -112,10 +103,10 @@ class UserResource extends Resource
|
|||||||
->visibleOn(['create'])
|
->visibleOn(['create'])
|
||||||
->required(fn (string $context): bool => $context === 'create')
|
->required(fn (string $context): bool => $context === 'create')
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
Select::make('currency')
|
Forms\Components\Select::make('currency')
|
||||||
->label('Currency (Personal Organization)')
|
->label('Currency (Personal Organization)')
|
||||||
->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().')';
|
||||||
@@ -126,11 +117,11 @@ class UserResource extends Resource
|
|||||||
->required()
|
->required()
|
||||||
->visibleOn(['create'])
|
->visibleOn(['create'])
|
||||||
->searchable(),
|
->searchable(),
|
||||||
DateTimePicker::make('created_at')
|
Forms\Components\DateTimePicker::make('created_at')
|
||||||
->label('Created At')
|
->label('Created At')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->disabled(),
|
->disabled(),
|
||||||
DateTimePicker::make('updated_at')
|
Forms\Components\DateTimePicker::make('updated_at')
|
||||||
->label('Updated At')
|
->label('Updated At')
|
||||||
->hiddenOn(['create'])
|
->hiddenOn(['create'])
|
||||||
->disabled(),
|
->disabled(),
|
||||||
@@ -141,25 +132,25 @@ class UserResource extends Resource
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('name')
|
Tables\Columns\TextColumn::make('name')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('email')
|
Tables\Columns\TextColumn::make('email')
|
||||||
->icon('heroicon-m-envelope')
|
->icon('heroicon-m-envelope')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
IconColumn::make('is_real_user')
|
Tables\Columns\IconColumn::make('is_real_user')
|
||||||
->getStateUsing(fn (User $record): bool => ! $record->is_placeholder)
|
->getStateUsing(fn (User $record): bool => ! $record->is_placeholder)
|
||||||
->label('Real user?')
|
->label('Real user?')
|
||||||
->boolean(),
|
->boolean(),
|
||||||
IconColumn::make('email_verified')
|
Tables\Columns\IconColumn::make('email_verified')
|
||||||
->getStateUsing(fn (User $record): bool => $record->email_verified_at !== null)
|
->getStateUsing(fn (User $record): bool => $record->email_verified_at !== null)
|
||||||
->label('Email verified?')
|
->label('Email verified?')
|
||||||
->boolean(),
|
->boolean(),
|
||||||
TextColumn::make('created_at')
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
TextColumn::make('updated_at')
|
Tables\Columns\TextColumn::make('updated_at')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable()
|
->sortable()
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
@@ -187,7 +178,7 @@ class UserResource extends Resource
|
|||||||
->attribute('email_verified_at')
|
->attribute('email_verified_at')
|
||||||
->nullable(),
|
->nullable(),
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
Impersonate::make()->before(function (User $record): void {
|
Impersonate::make()->before(function (User $record): void {
|
||||||
if ($record->currentOrganization === null) {
|
if ($record->currentOrganization === null) {
|
||||||
$organization = $record->organizations()->where('personal_team', '=', true)->first();
|
$organization = $record->organizations()->where('personal_team', '=', true)->first();
|
||||||
@@ -200,8 +191,8 @@ class UserResource extends Resource
|
|||||||
app(UserService::class)->switchCurrentOrganization($record, $organization);
|
app(UserService::class)->switchCurrentOrganization($record, $organization);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
EditAction::make(),
|
Tables\Actions\EditAction::make(),
|
||||||
DeleteAction::make()
|
Tables\Actions\DeleteAction::make()
|
||||||
->hidden(fn (User $record) => $record->is(Auth::user()))
|
->hidden(fn (User $record) => $record->is(Auth::user()))
|
||||||
->using(function (User $record): void {
|
->using(function (User $record): void {
|
||||||
try {
|
try {
|
||||||
@@ -216,8 +207,8 @@ class UserResource extends Resource
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
BulkAction::make('Resend verification email')
|
Tables\Actions\BulkAction::make('Resend verification email')
|
||||||
->icon('heroicon-o-paper-airplane')
|
->icon('heroicon-o-paper-airplane')
|
||||||
->action(function (Collection $records): void {
|
->action(function (Collection $records): void {
|
||||||
foreach ($records as $user) {
|
foreach ($records as $user) {
|
||||||
@@ -239,10 +230,10 @@ class UserResource extends Resource
|
|||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => ListUsers::route('/'),
|
'index' => Pages\ListUsers::route('/'),
|
||||||
'create' => CreateUser::route('/create'),
|
'create' => Pages\CreateUser::route('/create'),
|
||||||
'edit' => EditUser::route('/{record}/edit'),
|
'edit' => Pages\EditUser::route('/{record}/edit'),
|
||||||
'view' => ViewUser::route('/{record}'),
|
'view' => Pages\ViewUser::route('/{record}'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class CreateUser extends CreateRecord
|
|||||||
$data['email'],
|
$data['email'],
|
||||||
$data['password_create'],
|
$data['password_create'],
|
||||||
$data['timezone'],
|
$data['timezone'],
|
||||||
$data['week_start'] instanceof Weekday ? $data['week_start'] : Weekday::from($data['week_start']),
|
Weekday::from($data['week_start']),
|
||||||
$data['currency'],
|
$data['currency'],
|
||||||
verifyEmail: (bool) $data['is_email_verified']
|
verifyEmail: (bool) $data['is_email_verified']
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\UserResource\Pages;
|
namespace App\Filament\Resources\UserResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\UserResource;
|
use App\Filament\Resources\UserResource;
|
||||||
use App\Filament\Resources\UserResource\Actions\DeleteUser;
|
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
use STS\FilamentImpersonate\Actions\Impersonate;
|
use STS\FilamentImpersonate\Pages\Actions\Impersonate;
|
||||||
|
|
||||||
class EditUser extends EditRecord
|
class EditUser extends EditRecord
|
||||||
{
|
{
|
||||||
@@ -17,7 +16,7 @@ class EditUser extends EditRecord
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Impersonate::make()->record($this->getRecord()),
|
Impersonate::make()->record($this->getRecord()),
|
||||||
DeleteUser::make(),
|
UserResource\Actions\DeleteUser::make(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\UserResource\Pages;
|
namespace App\Filament\Resources\UserResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\UserResource;
|
use App\Filament\Resources\UserResource;
|
||||||
use Filament\Actions\CreateAction;
|
use Filament\Actions;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
class ListUsers extends ListRecords
|
class ListUsers extends ListRecords
|
||||||
@@ -15,7 +15,7 @@ class ListUsers extends ListRecords
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
CreateAction::make()
|
Actions\CreateAction::make()
|
||||||
->icon('heroicon-s-plus'),
|
->icon('heroicon-s-plus'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace App\Filament\Resources\UserResource\Pages;
|
|||||||
use App\Filament\Resources\UserResource;
|
use App\Filament\Resources\UserResource;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Resources\Pages\ViewRecord;
|
use Filament\Resources\Pages\ViewRecord;
|
||||||
use STS\FilamentImpersonate\Actions\Impersonate;
|
use STS\FilamentImpersonate\Pages\Actions\Impersonate;
|
||||||
|
|
||||||
class ViewUser extends ViewRecord
|
class ViewUser extends ViewRecord
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,13 +11,12 @@ use App\Models\Member;
|
|||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Service\MemberService;
|
use App\Service\MemberService;
|
||||||
use Filament\Actions\Action;
|
|
||||||
use Filament\Actions\DetachAction;
|
|
||||||
use Filament\Actions\EditAction;
|
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Forms\Form;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Actions\Action;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
@@ -25,10 +24,10 @@ class OrganizationsRelationManager extends RelationManager
|
|||||||
{
|
{
|
||||||
protected static string $relationship = 'organizations';
|
protected static string $relationship = 'organizations';
|
||||||
|
|
||||||
public function form(Schema $schema): Schema
|
public function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->components([
|
->schema([
|
||||||
Select::make('role')
|
Select::make('role')
|
||||||
->options(Role::class),
|
->options(Role::class),
|
||||||
]);
|
]);
|
||||||
@@ -47,22 +46,21 @@ class OrganizationsRelationManager extends RelationManager
|
|||||||
])
|
])
|
||||||
->headerActions([
|
->headerActions([
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
Action::make('view')
|
Action::make('view')
|
||||||
->icon('heroicon-o-eye')
|
->icon('heroicon-o-eye')
|
||||||
->color('gray')
|
->color('gray')
|
||||||
->url(fn (Organization $record): string => OrganizationResource::getUrl('view', [
|
->url(fn (Organization $record): string => OrganizationResource::getUrl('view', [
|
||||||
'record' => $record->getKey(),
|
'record' => $record->getKey(),
|
||||||
])),
|
])),
|
||||||
EditAction::make()
|
Tables\Actions\EditAction::make()
|
||||||
->using(function (Organization $record, array $data): Organization {
|
->using(function (Organization $record, array $data): Organization {
|
||||||
/** @var Member $member */
|
/** @var Member $member */
|
||||||
$member = $record->getRelation('membership');
|
$member = $record->getRelation('membership');
|
||||||
|
|
||||||
$newRole = $data['role'] instanceof Role ? $data['role'] : Role::from($data['role']);
|
if ($data['role'] !== $member->role) {
|
||||||
if ($newRole->value !== $member->role) {
|
|
||||||
try {
|
try {
|
||||||
app(MemberService::class)->changeRole($member, $record, $newRole, true);
|
app(MemberService::class)->changeRole($member, $record, Role::from($data['role']), true);
|
||||||
} catch (ApiException $exception) {
|
} catch (ApiException $exception) {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->danger()
|
->danger()
|
||||||
@@ -76,7 +74,7 @@ class OrganizationsRelationManager extends RelationManager
|
|||||||
|
|
||||||
return $record;
|
return $record;
|
||||||
}),
|
}),
|
||||||
DetachAction::make()
|
Tables\Actions\DetachAction::make()
|
||||||
->using(function (Organization $record): void {
|
->using(function (Organization $record): void {
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = $this->getOwnerRecord();
|
$user = $this->getOwnerRecord();
|
||||||
@@ -96,7 +94,7 @@ class OrganizationsRelationManager extends RelationManager
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ namespace App\Filament\Resources\UserResource\RelationManagers;
|
|||||||
|
|
||||||
use App\Filament\Resources\OrganizationResource;
|
use App\Filament\Resources\OrganizationResource;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
use Filament\Actions\Action;
|
use Filament\Forms\Form;
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Actions\Action;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
class OwnedOrganizationsRelationManager extends RelationManager
|
class OwnedOrganizationsRelationManager extends RelationManager
|
||||||
@@ -18,10 +18,10 @@ class OwnedOrganizationsRelationManager extends RelationManager
|
|||||||
|
|
||||||
protected static string $relationship = 'ownedOrganizations';
|
protected static string $relationship = 'ownedOrganizations';
|
||||||
|
|
||||||
public function form(Schema $schema): Schema
|
public function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $schema
|
return $form
|
||||||
->components([
|
->schema([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,14 +30,14 @@ class OwnedOrganizationsRelationManager extends RelationManager
|
|||||||
return $table
|
return $table
|
||||||
->recordTitleAttribute('name')
|
->recordTitleAttribute('name')
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('name'),
|
Tables\Columns\TextColumn::make('name'),
|
||||||
])
|
])
|
||||||
->filters([
|
->filters([
|
||||||
//
|
//
|
||||||
])
|
])
|
||||||
->headerActions([
|
->headerActions([
|
||||||
])
|
])
|
||||||
->recordActions([
|
->actions([
|
||||||
Action::make('view')
|
Action::make('view')
|
||||||
->icon('heroicon-o-eye')
|
->icon('heroicon-o-eye')
|
||||||
->color('gray')
|
->color('gray')
|
||||||
@@ -51,7 +51,7 @@ class OwnedOrganizationsRelationManager extends RelationManager
|
|||||||
]))
|
]))
|
||||||
->openUrlInNewTab(),
|
->openUrlInNewTab(),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->bulkActions([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Cache;
|
|||||||
|
|
||||||
class ServerOverview extends Widget
|
class ServerOverview extends Widget
|
||||||
{
|
{
|
||||||
protected string $view = 'filament.widgets.server-overview';
|
protected static string $view = 'filament.widgets.server-overview';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use Flowframe\Trend\TrendValue;
|
|||||||
|
|
||||||
class TimeEntriesCreated extends ChartWidget
|
class TimeEntriesCreated extends ChartWidget
|
||||||
{
|
{
|
||||||
protected ?string $heading = 'Time Entries Created';
|
protected static ?string $heading = 'Time Entries Created';
|
||||||
|
|
||||||
public ?string $filter = 'week';
|
public ?string $filter = 'week';
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ class TimeEntriesCreated extends ChartWidget
|
|||||||
return [
|
return [
|
||||||
'datasets' => [
|
'datasets' => [
|
||||||
[
|
[
|
||||||
'label' => $this->heading,
|
'label' => self::$heading,
|
||||||
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
|
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use Flowframe\Trend\TrendValue;
|
|||||||
|
|
||||||
class TimeEntriesImported extends ChartWidget
|
class TimeEntriesImported extends ChartWidget
|
||||||
{
|
{
|
||||||
protected ?string $heading = 'Time Entries Imported';
|
protected static ?string $heading = 'Time Entries Imported';
|
||||||
|
|
||||||
public ?string $filter = 'week';
|
public ?string $filter = 'week';
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ class TimeEntriesImported extends ChartWidget
|
|||||||
return [
|
return [
|
||||||
'datasets' => [
|
'datasets' => [
|
||||||
[
|
[
|
||||||
'label' => $this->heading,
|
'label' => self::$heading,
|
||||||
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
|
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use Flowframe\Trend\TrendValue;
|
|||||||
|
|
||||||
class UserRegistrations extends ChartWidget
|
class UserRegistrations extends ChartWidget
|
||||||
{
|
{
|
||||||
protected ?string $heading = 'User Registrations';
|
protected static ?string $heading = 'User Registrations';
|
||||||
|
|
||||||
public ?string $filter = 'week';
|
public ?string $filter = 'week';
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ class UserRegistrations extends ChartWidget
|
|||||||
return [
|
return [
|
||||||
'datasets' => [
|
'datasets' => [
|
||||||
[
|
[
|
||||||
'label' => $this->heading,
|
'label' => self::$heading,
|
||||||
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
|
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ use Illuminate\Auth\Access\AuthorizationException;
|
|||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
class ApiTokenController extends Controller
|
class ApiTokenController extends Controller
|
||||||
{
|
{
|
||||||
@@ -63,7 +62,7 @@ class ApiTokenController extends Controller
|
|||||||
$tokenModel = $token->getToken();
|
$tokenModel = $token->getToken();
|
||||||
|
|
||||||
return new ApiTokenWithAccessTokenResource($tokenModel, $token->accessToken);
|
return new ApiTokenWithAccessTokenResource($tokenModel, $token->accessToken);
|
||||||
} catch (RuntimeException $exception) {
|
} catch (\RuntimeException $exception) {
|
||||||
report($exception);
|
report($exception);
|
||||||
if (Str::contains($exception->getMessage(), ['Personal access client not found'])) {
|
if (Str::contains($exception->getMessage(), ['Personal access client not found'])) {
|
||||||
throw new PersonalAccessClientIsNotConfiguredException;
|
throw new PersonalAccessClientIsNotConfiguredException;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace App\Http\Controllers\Api\V1;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Service\CurrencyService;
|
use App\Service\CurrencyService;
|
||||||
use Brick\Money\Currency;
|
use Brick\Money\Currency;
|
||||||
use Brick\Money\IsoCurrencyProvider;
|
use Brick\Money\ISOCurrencyProvider;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
class CurrencyController extends Controller
|
class CurrencyController extends Controller
|
||||||
@@ -29,7 +29,7 @@ class CurrencyController extends Controller
|
|||||||
'name' => $currency->getName(),
|
'name' => $currency->getName(),
|
||||||
'symbol' => $currencyService->getCurrencySymbol($currency->getCurrencyCode()),
|
'symbol' => $currencyService->getCurrencySymbol($currency->getCurrencyCode()),
|
||||||
],
|
],
|
||||||
IsoCurrencyProvider::getInstance()->getAvailableCurrencies()
|
ISOCurrencyProvider::getInstance()->getAvailableCurrencies()
|
||||||
));
|
));
|
||||||
|
|
||||||
return response()->json($currencies);
|
return response()->json($currencies);
|
||||||
|
|||||||
@@ -88,8 +88,6 @@ class InvitationController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Remove a pending invitation
|
* Remove a pending invitation
|
||||||
*
|
*
|
||||||
* This revokes the invitation: the link in the invitation email stops working. Find the invitation ID with `GET /organizations/{organization}/invitations`.
|
|
||||||
*
|
|
||||||
* @throws AuthorizationException
|
* @throws AuthorizationException
|
||||||
*
|
*
|
||||||
* @operationId removeInvitation
|
* @operationId removeInvitation
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ use Illuminate\Http\JsonResponse;
|
|||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
class MemberController extends Controller
|
class MemberController extends Controller
|
||||||
{
|
{
|
||||||
@@ -145,12 +144,9 @@ class MemberController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Merge one member into another
|
* Merge one member into another
|
||||||
*
|
*
|
||||||
* Only placeholder members (for example people created by an import) can be merged. All time entries and other data of the placeholder
|
|
||||||
* are reassigned to the member given in `member_id`, and the placeholder is removed. Find both member IDs with `GET /organizations/{organization}/members`.
|
|
||||||
*
|
|
||||||
* @throws AuthorizationException
|
* @throws AuthorizationException
|
||||||
* @throws OnlyPlaceholdersCanBeMergedIntoAnotherMember
|
* @throws OnlyPlaceholdersCanBeMergedIntoAnotherMember
|
||||||
* @throws Throwable
|
* @throws \Throwable
|
||||||
*
|
*
|
||||||
* @operationId mergeMember
|
* @operationId mergeMember
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -71,8 +71,6 @@ class ReportController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Create report
|
* Create report
|
||||||
*
|
*
|
||||||
* A report is a saved set of filters. Set `is_public` to `true` to share it: the response then contains the `shareable_link` that can be opened without logging in.
|
|
||||||
*
|
|
||||||
* @throws AuthorizationException
|
* @throws AuthorizationException
|
||||||
*
|
*
|
||||||
* @operationId createReport
|
* @operationId createReport
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ use Illuminate\Support\Facades\DB;
|
|||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use LogicException;
|
|
||||||
use Maatwebsite\Excel\Facades\Excel;
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
use Spatie\TemporaryDirectory\TemporaryDirectory;
|
use Spatie\TemporaryDirectory\TemporaryDirectory;
|
||||||
|
|
||||||
@@ -109,14 +108,9 @@ class TimeEntryController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Get time entries in organization
|
* Get time entries in organization
|
||||||
*
|
*
|
||||||
* Without a member filter this returns the time entries of all members of the organization (for users who may view all time entries, such as owners and admins), not only your own.
|
* If you only need time entries for a specific user, you can filter by `member_id`.
|
||||||
* To get only your own time entries, pass your member ID as `member_id`. Your member ID is the `id` returned for this organization by `GET /v1/users/me/memberships`; it is not your user ID.
|
|
||||||
* Users with the permission `time-entries:view:own` can only use this endpoint with their own member ID in the member_id filter.
|
* Users with the permission `time-entries:view:own` can only use this endpoint with their own member ID in the member_id filter.
|
||||||
*
|
*
|
||||||
* The `start` and `end` filters both apply to the start time of an entry, in UTC. Convert the user's local day boundaries to UTC first.
|
|
||||||
* Results are paginated with `limit` (default 100, max 500) and `offset`; check `meta.total` and fetch further pages when needed.
|
|
||||||
* To find the running timer, use `active=true` (or `GET /v1/users/me/time-entries/active`).
|
|
||||||
*
|
|
||||||
* @return TimeEntryCollection<TimeEntryResource>
|
* @return TimeEntryCollection<TimeEntryResource>
|
||||||
*
|
*
|
||||||
* @throws AuthorizationException
|
* @throws AuthorizationException
|
||||||
@@ -196,9 +190,8 @@ class TimeEntryController extends Controller
|
|||||||
$roundingMinutes = $canAccessPremiumFeatures ? $request->getRoundingMinutes() : null;
|
$roundingMinutes = $canAccessPremiumFeatures ? $request->getRoundingMinutes() : null;
|
||||||
if ($roundingType !== null && $roundingMinutes !== null) {
|
if ($roundingType !== null && $roundingMinutes !== null) {
|
||||||
$select = array_diff($select, ['start', 'end']);
|
$select = array_diff($select, ['start', 'end']);
|
||||||
// These SQL fragments are generated from a rounding enum and validated minute count.
|
$select[] = DB::raw(app(TimeEntryService::class)->getStartSelectRawForRounding($roundingType, $roundingMinutes).' as start');
|
||||||
$select[] = DB::raw(app(TimeEntryService::class)->getStartSelectRawForRounding($roundingType, $roundingMinutes).' as start'); // @phpstan-ignore argument.type
|
$select[] = DB::raw(app(TimeEntryService::class)->getEndSelectRawForRounding($roundingType, $roundingMinutes).' as end');
|
||||||
$select[] = DB::raw(app(TimeEntryService::class)->getEndSelectRawForRounding($roundingType, $roundingMinutes).' as end'); // @phpstan-ignore argument.type
|
|
||||||
}
|
}
|
||||||
$timeEntriesQuery = TimeEntry::query()
|
$timeEntriesQuery = TimeEntry::query()
|
||||||
->whereBelongsTo($organization, 'organization')
|
->whereBelongsTo($organization, 'organization')
|
||||||
@@ -272,7 +265,7 @@ class TimeEntryController extends Controller
|
|||||||
}
|
}
|
||||||
$viewFile = file_get_contents(resource_path('views/reports/time-entry-index/pdf.blade.php'));
|
$viewFile = file_get_contents(resource_path('views/reports/time-entry-index/pdf.blade.php'));
|
||||||
if ($viewFile === false) {
|
if ($viewFile === false) {
|
||||||
throw new LogicException('View file not found');
|
throw new \LogicException('View file not found');
|
||||||
}
|
}
|
||||||
$timeEntriesAggregateQuery = $this->getTimeEntriesAggregateQuery($organization, $request, $memberFilter);
|
$timeEntriesAggregateQuery = $this->getTimeEntriesAggregateQuery($organization, $request, $memberFilter);
|
||||||
$aggregatedData = $timeEntryAggregationService->getAggregatedTimeEntries(
|
$aggregatedData = $timeEntryAggregationService->getAggregatedTimeEntries(
|
||||||
@@ -300,7 +293,7 @@ class TimeEntryController extends Controller
|
|||||||
]);
|
]);
|
||||||
$footerViewFile = file_get_contents(resource_path('views/reports/time-entry-index/pdf-footer.blade.php'));
|
$footerViewFile = file_get_contents(resource_path('views/reports/time-entry-index/pdf-footer.blade.php'));
|
||||||
if ($footerViewFile === false) {
|
if ($footerViewFile === false) {
|
||||||
throw new LogicException('View file not found');
|
throw new \LogicException('View file not found');
|
||||||
}
|
}
|
||||||
$footerHtml = Blade::render($footerViewFile);
|
$footerHtml = Blade::render($footerViewFile);
|
||||||
if ($debug) {
|
if ($debug) {
|
||||||
@@ -356,11 +349,6 @@ class TimeEntryController extends Controller
|
|||||||
* The parameters `group` and `sub_group` allow you to group the time entries by different criteria.
|
* The parameters `group` and `sub_group` allow you to group the time entries by different criteria.
|
||||||
* If the group parameters are all set to `null` or are all missing, the endpoint will aggregate all filtered time entries.
|
* If the group parameters are all set to `null` or are all missing, the endpoint will aggregate all filtered time entries.
|
||||||
*
|
*
|
||||||
* Durations are returned in `seconds` (divide by 3600 for hours) and amounts in `cost` as cents in the organization's currency (divide by 100 for money).
|
|
||||||
* Filter by member with `member_id`. Your member ID is the `id` returned for this organization by `GET /v1/users/me/memberships`; it is not your user ID.
|
|
||||||
* Array filters use the query format `client_ids[]=<id>`.
|
|
||||||
* Example: billable hours per client for a period: `group=client&billable=true&start=...&end=...`.
|
|
||||||
*
|
|
||||||
* @operationId getAggregatedTimeEntries
|
* @operationId getAggregatedTimeEntries
|
||||||
*
|
*
|
||||||
* @return array{
|
* @return array{
|
||||||
@@ -507,7 +495,7 @@ class TimeEntryController extends Controller
|
|||||||
]);
|
]);
|
||||||
$viewFile = file_get_contents(resource_path('views/reports/time-entry-aggregate/pdf.blade.php'));
|
$viewFile = file_get_contents(resource_path('views/reports/time-entry-aggregate/pdf.blade.php'));
|
||||||
if ($viewFile === false) {
|
if ($viewFile === false) {
|
||||||
throw new LogicException('View file not found');
|
throw new \LogicException('View file not found');
|
||||||
}
|
}
|
||||||
$html = Blade::render($viewFile, [
|
$html = Blade::render($viewFile, [
|
||||||
'aggregatedData' => $aggregatedData,
|
'aggregatedData' => $aggregatedData,
|
||||||
@@ -524,7 +512,7 @@ class TimeEntryController extends Controller
|
|||||||
]);
|
]);
|
||||||
$footerViewFile = file_get_contents(resource_path('views/reports/time-entry-aggregate/pdf-footer.blade.php'));
|
$footerViewFile = file_get_contents(resource_path('views/reports/time-entry-aggregate/pdf-footer.blade.php'));
|
||||||
if ($footerViewFile === false) {
|
if ($footerViewFile === false) {
|
||||||
throw new LogicException('View file not found');
|
throw new \LogicException('View file not found');
|
||||||
}
|
}
|
||||||
$footerHtml = Blade::render($footerViewFile);
|
$footerHtml = Blade::render($footerViewFile);
|
||||||
if ($debug) {
|
if ($debug) {
|
||||||
@@ -594,12 +582,6 @@ class TimeEntryController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Create time entry
|
* Create time entry
|
||||||
*
|
*
|
||||||
* `billable` is not taken from the project. To match the web app, set it to the project's `is_billable` value (or `false` without a project).
|
|
||||||
*
|
|
||||||
* A member can only have one running time entry (an entry with `end` set to `null`). Creating a running entry while another one runs fails with `time_entry_still_running`.
|
|
||||||
* To start a new timer, first stop the running entry by updating its `end` to the current time, then create the new entry.
|
|
||||||
* To log past work, send both `start` and `end` (UTC). Create one entry per block of work.
|
|
||||||
*
|
|
||||||
* @throws AuthorizationException
|
* @throws AuthorizationException
|
||||||
* @throws TimeEntryStillRunningApiException
|
* @throws TimeEntryStillRunningApiException
|
||||||
*
|
*
|
||||||
@@ -650,8 +632,6 @@ class TimeEntryController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Update time entry
|
* Update time entry
|
||||||
*
|
*
|
||||||
* To stop a running timer, set `end` to the stop time (UTC). Times the user gives in their own timezone must be converted to UTC first.
|
|
||||||
*
|
|
||||||
* @throws AuthorizationException|TimeEntryCanNotBeRestartedApiException
|
* @throws AuthorizationException|TimeEntryCanNotBeRestartedApiException
|
||||||
*
|
*
|
||||||
* @operationId updateTimeEntry
|
* @operationId updateTimeEntry
|
||||||
@@ -720,10 +700,6 @@ class TimeEntryController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Update multiple time entries
|
* Update multiple time entries
|
||||||
*
|
*
|
||||||
* Applies the same `changes` to every entry in `ids`. To find the IDs, list entries with `GET /organizations/{organization}/time-entries`
|
|
||||||
* (filtered by `member_id`, the project and the other criteria), then send their IDs here.
|
|
||||||
* When `changes.project_id` moves entries to another project, also set `changes.task_id` to a task of the new project or to `null`, because tasks belong to a project.
|
|
||||||
*
|
|
||||||
* @operationId updateMultipleTimeEntries
|
* @operationId updateMultipleTimeEntries
|
||||||
*
|
*
|
||||||
* @throws AuthorizationException
|
* @throws AuthorizationException
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace App\Http\Controllers\Web;
|
|||||||
|
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
use Brick\Money\Currency;
|
use Brick\Money\Currency;
|
||||||
use Brick\Money\IsoCurrencyProvider;
|
use Brick\Money\ISOCurrencyProvider;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@@ -53,7 +53,7 @@ class OrganizationController extends Controller
|
|||||||
],
|
],
|
||||||
'currencies' => array_map(function (Currency $currency): string {
|
'currencies' => array_map(function (Currency $currency): string {
|
||||||
return $currency->getName();
|
return $currency->getName();
|
||||||
}, IsoCurrencyProvider::getInstance()->getAvailableCurrencies()),
|
}, ISOCurrencyProvider::getInstance()->getAvailableCurrencies()),
|
||||||
'permissions' => [
|
'permissions' => [
|
||||||
'canDeleteTeam' => $this->hasPermission($organization, 'organizations:delete'),
|
'canDeleteTeam' => $this->hasPermission($organization, 'organizations:delete'),
|
||||||
'canUpdateTeam' => $this->hasPermission($organization, 'organizations:update'),
|
'canUpdateTeam' => $this->hasPermission($organization, 'organizations:update'),
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ use Inertia\Inertia;
|
|||||||
use Inertia\Response;
|
use Inertia\Response;
|
||||||
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
|
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
|
||||||
use Laravel\Fortify\Features;
|
use Laravel\Fortify\Features;
|
||||||
use stdClass;
|
|
||||||
|
|
||||||
class UserProfileController extends Controller
|
class UserProfileController extends Controller
|
||||||
{
|
{
|
||||||
@@ -104,7 +103,7 @@ class UserProfileController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Get the current sessions.
|
* Get the current sessions.
|
||||||
*
|
*
|
||||||
* @return array<int, object{agent: array{is_desktop: bool, platform: string|null, browser: string|null}, ip_address: string, is_current_device: bool, last_active: string}&stdClass>
|
* @return array<int, object{agent: array{is_desktop: bool, platform: string|null, browser: string|null}, ip_address: string, is_current_device: bool, last_active: string}&\stdClass>
|
||||||
*/
|
*/
|
||||||
public function sessions(Request $request): array
|
public function sessions(Request $request): array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ use App\Http\Middleware\EnsureEmailIsVerified;
|
|||||||
use App\Http\Middleware\ForceHttps;
|
use App\Http\Middleware\ForceHttps;
|
||||||
use App\Http\Middleware\ForceJsonResponse;
|
use App\Http\Middleware\ForceJsonResponse;
|
||||||
use App\Http\Middleware\HandleInertiaRequests;
|
use App\Http\Middleware\HandleInertiaRequests;
|
||||||
use App\Http\Middleware\PreventRequestForgery;
|
|
||||||
use App\Http\Middleware\PreventRequestsDuringMaintenance;
|
use App\Http\Middleware\PreventRequestsDuringMaintenance;
|
||||||
use App\Http\Middleware\RedirectIfAuthenticated;
|
use App\Http\Middleware\RedirectIfAuthenticated;
|
||||||
use App\Http\Middleware\ShareInertiaData;
|
use App\Http\Middleware\ShareInertiaData;
|
||||||
@@ -19,6 +18,7 @@ use App\Http\Middleware\TrimStrings;
|
|||||||
use App\Http\Middleware\TrustHosts;
|
use App\Http\Middleware\TrustHosts;
|
||||||
use App\Http\Middleware\TrustProxies;
|
use App\Http\Middleware\TrustProxies;
|
||||||
use App\Http\Middleware\ValidateSignature;
|
use App\Http\Middleware\ValidateSignature;
|
||||||
|
use App\Http\Middleware\VerifyCsrfToken;
|
||||||
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
|
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
|
||||||
use Illuminate\Auth\Middleware\Authorize;
|
use Illuminate\Auth\Middleware\Authorize;
|
||||||
use Illuminate\Auth\Middleware\RequirePassword;
|
use Illuminate\Auth\Middleware\RequirePassword;
|
||||||
@@ -68,7 +68,7 @@ class Kernel extends HttpKernel
|
|||||||
AddQueuedCookiesToResponse::class,
|
AddQueuedCookiesToResponse::class,
|
||||||
StartSession::class,
|
StartSession::class,
|
||||||
ShareErrorsFromSession::class,
|
ShareErrorsFromSession::class,
|
||||||
PreventRequestForgery::class,
|
VerifyCsrfToken::class,
|
||||||
SubstituteBindings::class,
|
SubstituteBindings::class,
|
||||||
HandleInertiaRequests::class,
|
HandleInertiaRequests::class,
|
||||||
ShareInertiaData::class,
|
ShareInertiaData::class,
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use Filament\Http\Middleware\Authenticate as Middleware;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class AuthenticateFilamentPanel extends Middleware
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The admin panel has no login page of its own (it shares the app's
|
|
||||||
* session-based login), so Filament::getLoginUrl() always returns null.
|
|
||||||
* Fall back to the app's login route so guests are redirected instead
|
|
||||||
* of receiving an empty 401 response.
|
|
||||||
*/
|
|
||||||
protected function redirectTo($request): ?string
|
|
||||||
{
|
|
||||||
return parent::redirectTo($request) ?? ($request->expectsJson() ? null : route('login'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ use App\Models\Organization;
|
|||||||
use App\Service\BillingContract;
|
use App\Service\BillingContract;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use LogicException;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
class CheckOrganizationBlocked
|
class CheckOrganizationBlocked
|
||||||
@@ -26,7 +25,7 @@ class CheckOrganizationBlocked
|
|||||||
$organization = $request->route('organization');
|
$organization = $request->route('organization');
|
||||||
|
|
||||||
if (! ($organization instanceof Organization)) {
|
if (! ($organization instanceof Organization)) {
|
||||||
throw new LogicException('The organization must be loaded before this middleware.');
|
throw new \LogicException('The organization must be loaded before this middleware.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var BillingContract $billing */
|
/** @var BillingContract $billing */
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery as Middleware;
|
|
||||||
|
|
||||||
class PreventRequestForgery extends Middleware
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The URIs that should be excluded.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
|
||||||
*/
|
|
||||||
protected $except = [
|
|
||||||
//
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Http\Middleware\TrustHosts as BaseTrustHosts;
|
use Illuminate\Http\Middleware\TrustHosts as BaseTrustHosts;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
@@ -40,7 +39,7 @@ class TrustHosts extends BaseTrustHosts
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Closure(Request):Response $next
|
* @param \Closure(Request): Response $next
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, $next)
|
public function handle(Request $request, $next)
|
||||||
{
|
{
|
||||||
|
|||||||
19
app/Http/Middleware/VerifyCsrfToken.php
Normal file
19
app/Http/Middleware/VerifyCsrfToken.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
||||||
|
|
||||||
|
class VerifyCsrfToken extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The URIs that should be excluded from CSRF verification.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -27,7 +27,6 @@ class MemberUpdateRequest extends BaseFormRequest
|
|||||||
'string',
|
'string',
|
||||||
Rule::enum(Role::class),
|
Rule::enum(Role::class),
|
||||||
],
|
],
|
||||||
// Billable rate in cents per hour (example: 8000 means 80.00 in the organization's currency)
|
|
||||||
'billable_rate' => array_merge(
|
'billable_rate' => array_merge(
|
||||||
[
|
[
|
||||||
'nullable',
|
'nullable',
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ class OrganizationUpdateRequest extends BaseFormRequest
|
|||||||
'string',
|
'string',
|
||||||
new CurrencyRule,
|
new CurrencyRule,
|
||||||
],
|
],
|
||||||
// Billable rate in cents per hour (example: 8000 means 80.00 in the organization's currency)
|
|
||||||
'billable_rate' => array_merge(
|
'billable_rate' => array_merge(
|
||||||
[
|
[
|
||||||
'nullable',
|
'nullable',
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ class ProjectStoreRequest extends BaseFormRequest
|
|||||||
'required',
|
'required',
|
||||||
'boolean',
|
'boolean',
|
||||||
],
|
],
|
||||||
// Billable rate in cents per hour (example: 8000 means 80.00 in the organization's currency)
|
|
||||||
'billable_rate' => array_merge(
|
'billable_rate' => array_merge(
|
||||||
[
|
[
|
||||||
'nullable',
|
'nullable',
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ class ProjectUpdateRequest extends BaseFormRequest
|
|||||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||||
})->uuid(),
|
})->uuid(),
|
||||||
],
|
],
|
||||||
// Billable rate in cents per hour (example: 8000 means 80.00 in the organization's currency)
|
|
||||||
'billable_rate' => array_merge([
|
'billable_rate' => array_merge([
|
||||||
'nullable',
|
'nullable',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ class ProjectMemberStoreRequest extends BaseFormRequest
|
|||||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||||
})->uuid(),
|
})->uuid(),
|
||||||
],
|
],
|
||||||
// Billable rate in cents per hour (example: 8000 means 80.00 in the organization's currency)
|
|
||||||
'billable_rate' => array_merge(
|
'billable_rate' => array_merge(
|
||||||
[
|
[
|
||||||
'nullable',
|
'nullable',
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ class ProjectMemberUpdateRequest extends BaseFormRequest
|
|||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
// Billable rate in cents per hour (example: 8000 means 80.00 in the organization's currency)
|
|
||||||
'billable_rate' => array_merge(
|
'billable_rate' => array_merge(
|
||||||
[
|
[
|
||||||
'nullable',
|
'nullable',
|
||||||
|
|||||||
@@ -13,13 +13,11 @@ use App\Enums\Weekday;
|
|||||||
use App\Http\Requests\V1\BaseFormRequest;
|
use App\Http\Requests\V1\BaseFormRequest;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
use App\Service\TimeEntryFilter;
|
use App\Service\TimeEntryFilter;
|
||||||
use Closure;
|
|
||||||
use Illuminate\Contracts\Validation\Rule as LegacyValidationRule;
|
use Illuminate\Contracts\Validation\Rule as LegacyValidationRule;
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use LogicException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property Organization $organization Organization from model binding
|
* @property Organization $organization Organization from model binding
|
||||||
@@ -29,7 +27,7 @@ class ReportStoreRequest extends BaseFormRequest
|
|||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, array<string|ValidationRule|LegacyValidationRule|Closure>>
|
* @return array<string, array<string|ValidationRule|LegacyValidationRule|\Closure>>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
@@ -87,7 +85,7 @@ class ReportStoreRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'properties.client_ids.*' => [
|
'properties.client_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -103,7 +101,7 @@ class ReportStoreRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'properties.project_ids.*' => [
|
'properties.project_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -119,7 +117,7 @@ class ReportStoreRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'properties.tag_ids.*' => [
|
'properties.tag_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -139,7 +137,7 @@ class ReportStoreRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'properties.task_ids.*' => [
|
'properties.task_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -215,7 +213,7 @@ class ReportStoreRequest extends BaseFormRequest
|
|||||||
{
|
{
|
||||||
$start = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('properties.start'));
|
$start = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('properties.start'));
|
||||||
if ($start === null) {
|
if ($start === null) {
|
||||||
throw new LogicException('Start date validation is not working');
|
throw new \LogicException('Start date validation is not working');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $start;
|
return $start;
|
||||||
@@ -225,7 +223,7 @@ class ReportStoreRequest extends BaseFormRequest
|
|||||||
{
|
{
|
||||||
$end = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('properties.end'));
|
$end = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('properties.end'));
|
||||||
if ($end === null) {
|
if ($end === null) {
|
||||||
throw new LogicException('End date validation is not working');
|
throw new \LogicException('End date validation is not working');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $end;
|
return $end;
|
||||||
|
|||||||
@@ -19,13 +19,11 @@ use App\Models\Tag;
|
|||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Service\TimeEntryFilter;
|
use App\Service\TimeEntryFilter;
|
||||||
use Closure;
|
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Korridor\LaravelModelValidationRules\Rules\ExistsEloquent;
|
use Korridor\LaravelModelValidationRules\Rules\ExistsEloquent;
|
||||||
use LogicException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property Organization $organization
|
* @property Organization $organization
|
||||||
@@ -35,7 +33,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
|||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|Closure>>
|
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|\Closure>>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
@@ -99,7 +97,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'project_ids.*' => [
|
'project_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -116,7 +114,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'client_ids.*' => [
|
'client_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -133,7 +131,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'tag_ids.*' => [
|
'tag_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -154,7 +152,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'task_ids.*' => [
|
'task_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -238,7 +236,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
|||||||
{
|
{
|
||||||
$start = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('start'), 'UTC');
|
$start = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('start'), 'UTC');
|
||||||
if ($start === null) {
|
if ($start === null) {
|
||||||
throw new LogicException('Start date validation is not working');
|
throw new \LogicException('Start date validation is not working');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $start;
|
return $start;
|
||||||
@@ -248,7 +246,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
|||||||
{
|
{
|
||||||
$end = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('end'), 'UTC');
|
$end = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('end'), 'UTC');
|
||||||
if ($end === null) {
|
if ($end === null) {
|
||||||
throw new LogicException('End date validation is not working');
|
throw new \LogicException('End date validation is not working');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $end;
|
return $end;
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ use App\Models\Tag;
|
|||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Service\TimeEntryFilter;
|
use App\Service\TimeEntryFilter;
|
||||||
use Closure;
|
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
@@ -32,7 +31,7 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
|||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|Closure>>
|
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|\Closure>>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
@@ -84,7 +83,7 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'project_ids.*' => [
|
'project_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -101,7 +100,7 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'client_ids.*' => [
|
'client_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -118,7 +117,7 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'tag_ids.*' => [
|
'tag_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -139,7 +138,7 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'task_ids.*' => [
|
'task_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,11 @@ use App\Models\Project;
|
|||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
use App\Service\TimeEntryFilter;
|
use App\Service\TimeEntryFilter;
|
||||||
use Closure;
|
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Korridor\LaravelModelValidationRules\Rules\ExistsEloquent;
|
use Korridor\LaravelModelValidationRules\Rules\ExistsEloquent;
|
||||||
use LogicException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property Organization $organization
|
* @property Organization $organization
|
||||||
@@ -31,7 +29,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
|||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|Closure>>
|
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|\Closure>>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
@@ -70,7 +68,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
|||||||
],
|
],
|
||||||
'client_ids.*' => [
|
'client_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -87,7 +85,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
|||||||
],
|
],
|
||||||
'project_ids.*' => [
|
'project_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -104,7 +102,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
|||||||
],
|
],
|
||||||
'tag_ids.*' => [
|
'tag_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -125,7 +123,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
|||||||
],
|
],
|
||||||
'task_ids.*' => [
|
'task_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -202,7 +200,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
|||||||
{
|
{
|
||||||
$start = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('start'), 'UTC');
|
$start = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('start'), 'UTC');
|
||||||
if ($start === null) {
|
if ($start === null) {
|
||||||
throw new LogicException('Start date validation is not working');
|
throw new \LogicException('Start date validation is not working');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $start;
|
return $start;
|
||||||
@@ -212,7 +210,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
|||||||
{
|
{
|
||||||
$end = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('end'), 'UTC');
|
$end = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('end'), 'UTC');
|
||||||
if ($end === null) {
|
if ($end === null) {
|
||||||
throw new LogicException('End date validation is not working');
|
throw new \LogicException('End date validation is not working');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $end;
|
return $end;
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ use App\Models\Project;
|
|||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
use App\Service\TimeEntryFilter;
|
use App\Service\TimeEntryFilter;
|
||||||
use Closure;
|
|
||||||
use Illuminate\Contracts\Validation\Rule as RuleContract;
|
use Illuminate\Contracts\Validation\Rule as RuleContract;
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@@ -30,12 +29,12 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
|||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, array<string|ValidationRule|RuleContract|Closure>>
|
* @return array<string, array<string|ValidationRule|RuleContract|\Closure>>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
// Filter by member ID. Without it, users who may view all time entries (owners, admins) get the entries of every member; pass your own member ID (from GET /v1/users/me/memberships) to get only yours
|
// Filter by member ID
|
||||||
'member_id' => [
|
'member_id' => [
|
||||||
'string',
|
'string',
|
||||||
ExistsEloquent::make(Member::class, null, function (Builder $builder): Builder {
|
ExistsEloquent::make(Member::class, null, function (Builder $builder): Builder {
|
||||||
@@ -62,7 +61,7 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'client_ids.*' => [
|
'client_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -79,7 +78,7 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'project_ids.*' => [
|
'project_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -96,7 +95,7 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'tag_ids.*' => [
|
'tag_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -117,7 +116,7 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
|||||||
],
|
],
|
||||||
'task_ids.*' => [
|
'task_ids.*' => [
|
||||||
'string',
|
'string',
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -155,7 +154,7 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
|||||||
'string',
|
'string',
|
||||||
Rule::enum(TimeEntryType::class),
|
Rule::enum(TimeEntryType::class),
|
||||||
],
|
],
|
||||||
// Limit the number of returned time entries (default: 100)
|
// Limit the number of returned time entries (default: 150)
|
||||||
'limit' => [
|
'limit' => [
|
||||||
'integer',
|
'integer',
|
||||||
'min:1',
|
'min:1',
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use App\Models\Project;
|
|||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
use App\Service\PermissionStore;
|
use App\Service\PermissionStore;
|
||||||
use Closure;
|
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
@@ -27,12 +26,12 @@ class TimeEntryStoreRequest extends BaseFormRequest
|
|||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, array<string|Closure|ValidationRule|\Illuminate\Contracts\Validation\Rule>>
|
* @return array<string, array<string|\Closure|ValidationRule|\Illuminate\Contracts\Validation\Rule>>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
// ID of the organization member that the time entry should belong to (a member ID from GET /v1/users/me/memberships or the members list, not a user ID)
|
// ID of the organization member that the time entry should belong to
|
||||||
'member_id' => [
|
'member_id' => [
|
||||||
'required',
|
'required',
|
||||||
'string',
|
'string',
|
||||||
@@ -86,7 +85,7 @@ class TimeEntryStoreRequest extends BaseFormRequest
|
|||||||
'date_format:Y-m-d\TH:i:s\Z',
|
'date_format:Y-m-d\TH:i:s\Z',
|
||||||
'after_or_equal:start',
|
'after_or_equal:start',
|
||||||
],
|
],
|
||||||
// Whether time entry is billable. Not derived from the project: set it to the project's is_billable value to match the web app
|
// Whether time entry is billable
|
||||||
'billable' => [
|
'billable' => [
|
||||||
'required',
|
'required',
|
||||||
'boolean',
|
'boolean',
|
||||||
@@ -95,7 +94,7 @@ class TimeEntryStoreRequest extends BaseFormRequest
|
|||||||
// Type of the time entry (work time or a break)
|
// Type of the time entry (work time or a break)
|
||||||
'type' => [
|
'type' => [
|
||||||
Rule::enum(TimeEntryType::class),
|
Rule::enum(TimeEntryType::class),
|
||||||
function (string $attribute, mixed $value, Closure $fail): void {
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
if ($value === TimeEntryType::Break->value && ! $this->organization->breaks_enabled) {
|
if ($value === TimeEntryType::Break->value && ! $this->organization->breaks_enabled) {
|
||||||
$fail('Breaks are disabled for this organization.');
|
$fail('Breaks are disabled for this organization.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ use App\Models\Tag;
|
|||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
use App\Models\TimeEntry;
|
use App\Models\TimeEntry;
|
||||||
use App\Service\PermissionStore;
|
use App\Service\PermissionStore;
|
||||||
use Closure;
|
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
@@ -30,7 +29,7 @@ class TimeEntryUpdateRequest extends BaseFormRequest
|
|||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, array<string|Closure|ValidationRule|\Illuminate\Contracts\Validation\Rule|ProhibitedIf|ConditionalRules>>
|
* @return array<string, array<string|\Closure|ValidationRule|\Illuminate\Contracts\Validation\Rule|ProhibitedIf|ConditionalRules>>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
@@ -106,7 +105,7 @@ class TimeEntryUpdateRequest extends BaseFormRequest
|
|||||||
// Type of the time entry (work time or a break)
|
// Type of the time entry (work time or a break)
|
||||||
'type' => [
|
'type' => [
|
||||||
Rule::enum(TimeEntryType::class),
|
Rule::enum(TimeEntryType::class),
|
||||||
function (string $attribute, mixed $value, Closure $fail) use ($timeEntry): void {
|
function (string $attribute, mixed $value, \Closure $fail) use ($timeEntry): void {
|
||||||
// While breaks are disabled, entries that already are breaks may stay
|
// While breaks are disabled, entries that already are breaks may stay
|
||||||
// breaks, but converting a work entry to a break is not allowed.
|
// breaks, but converting a work entry to a break is not allowed.
|
||||||
if ($value === TimeEntryType::Break->value
|
if ($value === TimeEntryType::Break->value
|
||||||
|
|||||||
@@ -4,19 +4,15 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Models\Concerns\AuditableWithoutOwner;
|
|
||||||
use Database\Factories\AuditFactory;
|
use Database\Factories\AuditFactory;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use OwenIt\Auditing\Models\Audit as PackageAuditModel;
|
use OwenIt\Auditing\Models\Audit as PackageAuditModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
* @property string|null $actor_type
|
* @property string|null $user_type
|
||||||
* @property string|null $actor_id
|
* @property string|null $user_id
|
||||||
* @property string $event
|
* @property string $event
|
||||||
* @property string $auditable_type
|
* @property string $auditable_type
|
||||||
* @property string $auditable_id
|
* @property string $auditable_id
|
||||||
@@ -26,71 +22,13 @@ use OwenIt\Auditing\Models\Audit as PackageAuditModel;
|
|||||||
* @property string|null $ip_address
|
* @property string|null $ip_address
|
||||||
* @property string|null $user_agent
|
* @property string|null $user_agent
|
||||||
* @property string|null $tags
|
* @property string|null $tags
|
||||||
* @property string|null $owner_user_id
|
|
||||||
* @property string|null $owner_organization_id
|
|
||||||
* @property Carbon|null $created_at
|
* @property Carbon|null $created_at
|
||||||
* @property Carbon|null $updated_at
|
* @property Carbon|null $updated_at
|
||||||
* @property-read User|null $ownerUser
|
|
||||||
* @property-read Organization|null $ownerOrganization
|
|
||||||
*
|
*
|
||||||
* @method static AuditFactory factory()
|
* @method static AuditFactory factory()
|
||||||
* @method static Builder<Audit> whereMissingOwner()
|
|
||||||
*/
|
*/
|
||||||
class Audit extends PackageAuditModel
|
class Audit extends PackageAuditModel
|
||||||
{
|
{
|
||||||
/** @use HasFactory<AuditFactory> */
|
/** @use HasFactory<AuditFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
/**
|
|
||||||
* @return BelongsTo<User, $this>
|
|
||||||
*/
|
|
||||||
public function ownerUser(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(User::class, 'owner_user_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return BelongsTo<Organization, $this>
|
|
||||||
*/
|
|
||||||
public function ownerOrganization(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Organization::class, 'owner_organization_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether audits of the given auditable type intentionally have no owner (see AuditableWithoutOwner).
|
|
||||||
*/
|
|
||||||
public static function isAuditableTypeWithoutOwner(string $auditableType): bool
|
|
||||||
{
|
|
||||||
$modelClass = Relation::getMorphedModel($auditableType) ?? $auditableType;
|
|
||||||
|
|
||||||
return is_subclass_of($modelClass, AuditableWithoutOwner::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auditable types (morph aliases) whose audits intentionally have no owner (see AuditableWithoutOwner).
|
|
||||||
*
|
|
||||||
* @return array<int, string>
|
|
||||||
*/
|
|
||||||
public static function getAuditableTypesWithoutOwner(): array
|
|
||||||
{
|
|
||||||
return collect(Relation::morphMap())
|
|
||||||
->filter(fn (string $modelClass): bool => is_subclass_of($modelClass, AuditableWithoutOwner::class))
|
|
||||||
->keys()
|
|
||||||
->values()
|
|
||||||
->all();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Audits that have neither an owner organization nor an owner user, although their auditable type should have one.
|
|
||||||
* These are audits whose owner no longer exists or could not be determined (yet).
|
|
||||||
*
|
|
||||||
* @param Builder<Audit> $builder
|
|
||||||
*/
|
|
||||||
public function scopeWhereMissingOwner(Builder $builder): void
|
|
||||||
{
|
|
||||||
$builder->whereNull('owner_organization_id')
|
|
||||||
->whereNull('owner_user_id')
|
|
||||||
->whereNotIn('auditable_type', self::getAuditableTypesWithoutOwner());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Models\Concerns;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks an auditable model whose audits are owned by the owner of its parent model,
|
|
||||||
* because the model itself has no organization_id (for example a project member belongs to an organization via its project).
|
|
||||||
* The parent model has to have an organization_id column.
|
|
||||||
* This is used to set the owner of new audits (CustomAuditable) and to backfill the owner of existing audits.
|
|
||||||
*/
|
|
||||||
interface AuditableThroughParent
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @return BelongsTo<covariant Model, covariant Model>
|
|
||||||
*/
|
|
||||||
public function getAuditParentRelation(): BelongsTo;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Models\Concerns;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks an auditable model whose audits intentionally have no owner (neither an organization nor a user),
|
|
||||||
* either because the model belongs to neither of them, or because the model and its audits have to be kept
|
|
||||||
* when its organization or user is deleted (for example billing records).
|
|
||||||
* Audits of these models are not deleted together with an organization or user,
|
|
||||||
* and are not considered as missing an owner (for example by the audit backfill command).
|
|
||||||
*/
|
|
||||||
interface AuditableWithoutOwner {}
|
|
||||||
@@ -4,8 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Models\Concerns;
|
namespace App\Models\Concerns;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Support\Facades\Config;
|
|
||||||
use OwenIt\Auditing\Auditable;
|
use OwenIt\Auditing\Auditable;
|
||||||
|
|
||||||
trait CustomAuditable
|
trait CustomAuditable
|
||||||
@@ -21,92 +19,4 @@ trait CustomAuditable
|
|||||||
{
|
{
|
||||||
$this->auditEvents = [];
|
$this->auditEvents = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The organization that owns the audited model.
|
|
||||||
* The audits of the model are deleted (via foreign key cascade) when the organization is deleted.
|
|
||||||
*/
|
|
||||||
public function getAuditOwnerOrganizationId(): ?string
|
|
||||||
{
|
|
||||||
if ($this instanceof AuditableThroughParent) {
|
|
||||||
$relation = $this->getAuditParentRelation();
|
|
||||||
$parentId = $this->getAttributes()[$relation->getForeignKeyName()] ?? null;
|
|
||||||
if ($parentId === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: The loaded parent is only used if it still matches the foreign key and was loaded with the organization
|
|
||||||
$relationName = $relation->getRelationName();
|
|
||||||
if ($this->relationLoaded($relationName)) {
|
|
||||||
$parent = $this->getRelation($relationName);
|
|
||||||
if ($parent instanceof Model
|
|
||||||
&& $parent->getAttribute($relation->getOwnerKeyName()) === $parentId
|
|
||||||
&& array_key_exists('organization_id', $parent->getAttributes())) {
|
|
||||||
/** @var string|null $organizationId */
|
|
||||||
$organizationId = $parent->getAttributes()['organization_id'];
|
|
||||||
|
|
||||||
return $organizationId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var string|null $organizationId */
|
|
||||||
$organizationId = $relation->getRelated()->newQuery()
|
|
||||||
->toBase()
|
|
||||||
->where($relation->getOwnerKeyName(), $parentId)
|
|
||||||
->value('organization_id');
|
|
||||||
|
|
||||||
return $organizationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->getAttributes()['organization_id'] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The user that owns the audited model.
|
|
||||||
* The audits of the model are deleted (via foreign key cascade) when the user is deleted.
|
|
||||||
*/
|
|
||||||
public function getAuditOwnerUserId(): ?string
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Models that are the owner of their own audits can not record the deletion audit,
|
|
||||||
* since the audit would reference the already deleted model and therefore violate the foreign key.
|
|
||||||
*/
|
|
||||||
protected function isAuditOwnerOfItself(): bool
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<int|string, string>
|
|
||||||
*/
|
|
||||||
public function getAuditEvents(): array
|
|
||||||
{
|
|
||||||
$events = $this->auditEvents ?? Config::get('audit.events', [
|
|
||||||
'created',
|
|
||||||
'updated',
|
|
||||||
'deleted',
|
|
||||||
'restored',
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($this->isAuditOwnerOfItself()) {
|
|
||||||
$events = array_filter($events, fn (string $value, int|string $key): bool => (is_int($key) ? $value : $key) !== 'deleted', ARRAY_FILTER_USE_BOTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $events;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed> $data
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function transformAudit(array $data): array
|
|
||||||
{
|
|
||||||
$data['owner_organization_id'] = $this->getAuditOwnerOrganizationId();
|
|
||||||
$data['owner_user_id'] = $this->getAuditOwnerUserId();
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
|
use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
|
||||||
@@ -96,20 +97,10 @@ class Organization extends Model implements AuditableContract
|
|||||||
protected $attributes = [
|
protected $attributes = [
|
||||||
];
|
];
|
||||||
|
|
||||||
public function getAuditOwnerOrganizationId(): ?string
|
|
||||||
{
|
|
||||||
return $this->getKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function isAuditOwnerOfItself(): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the users that belong to the team.
|
* Get all the users that belong to the team.
|
||||||
*
|
*
|
||||||
* @return BelongsToMany<User, $this, Member, 'membership'>
|
* @return BelongsToMany<User, $this, Pivot, 'membership'>
|
||||||
*/
|
*/
|
||||||
public function users(): BelongsToMany
|
public function users(): BelongsToMany
|
||||||
{
|
{
|
||||||
@@ -142,7 +133,7 @@ class Organization extends Model implements AuditableContract
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return BelongsToMany<User, $this, Member, 'membership'>
|
* @return BelongsToMany<User, $this, Pivot, 'membership'>
|
||||||
*/
|
*/
|
||||||
public function realUsers(): BelongsToMany
|
public function realUsers(): BelongsToMany
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Models\Concerns\AuditableThroughParent;
|
|
||||||
use App\Models\Concerns\CustomAuditable;
|
use App\Models\Concerns\CustomAuditable;
|
||||||
use App\Models\Concerns\HasUuids;
|
use App\Models\Concerns\HasUuids;
|
||||||
use Database\Factories\ProjectMemberFactory;
|
use Database\Factories\ProjectMemberFactory;
|
||||||
@@ -30,7 +29,7 @@ use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
|
|||||||
* @method static Builder<ProjectMember> whereBelongsToOrganization(Organization $organization)
|
* @method static Builder<ProjectMember> whereBelongsToOrganization(Organization $organization)
|
||||||
* @method static ProjectMemberFactory factory()
|
* @method static ProjectMemberFactory factory()
|
||||||
*/
|
*/
|
||||||
class ProjectMember extends Model implements AuditableContract, AuditableThroughParent
|
class ProjectMember extends Model implements AuditableContract
|
||||||
{
|
{
|
||||||
use CustomAuditable;
|
use CustomAuditable;
|
||||||
|
|
||||||
@@ -83,12 +82,4 @@ class ProjectMember extends Model implements AuditableContract, AuditableThrough
|
|||||||
$query->whereBelongsTo($organization, 'organization');
|
$query->whereBelongsTo($organization, 'organization');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return BelongsTo<Project, $this>
|
|
||||||
*/
|
|
||||||
public function getAuditParentRelation(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->project();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
@@ -124,16 +125,6 @@ class User extends Authenticatable implements AuditableContract, FilamentUser, M
|
|||||||
'send_time_entry_still_running_email' => true,
|
'send_time_entry_still_running_email' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
public function getAuditOwnerUserId(): ?string
|
|
||||||
{
|
|
||||||
return $this->getKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function isAuditOwnerOfItself(): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the URL to the user's profile photo.
|
* Get the URL to the user's profile photo.
|
||||||
*
|
*
|
||||||
@@ -184,7 +175,7 @@ class User extends Authenticatable implements AuditableContract, FilamentUser, M
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return BelongsToMany<Organization, $this, Member, 'membership'>
|
* @return BelongsToMany<Organization, $this, Pivot, 'membership'>
|
||||||
*/
|
*/
|
||||||
public function organizations(): BelongsToMany
|
public function organizations(): BelongsToMany
|
||||||
{
|
{
|
||||||
@@ -199,7 +190,7 @@ class User extends Authenticatable implements AuditableContract, FilamentUser, M
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return BelongsToMany<Organization, $this, Member, 'membership'>
|
* @return BelongsToMany<Organization, $this, Pivot, 'membership'>
|
||||||
*/
|
*/
|
||||||
public function ownedOrganizations(): BelongsToMany
|
public function ownedOrganizations(): BelongsToMany
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ use Dedoc\Scramble\Scramble;
|
|||||||
use Dedoc\Scramble\Support\Generator\OpenApi;
|
use Dedoc\Scramble\Support\Generator\OpenApi;
|
||||||
use Dedoc\Scramble\Support\Generator\SecurityScheme;
|
use Dedoc\Scramble\Support\Generator\SecurityScheme;
|
||||||
use Dedoc\Scramble\Support\Generator\SecuritySchemes\OAuthFlow;
|
use Dedoc\Scramble\Support\Generator\SecuritySchemes\OAuthFlow;
|
||||||
use Filament\Schemas\Components\Section;
|
use Filament\Forms\Components\Section;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
|||||||
@@ -9,17 +9,17 @@ use App\Filament\Widgets\ServerOverview;
|
|||||||
use App\Filament\Widgets\TimeEntriesCreated;
|
use App\Filament\Widgets\TimeEntriesCreated;
|
||||||
use App\Filament\Widgets\TimeEntriesImported;
|
use App\Filament\Widgets\TimeEntriesImported;
|
||||||
use App\Filament\Widgets\UserRegistrations;
|
use App\Filament\Widgets\UserRegistrations;
|
||||||
use App\Http\Middleware\AuthenticateFilamentPanel;
|
use Filament\Http\Middleware\Authenticate;
|
||||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||||
use Filament\Navigation\NavigationGroup;
|
use Filament\Navigation\NavigationGroup;
|
||||||
use Filament\Pages\Dashboard;
|
use Filament\Pages;
|
||||||
use Filament\Panel;
|
use Filament\Panel;
|
||||||
use Filament\PanelProvider;
|
use Filament\PanelProvider;
|
||||||
use Filament\Support\Colors\Color;
|
use Filament\Support\Colors\Color;
|
||||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||||
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||||
use Illuminate\Session\Middleware\StartSession;
|
use Illuminate\Session\Middleware\StartSession;
|
||||||
@@ -41,7 +41,7 @@ class AdminPanelProvider extends PanelProvider
|
|||||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||||
->pages([
|
->pages([
|
||||||
Dashboard::class,
|
Pages\Dashboard::class,
|
||||||
])
|
])
|
||||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||||
->widgets([
|
->widgets([
|
||||||
@@ -51,6 +51,7 @@ class AdminPanelProvider extends PanelProvider
|
|||||||
TimeEntriesCreated::class,
|
TimeEntriesCreated::class,
|
||||||
TimeEntriesImported::class,
|
TimeEntriesImported::class,
|
||||||
])
|
])
|
||||||
|
->viteTheme('resources/css/filament/admin/theme.css')
|
||||||
->plugins([
|
->plugins([
|
||||||
EnvironmentIndicatorPlugin::make()
|
EnvironmentIndicatorPlugin::make()
|
||||||
->color(fn () => match (App::environment()) {
|
->color(fn () => match (App::environment()) {
|
||||||
@@ -71,9 +72,6 @@ class AdminPanelProvider extends PanelProvider
|
|||||||
NavigationGroup::make()
|
NavigationGroup::make()
|
||||||
->label('Auth')
|
->label('Auth')
|
||||||
->collapsed(),
|
->collapsed(),
|
||||||
NavigationGroup::make()
|
|
||||||
->label('Self Hosting')
|
|
||||||
->collapsed(),
|
|
||||||
])
|
])
|
||||||
->middleware([
|
->middleware([
|
||||||
EncryptCookies::class,
|
EncryptCookies::class,
|
||||||
@@ -81,13 +79,13 @@ class AdminPanelProvider extends PanelProvider
|
|||||||
StartSession::class,
|
StartSession::class,
|
||||||
AuthenticateSession::class,
|
AuthenticateSession::class,
|
||||||
ShareErrorsFromSession::class,
|
ShareErrorsFromSession::class,
|
||||||
PreventRequestForgery::class,
|
VerifyCsrfToken::class,
|
||||||
SubstituteBindings::class,
|
SubstituteBindings::class,
|
||||||
DisableBladeIconComponents::class,
|
DisableBladeIconComponents::class,
|
||||||
DispatchServingFilamentEvent::class,
|
DispatchServingFilamentEvent::class,
|
||||||
])
|
])
|
||||||
->authMiddleware([
|
->authMiddleware([
|
||||||
AuthenticateFilamentPanel::class,
|
Authenticate::class,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$modules = Module::allEnabled();
|
$modules = Module::allEnabled();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Rules;
|
namespace App\Rules;
|
||||||
|
|
||||||
use Brick\Money\IsoCurrencyProvider;
|
use Brick\Money\ISOCurrencyProvider;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Translation\PotentiallyTranslatedString;
|
use Illuminate\Translation\PotentiallyTranslatedString;
|
||||||
@@ -24,7 +24,7 @@ class CurrencyRule implements ValidationRule
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$currencies = IsoCurrencyProvider::getInstance()->getAvailableCurrencies();
|
$currencies = ISOCurrencyProvider::getInstance()->getAvailableCurrencies();
|
||||||
if (array_key_exists($value, $currencies)) {
|
if (array_key_exists($value, $currencies)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,18 +15,11 @@ use App\Models\User;
|
|||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Log;
|
use Log;
|
||||||
use Nwidart\Modules\Facades\Module;
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
class ApiService
|
class ApiService
|
||||||
{
|
{
|
||||||
private const string API_URL = 'https://app.solidtime.io/api/v1';
|
private const string API_URL = 'https://app.solidtime.io/api/v1';
|
||||||
|
|
||||||
private function isPremium(): bool
|
|
||||||
{
|
|
||||||
return Module::has('Invoicing');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkForUpdate(): ?string
|
public function checkForUpdate(): ?string
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -37,7 +30,6 @@ class ApiService
|
|||||||
'version' => config('app.version'),
|
'version' => config('app.version'),
|
||||||
'build' => config('app.build'),
|
'build' => config('app.build'),
|
||||||
'url' => config('app.url'),
|
'url' => config('app.url'),
|
||||||
'is_premium' => $this->isPremium(),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($response->status() === 200 && isset($response->json()['version']) && is_string($response->json()['version'])) {
|
if ($response->status() === 200 && isset($response->json()['version']) && is_string($response->json()['version'])) {
|
||||||
@@ -50,7 +42,7 @@ class ApiService
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
Log::warning('Failed to check for update', [
|
Log::warning('Failed to check for update', [
|
||||||
'message' => $e->getMessage(),
|
'message' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
@@ -69,7 +61,6 @@ class ApiService
|
|||||||
'version' => config('app.version'),
|
'version' => config('app.version'),
|
||||||
'build' => config('app.build'),
|
'build' => config('app.build'),
|
||||||
'url' => config('app.url'),
|
'url' => config('app.url'),
|
||||||
'is_premium' => $this->isPremium(),
|
|
||||||
// telemetry data
|
// telemetry data
|
||||||
'user_count' => User::count(),
|
'user_count' => User::count(),
|
||||||
'organization_count' => Organization::count(),
|
'organization_count' => Organization::count(),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Service;
|
namespace App\Service;
|
||||||
|
|
||||||
use Brick\Money\IsoCurrencyProvider;
|
use Brick\Money\ISOCurrencyProvider;
|
||||||
use Brick\Money\Money;
|
use Brick\Money\Money;
|
||||||
|
|
||||||
class CurrencyService
|
class CurrencyService
|
||||||
@@ -378,7 +378,7 @@ class CurrencyService
|
|||||||
|
|
||||||
public function getRandomCurrencyCode(): string
|
public function getRandomCurrencyCode(): string
|
||||||
{
|
{
|
||||||
$currencies = IsoCurrencyProvider::getInstance()->getAvailableCurrencies();
|
$currencies = ISOCurrencyProvider::getInstance()->getAvailableCurrencies();
|
||||||
$currencyCodes = array_keys($currencies);
|
$currencyCodes = array_keys($currencies);
|
||||||
|
|
||||||
return $currencyCodes[array_rand($currencyCodes)];
|
return $currencyCodes[array_rand($currencyCodes)];
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ use Illuminate\Database\Eloquent\Builder;
|
|||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
class DashboardService
|
class DashboardService
|
||||||
{
|
{
|
||||||
@@ -102,7 +101,7 @@ class DashboardService
|
|||||||
$value1 = Carbon::createFromFormat('Y-m-d', $possibleDates->first(), $timeZone);
|
$value1 = Carbon::createFromFormat('Y-m-d', $possibleDates->first(), $timeZone);
|
||||||
$value2 = Carbon::createFromFormat('Y-m-d', $possibleDates->last(), $timeZone);
|
$value2 = Carbon::createFromFormat('Y-m-d', $possibleDates->last(), $timeZone);
|
||||||
if ($value2 === null || $value1 === null) {
|
if ($value2 === null || $value1 === null) {
|
||||||
throw new RuntimeException('Provided date is not valid');
|
throw new \RuntimeException('Provided date is not valid');
|
||||||
}
|
}
|
||||||
if ($value1->gt($value2)) {
|
if ($value1->gt($value2)) {
|
||||||
$last = $value1;
|
$last = $value1;
|
||||||
@@ -152,13 +151,12 @@ class DashboardService
|
|||||||
|
|
||||||
$possibleDays = $this->lastDays($days, $timezone);
|
$possibleDays = $this->lastDays($days, $timezone);
|
||||||
|
|
||||||
// The SQL fragment contains only a timezone offset calculated as an integer.
|
|
||||||
$query = TimeEntry::query()
|
$query = TimeEntry::query()
|
||||||
->select(DB::raw('DATE('.$dateWithTimeZone.') as date, round(sum(extract(epoch from (coalesce("end", now()) - start)))) as aggregate')) // @phpstan-ignore argument.type
|
->select(DB::raw('DATE('.$dateWithTimeZone.') as date, round(sum(extract(epoch from (coalesce("end", now()) - start)))) as aggregate'))
|
||||||
->where('user_id', '=', $user->getKey())
|
->where('user_id', '=', $user->getKey())
|
||||||
->where('organization_id', '=', $organization->getKey())
|
->where('organization_id', '=', $organization->getKey())
|
||||||
->workTime()
|
->workTime()
|
||||||
->groupBy(DB::raw('DATE('.$dateWithTimeZone.')')) // @phpstan-ignore argument.type
|
->groupBy(DB::raw('DATE('.$dateWithTimeZone.')'))
|
||||||
->orderBy('date');
|
->orderBy('date');
|
||||||
|
|
||||||
$query = $this->constrainDateByPossibleDates($query, $possibleDays, $timezone);
|
$query = $this->constrainDateByPossibleDates($query, $possibleDays, $timezone);
|
||||||
@@ -195,13 +193,12 @@ class DashboardService
|
|||||||
}
|
}
|
||||||
$possibleDays = $this->daysOfThisWeek($timezone, $user->week_start);
|
$possibleDays = $this->daysOfThisWeek($timezone, $user->week_start);
|
||||||
|
|
||||||
// The SQL fragment contains only a timezone offset calculated as an integer.
|
|
||||||
$query = TimeEntry::query()
|
$query = TimeEntry::query()
|
||||||
->select(DB::raw('DATE('.$dateWithTimeZone.') as date, round(sum(extract(epoch from (coalesce("end", now()) - start)))) as aggregate')) // @phpstan-ignore argument.type
|
->select(DB::raw('DATE('.$dateWithTimeZone.') as date, round(sum(extract(epoch from (coalesce("end", now()) - start)))) as aggregate'))
|
||||||
->where('user_id', '=', $user->getKey())
|
->where('user_id', '=', $user->getKey())
|
||||||
->where('organization_id', '=', $organization->getKey())
|
->where('organization_id', '=', $organization->getKey())
|
||||||
->workTime()
|
->workTime()
|
||||||
->groupBy(DB::raw('DATE('.$dateWithTimeZone.')')) // @phpstan-ignore argument.type
|
->groupBy(DB::raw('DATE('.$dateWithTimeZone.')'))
|
||||||
->orderBy('date');
|
->orderBy('date');
|
||||||
|
|
||||||
$query = $this->constrainDateByPossibleDates($query, $possibleDays, $timezone);
|
$query = $this->constrainDateByPossibleDates($query, $possibleDays, $timezone);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user