From 3ee7839ca9814f93fe309b7241fdf5af15d22c5e Mon Sep 17 00:00:00 2001 From: Gregor Vostrak Date: Wed, 18 Sep 2024 16:54:10 +0200 Subject: [PATCH] add detailed reporting page --- .../V1/TimeEntry/TimeEntryIndexRequest.php | 14 + resources/js/Layouts/AppLayout.vue | 5 +- resources/js/Pages/Reporting.vue | 18 +- resources/js/Pages/ReportingDetailed.vue | 349 ++++++++++++++++++ resources/js/packages/api/src/index.ts | 5 + .../packages/api/src/openapi.json.client.ts | 10 +- .../ui/src/Input/MultiselectDropdown.vue | 4 +- .../ui/src/Input/MultiselectDropdownItem.vue | 4 +- .../TimeEntry/TimeEntryDescriptionInput.vue | 6 +- .../src/TimeEntry/TimeEntryRangeSelector.vue | 12 +- .../ui/src/TimeEntry/TimeEntryRow.vue | 23 +- resources/js/utils/useTimeEntries.ts | 14 +- routes/web.php | 4 + 13 files changed, 446 insertions(+), 22 deletions(-) create mode 100644 resources/js/Pages/ReportingDetailed.vue diff --git a/app/Http/Requests/V1/TimeEntry/TimeEntryIndexRequest.php b/app/Http/Requests/V1/TimeEntry/TimeEntryIndexRequest.php index d7f4fae2..972bb0f1 100644 --- a/app/Http/Requests/V1/TimeEntry/TimeEntryIndexRequest.php +++ b/app/Http/Requests/V1/TimeEntry/TimeEntryIndexRequest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Http\Requests\V1\TimeEntry; +use App\Models\Client; use App\Models\Member; use App\Models\Organization; use App\Models\Project; @@ -49,6 +50,19 @@ class TimeEntryIndexRequest 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 project IDs, project IDs are OR combined 'project_ids' => [ 'array', diff --git a/resources/js/Layouts/AppLayout.vue b/resources/js/Layouts/AppLayout.vue index 99bfc784..9f2fb23d 100644 --- a/resources/js/Layouts/AppLayout.vue +++ b/resources/js/Layouts/AppLayout.vue @@ -114,7 +114,10 @@ const page = usePage<{ diff --git a/resources/js/Pages/Reporting.vue b/resources/js/Pages/Reporting.vue index cda54941..edc6ecbc 100644 --- a/resources/js/Pages/Reporting.vue +++ b/resources/js/Pages/Reporting.vue @@ -35,13 +35,16 @@ import { getCurrentMembershipId, getCurrentRole } from '@/utils/useUser'; import ClientMultiselectDropdown from '@/Components/Common/Client/ClientMultiselectDropdown.vue'; import { useTagsStore } from '@/utils/useTags'; import { formatCents } from '@/packages/ui/src/utils/money'; -import { useStorage } from '@vueuse/core'; +import { useSessionStorage, useStorage } from '@vueuse/core'; +import TabBar from '@/Components/Common/TabBar/TabBar.vue'; +import TabBarItem from '@/Components/Common/TabBar/TabBarItem.vue'; +import { router } from '@inertiajs/vue3'; -const startDate = useStorage( +const startDate = useSessionStorage( 'reporting-start-date', getLocalizedDayJs(getDayJsInstance()().format()).subtract(14, 'd').format() ); -const endDate = useStorage( +const endDate = useSessionStorage( 'reporting-end-date', getLocalizedDayJs(getDayJsInstance()().format()).format() ); @@ -157,6 +160,15 @@ async function createTag(tag: string) { class="py-3 sm:py-5 border-b border-default-background-separator flex justify-between items-center">
+ + Overview + Detailed +
diff --git a/resources/js/Pages/ReportingDetailed.vue b/resources/js/Pages/ReportingDetailed.vue new file mode 100644 index 00000000..92980eb5 --- /dev/null +++ b/resources/js/Pages/ReportingDetailed.vue @@ -0,0 +1,349 @@ + + + diff --git a/resources/js/packages/api/src/index.ts b/resources/js/packages/api/src/index.ts index 9ee2a2cd..120812f9 100644 --- a/resources/js/packages/api/src/index.ts +++ b/resources/js/packages/api/src/index.ts @@ -107,6 +107,11 @@ export type ReportingResponse = ZodiosResponseByAlias< export type AggregatedTimeEntries = ReportingResponse['data']; export type GroupedDataEntries = ReportingResponse['data']['grouped_data']; +export type TimeEntriesQueryParams = ZodiosQueryParamsByAlias< + SolidTimeApi, + 'getTimeEntries' +>; + export type AggregatedTimeEntriesQueryParams = ZodiosQueryParamsByAlias< SolidTimeApi, 'getAggregatedTimeEntries' diff --git a/resources/js/packages/api/src/openapi.json.client.ts b/resources/js/packages/api/src/openapi.json.client.ts index 084ad8c4..8d4aa9e4 100644 --- a/resources/js/packages/api/src/openapi.json.client.ts +++ b/resources/js/packages/api/src/openapi.json.client.ts @@ -2157,6 +2157,11 @@ Users with the permission `time-entries:view:own` can only use this en type: 'Query', schema: z.array(z.string().uuid()).min(1).optional(), }, + { + name: 'client_ids', + type: 'Query', + schema: z.array(z.string().uuid()).min(1).optional(), + }, { name: 'project_ids', type: 'Query', @@ -2172,11 +2177,6 @@ Users with the permission `time-entries:view:own` can only use this en type: 'Query', schema: z.array(z.string().uuid()).min(1).optional(), }, - { - name: 'client_ids', - type: 'Query', - schema: z.string().optional(), - }, { name: 'user_id', type: 'Query', diff --git a/resources/js/packages/ui/src/Input/MultiselectDropdown.vue b/resources/js/packages/ui/src/Input/MultiselectDropdown.vue index ebbee064..f6df0393 100644 --- a/resources/js/packages/ui/src/Input/MultiselectDropdown.vue +++ b/resources/js/packages/ui/src/Input/MultiselectDropdown.vue @@ -165,7 +165,9 @@ const highlightedItem = computed(() => { ref="searchInput" class="bg-card-background border-0 placeholder-muted text-sm text-white py-2.5 focus:ring-0 border-b border-card-background-separator focus:border-card-background-separator w-full" :placeholder="searchPlaceholder" /> -
+
{
- {{ name }} + {{ + name + }}
diff --git a/resources/js/packages/ui/src/TimeEntry/TimeEntryDescriptionInput.vue b/resources/js/packages/ui/src/TimeEntry/TimeEntryDescriptionInput.vue index 0c526516..4bfd127d 100644 --- a/resources/js/packages/ui/src/TimeEntry/TimeEntryDescriptionInput.vue +++ b/resources/js/packages/ui/src/TimeEntry/TimeEntryDescriptionInput.vue @@ -29,8 +29,8 @@ const displaysPlaceholder = computed(() => {
{{ liveDataValue }}
@@ -41,7 +41,7 @@ const displaysPlaceholder = computed(() => { @input="onInput" @keydown.enter="onChange" placeholder="Add a description" - class="absolute px-0 h-full min-w-0 pl-3 pr-1 left-0 top-0 w-full text-sm lg:text-base text-white font-medium bg-transparent focus-visible:ring-0 rounded-lg border-0" /> + class="absolute px-0 h-full min-w-0 pl-3 pr-1 left-0 top-0 w-full text-sm text-white font-medium bg-transparent focus-visible:ring-0 rounded-lg border-0" />
diff --git a/resources/js/packages/ui/src/TimeEntry/TimeEntryRangeSelector.vue b/resources/js/packages/ui/src/TimeEntry/TimeEntryRangeSelector.vue index 271ad0ae..84c10ccc 100644 --- a/resources/js/packages/ui/src/TimeEntry/TimeEntryRangeSelector.vue +++ b/resources/js/packages/ui/src/TimeEntry/TimeEntryRangeSelector.vue @@ -1,12 +1,13 @@