From b2af9c6bf1f6cfadbb61450f224de5d53d1f75c7 Mon Sep 17 00:00:00 2001 From: Gregor Vostrak Date: Mon, 2 Feb 2026 01:57:40 +0100 Subject: [PATCH] Add client_ids filter to time entry export --- .../TimeEntry/TimeEntryIndexExportRequest.php | 18 +++++ e2e/members.spec.ts | 4 +- e2e/utils/reporting.ts | 6 +- .../Common/Reporting/ReportingFilterBar.vue | 16 +--- .../Common/Reporting/ReportingOverview.vue | 23 +++--- .../ui/src/Input/MultiselectDropdown.vue | 8 +- .../js/utils/useAggregatedTimeEntriesQuery.ts | 6 +- .../Endpoint/Api/V1/TimeEntryEndpointTest.php | 78 +++++++++++++++++++ 8 files changed, 125 insertions(+), 34 deletions(-) diff --git a/app/Http/Requests/V1/TimeEntry/TimeEntryIndexExportRequest.php b/app/Http/Requests/V1/TimeEntry/TimeEntryIndexExportRequest.php index 5daa61e1..70447609 100644 --- a/app/Http/Requests/V1/TimeEntry/TimeEntryIndexExportRequest.php +++ b/app/Http/Requests/V1/TimeEntry/TimeEntryIndexExportRequest.php @@ -6,6 +6,7 @@ namespace App\Http\Requests\V1\TimeEntry; use App\Enums\ExportFormat; use App\Enums\TimeEntryRoundingType; +use App\Models\Client; use App\Models\Member; use App\Models\Organization; use App\Models\Project; @@ -58,6 +59,23 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest return $builder->whereBelongsTo($this->organization, 'organization'); }), ], + // Filter by client IDs, client IDs are OR combined + 'client_ids' => [ + 'array', + 'min:1', + ], + 'client_ids.*' => [ + 'string', + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, + ], // Filter by project IDs, project IDs are OR combined 'project_ids' => [ 'array', diff --git a/e2e/members.spec.ts b/e2e/members.spec.ts index 49661246..7f182811 100644 --- a/e2e/members.spec.ts +++ b/e2e/members.spec.ts @@ -205,7 +205,9 @@ test('test that merging a placeholder member works', async ({ page }) => { page.getByRole('button', { name: 'Merge Member' }).click(), page.waitForResponse( (response) => - response.url().includes('/member/') && response.url().includes('/merge-into') && response.ok() + response.url().includes('/member/') && + response.url().includes('/merge-into') && + response.ok() ), ]); diff --git a/e2e/utils/reporting.ts b/e2e/utils/reporting.ts index d7b63421..d48c793c 100644 --- a/e2e/utils/reporting.ts +++ b/e2e/utils/reporting.ts @@ -52,11 +52,7 @@ export async function createClient(page: Page, clientName: string) { await expect(page.getByText(clientName)).toBeVisible(); } -export async function createProjectWithClient( - page: Page, - projectName: string, - clientName: string -) { +export async function createProjectWithClient(page: Page, projectName: string, clientName: string) { await page.goto(PLAYWRIGHT_BASE_URL + '/projects'); await expect(page.getByRole('button', { name: 'Create Project' })).toBeVisible(); await page.getByRole('button', { name: 'Create Project' }).click(); diff --git a/resources/js/Components/Common/Reporting/ReportingFilterBar.vue b/resources/js/Components/Common/Reporting/ReportingFilterBar.vue index 085ef5f3..8c6e740c 100644 --- a/resources/js/Components/Common/Reporting/ReportingFilterBar.vue +++ b/resources/js/Components/Common/Reporting/ReportingFilterBar.vue @@ -51,9 +51,7 @@ async function createTag(name: string) {
Filters
- + - + - + - +