From 4c977b5bf8001b63024c7d913fa2275957f160e7 Mon Sep 17 00:00:00 2001 From: Gregor Vostrak Date: Fri, 31 May 2024 02:04:45 +0200 Subject: [PATCH] add client_ids to reporting filters --- .../TimeEntry/TimeEntryAggregateRequest.php | 14 +++++ openapi.json.client.ts | 20 +++--- .../Client/ClientMultiselectDropdown.vue | 29 +++++++++ resources/js/Pages/Reporting.vue | 61 ++++++++++--------- 4 files changed, 87 insertions(+), 37 deletions(-) create mode 100644 resources/js/Components/Common/Client/ClientMultiselectDropdown.vue diff --git a/app/Http/Requests/V1/TimeEntry/TimeEntryAggregateRequest.php b/app/Http/Requests/V1/TimeEntry/TimeEntryAggregateRequest.php index 365a1f5e..8d400817 100644 --- a/app/Http/Requests/V1/TimeEntry/TimeEntryAggregateRequest.php +++ b/app/Http/Requests/V1/TimeEntry/TimeEntryAggregateRequest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Http\Requests\V1\TimeEntry; use App\Enums\TimeEntryAggregationType; +use App\Models\Client; use App\Models\Member; use App\Models\Organization; use App\Models\Project; @@ -86,6 +87,19 @@ class TimeEntryAggregateRequest extends FormRequest return $builder->whereBelongsTo($this->organization, 'organization'); }), ], + // Filter by client IDs, client IDs are OR combined + 'client_ids' => [ + 'array', + 'min:1', + ], + 'client_ids.*' => [ + 'string', + 'uuid', + new ExistsEloquent(Client::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + }), + ], // Filter by tag IDs, tag IDs are AND combined 'tag_ids' => [ 'array', diff --git a/openapi.json.client.ts b/openapi.json.client.ts index 696268cd..b7666707 100644 --- a/openapi.json.client.ts +++ b/openapi.json.client.ts @@ -165,15 +165,16 @@ const v1_time_entries_update_multiple_Body = z .passthrough(); const updateTimeEntry_Body = z .object({ - member_id: z.string().uuid().optional(), - project_id: z.union([z.string(), z.null()]).optional(), - task_id: z.union([z.string(), z.null()]).optional(), + member_id: z.string().uuid(), + project_id: z.union([z.string(), z.null()]), + task_id: z.union([z.string(), z.null()]), start: z.string(), - end: z.union([z.string(), z.null()]).optional(), - billable: z.boolean().optional(), - description: z.union([z.string(), z.null()]).optional(), - tags: z.union([z.array(z.string()), z.null()]).optional(), + end: z.union([z.string(), z.null()]), + billable: z.boolean(), + description: z.union([z.string(), z.null()]), + tags: z.union([z.array(z.string()), z.null()]), }) + .partial() .passthrough(); export const schemas = { @@ -2114,6 +2115,11 @@ If the group parameters are all set to `null` or are all missing, the type: 'Query', schema: z.array(z.string()).min(1).optional(), }, + { + name: 'client_ids', + type: 'Query', + schema: z.array(z.string()).min(1).optional(), + }, { name: 'tag_ids', type: 'Query', diff --git a/resources/js/Components/Common/Client/ClientMultiselectDropdown.vue b/resources/js/Components/Common/Client/ClientMultiselectDropdown.vue new file mode 100644 index 00000000..a775f26d --- /dev/null +++ b/resources/js/Components/Common/Client/ClientMultiselectDropdown.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/Pages/Reporting.vue b/resources/js/Pages/Reporting.vue index 8ef777a3..91c528ed 100644 --- a/resources/js/Pages/Reporting.vue +++ b/resources/js/Pages/Reporting.vue @@ -28,6 +28,7 @@ import ReportingRow from '@/Components/Common/Reporting/ReportingRow.vue'; import { formatCents } from '@/utils/money'; import ReportingPieChart from '@/Components/Common/Reporting/ReportingPieChart.vue'; import { getCurrentMembershipId, getCurrentRole } from '@/utils/useUser'; +import ClientMultiselectDropdown from '@/Components/Common/Client/ClientMultiselectDropdown.vue'; const startDate = ref( getDayJsInstance()().subtract(14, 'd').format('YYYY-MM-DD') @@ -37,6 +38,8 @@ const selectedTags = ref([]); const selectedProjects = ref([]); const selectedMembers = ref([]); const selectedTasks = ref([]); +const selectedClients = ref([]); + const billable = ref<'true' | 'false' | null>(null); type GroupingOption = 'project' | 'task' | 'user' | 'billable' | 'client'; @@ -49,36 +52,25 @@ function getFilterAttributes() { start: getDayJsInstance()(startDate.value).utc().format(), end: getDayJsInstance()(endDate.value).endOf('day').utc().format(), }; - if (selectedMembers.value.length > 0) { - params = { - ...params, - member_ids: selectedMembers.value, - }; - } - if (selectedProjects.value.length > 0) { - params = { - ...params, - project_ids: selectedProjects.value, - }; - } - if (selectedTasks.value.length > 0) { - params = { - ...params, - task_ids: selectedTasks.value, - }; - } - if (selectedTags.value.length > 0) { - params = { - ...params, - tag_ids: selectedTags.value, - }; - } - if (billable.value !== null) { - params = { - ...params, - billable: billable.value, - }; - } + params = { + ...params, + member_ids: + selectedMembers.value.length > 0 + ? selectedMembers.value + : undefined, + project_ids: + selectedProjects.value.length > 0 + ? selectedProjects.value + : undefined, + task_ids: + selectedTasks.value.length > 0 ? selectedTasks.value : undefined, + client_ids: + selectedClients.value.length > 0 + ? selectedClients.value + : undefined, + tag_ids: selectedTags.value.length > 0 ? selectedTags.value : undefined, + billable: billable.value !== null ? billable.value : undefined, + }; return params; } @@ -179,6 +171,15 @@ onMounted(() => { :icon="CheckCircleIcon"> + + +