From fffa3639d309cfa7bb5252bb6eee2b36c8275f85 Mon Sep 17 00:00:00 2001 From: Gregor Vostrak Date: Tue, 16 Apr 2024 23:23:26 +0200 Subject: [PATCH] add import types and description from the server, add report data modal --- .../Controllers/Api/V1/ImportController.php | 2 +- app/Service/Import/Importers/ReportDto.php | 4 +- openapi.json.client.ts | 42 +++++- .../js/Pages/Teams/Partials/ImportData.vue | 127 ++++++++++++++---- .../Teams/Partials/UpdateTeamNameForm.vue | 4 +- resources/js/utils/api.ts | 6 + .../Endpoint/Api/V1/ImportEndpointTest.php | 2 +- 7 files changed, 157 insertions(+), 30 deletions(-) diff --git a/app/Http/Controllers/Api/V1/ImportController.php b/app/Http/Controllers/Api/V1/ImportController.php index 898a85c7..f1fe44bd 100644 --- a/app/Http/Controllers/Api/V1/ImportController.php +++ b/app/Http/Controllers/Api/V1/ImportController.php @@ -84,7 +84,7 @@ class ImportController extends Controller * tasks: array{ * created: int, * }, - * time-entries: array{ + * time_entries: array{ * created: int, * }, * tags: array{ diff --git a/app/Service/Import/Importers/ReportDto.php b/app/Service/Import/Importers/ReportDto.php index 5f4748af..72aeb05e 100644 --- a/app/Service/Import/Importers/ReportDto.php +++ b/app/Service/Import/Importers/ReportDto.php @@ -39,7 +39,7 @@ class ReportDto * tasks: array{ * created: int, * }, - * time-entries: array{ + * time_entries: array{ * created: int, * }, * tags: array{ @@ -62,7 +62,7 @@ class ReportDto 'tasks' => [ 'created' => $this->tasksCreated, ], - 'time-entries' => [ + 'time_entries' => [ 'created' => $this->timeEntriesCreated, ], 'tags' => [ diff --git a/openapi.json.client.ts b/openapi.json.client.ts index 7de4e50e..cda97e89 100644 --- a/openapi.json.client.ts +++ b/openapi.json.client.ts @@ -433,7 +433,7 @@ const endpoints = makeApi([ tasks: z .object({ created: z.number().int() }) .passthrough(), - 'time-entries': z + time_entries: z .object({ created: z.number().int() }) .passthrough(), tags: z @@ -476,6 +476,44 @@ const endpoints = makeApi([ }, ], }, + { + method: 'get', + path: '/v1/organizations/:organization/importers', + alias: 'getImporters', + requestFormat: 'json', + parameters: [ + { + name: 'organization', + type: 'Path', + schema: z.string().uuid(), + }, + ], + response: z + .object({ + data: z.array( + z + .object({ + key: z.string(), + name: z.string(), + description: z.string(), + }) + .passthrough() + ), + }) + .passthrough(), + errors: [ + { + status: 403, + description: `Authorization error`, + schema: z.object({ message: z.string() }).passthrough(), + }, + { + status: 404, + description: `Not found`, + schema: z.object({ message: z.string() }).passthrough(), + }, + ], + }, { method: 'get', path: '/v1/organizations/:organization/invitations', @@ -1545,6 +1583,8 @@ const endpoints = makeApi([ method: 'get', path: '/v1/organizations/:organization/time-entries', alias: 'getTimeEntries', + description: `If you only need time entries for a specific user, you can filter by `user_id`. +Users with the permission `time-entries:view:own` can only use this endpoint with their own user ID in the user_id filter.`, requestFormat: 'json', parameters: [ { diff --git a/resources/js/Pages/Teams/Partials/ImportData.vue b/resources/js/Pages/Teams/Partials/ImportData.vue index a209d109..fd384537 100644 --- a/resources/js/Pages/Teams/Partials/ImportData.vue +++ b/resources/js/Pages/Teams/Partials/ImportData.vue @@ -2,51 +2,58 @@ import FormSection from '@/Components/FormSection.vue'; import PrimaryButton from '@/Components/PrimaryButton.vue'; import type { Organization } from '@/types/models'; -import { ref } from 'vue'; +import { computed, onMounted, ref } from 'vue'; import { useNotificationsStore } from '@/utils/notification'; import { api } from '../../../../../openapi.json.client'; import InputLabel from '@/Components/InputLabel.vue'; import { DocumentIcon } from '@heroicons/vue/24/solid'; import { getCurrentOrganizationId } from '@/utils/useUser'; +import type { ImportReport, ImportType } from '@/utils/api'; +import DialogModal from '@/Components/DialogModal.vue'; +import SecondaryButton from '@/Components/SecondaryButton.vue'; defineProps<{ team: Organization; }>(); -type ImportType = - | 'toggl_time_entries' - | 'toggl_data_importer' - | 'clockify_time_entries' - | 'clockify_projects'; - -const importTypeOptions: { value: ImportType; label: string }[] = [ - { value: 'toggl_time_entries', label: 'Toggl Time Entries' }, - { value: 'toggl_data_importer', label: 'Toggl Data Importer' }, - { value: 'clockify_time_entries', label: 'Clockify Time Entries' }, - { value: 'clockify_projects', label: 'Clockify Projects' }, -]; +const importTypeOptions = ref([]); const { addNotification } = useNotificationsStore(); +onMounted(async () => { + const organizationId = getCurrentOrganizationId(); + if (organizationId) { + importTypeOptions.value = ( + await api.getImporters({ + params: { + organization: organizationId, + }, + }) + ).data; + } +}); + +const reportResult = ref(); +const files = ref(null); + async function importData() { - const files = importFile.value?.files ?? []; if (importType.value === null) { addNotification('error', 'Please select the import type'); return; } - if (files.length !== 1) { + if (files.value?.length !== 1) { addNotification( 'error', 'Please select the CSV or ZIP file that you want to import' ); return; } - const base64String = await toBase64(files[0]); + const base64String = await toBase64(files.value[0]); const organizationId = getCurrentOrganizationId(); if (organizationId !== null) { - await api.importData( + reportResult.value = await api.importData( { - type: importType.value, + type: importType.value.key, data: base64String.replace('data:text/csv;base64,', ''), }, { @@ -55,6 +62,7 @@ async function importData() { }, } ); + showResultModal.value = true; } } @@ -77,11 +85,68 @@ function toBase64(file: File): Promise { }); } +function updateFiles() { + files.value = importFile.value?.files ?? null; +} + +const currentImporterDescription = computed(() => { + if (importType.value === null) { + return ''; + } + return importType.value.description; +}); + +const filenames = computed(() => { + return files.value?.item(0)?.name ?? 'Import File selected'; +}); + const importType = ref(null); + +const showResultModal = ref(false);