feat(badges): add Axonius Zoom CSV server import option to upload form
This commit is contained in:
@@ -4,6 +4,7 @@ import { Loader2 } from '@lucide/svelte';
|
|||||||
import type { key_val } from '$lib/stores/ae_stores';
|
import type { key_val } from '$lib/stores/ae_stores';
|
||||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||||
import { ae_api } from '$lib/stores/ae_stores';
|
import { ae_api } from '$lib/stores/ae_stores';
|
||||||
|
import { api } from '$lib/api/api';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
event_id: string;
|
event_id: string;
|
||||||
@@ -22,6 +23,10 @@ let upload_status: string = $state('idle'); // idle, loading, processing, succes
|
|||||||
let upload_message: string = $state('');
|
let upload_message: string = $state('');
|
||||||
let processed_badges_count: number = $state(0);
|
let processed_badges_count: number = $state(0);
|
||||||
let total_badges_in_file: number = $state(0);
|
let total_badges_in_file: number = $state(0);
|
||||||
|
let upload_mode: 'client_csv' | 'axonius_zoom' = $state('client_csv');
|
||||||
|
let begin_at: number | null = $state(null);
|
||||||
|
let end_at: number | null = $state(null);
|
||||||
|
let return_detail: boolean = $state(false);
|
||||||
|
|
||||||
// A very basic CSV parser that assumes the first row is headers
|
// A very basic CSV parser that assumes the first row is headers
|
||||||
function parse_csv(text: string): key_val[] {
|
function parse_csv(text: string): key_val[] {
|
||||||
@@ -59,6 +64,69 @@ async function handle_upload(event: Event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Server-side import for Axonius Zoom CSV
|
||||||
|
if (upload_mode === 'axonius_zoom') {
|
||||||
|
upload_status = 'loading';
|
||||||
|
upload_message = 'Uploading to server...';
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', selected_file, selected_file.name);
|
||||||
|
|
||||||
|
const params: any = {};
|
||||||
|
if (begin_at !== null && begin_at !== undefined) params.begin_at = String(begin_at);
|
||||||
|
if (end_at !== null && end_at !== undefined) params.end_at = String(end_at);
|
||||||
|
if (return_detail) params.return_detail = String(true);
|
||||||
|
|
||||||
|
const task_id = (crypto && (crypto as any).randomUUID)
|
||||||
|
? (crypto as any).randomUUID()
|
||||||
|
: String(Date.now());
|
||||||
|
|
||||||
|
try {
|
||||||
|
upload_status = 'processing';
|
||||||
|
const endpoint = `/event/${event_id}/badge/import/zoom_csv`;
|
||||||
|
const result = await api.post_object({
|
||||||
|
api_cfg: $ae_api,
|
||||||
|
endpoint,
|
||||||
|
form_data: formData,
|
||||||
|
params,
|
||||||
|
return_meta: true,
|
||||||
|
task_id,
|
||||||
|
log_lvl: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
upload_status = 'error';
|
||||||
|
upload_message = 'Server import failed (no response).';
|
||||||
|
if (onerror) onerror(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = result.meta ?? result;
|
||||||
|
if (meta && meta.success === false) {
|
||||||
|
upload_status = 'error';
|
||||||
|
upload_message = meta?.details?.message || 'Server import failed';
|
||||||
|
if (onerror) onerror(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const processed = meta?.processed ?? meta?.data_list_count ?? (Array.isArray(result?.data) ? result.data.length : 0);
|
||||||
|
processed_badges_count = processed || 0;
|
||||||
|
total_badges_in_file = meta?.total ?? processed_badges_count;
|
||||||
|
|
||||||
|
upload_status = 'success';
|
||||||
|
upload_message = `Server processed ${processed_badges_count} badges.`;
|
||||||
|
if (onsuccess) onsuccess();
|
||||||
|
} catch (err: any) {
|
||||||
|
upload_status = 'error';
|
||||||
|
upload_message = `Upload failed: ${err?.message || 'Unknown error'}`;
|
||||||
|
console.error('Upload error:', err);
|
||||||
|
if (onerror) onerror(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: client-side CSV parsing (existing behavior)
|
||||||
upload_status = 'loading';
|
upload_status = 'loading';
|
||||||
upload_message = 'Reading file...';
|
upload_message = 'Reading file...';
|
||||||
|
|
||||||
@@ -101,13 +169,11 @@ async function handle_upload(event: Event) {
|
|||||||
|
|
||||||
upload_status = 'success';
|
upload_status = 'success';
|
||||||
upload_message = `Successfully uploaded ${processed_badges_count} out of ${total_badges_in_file} badges.`;
|
upload_message = `Successfully uploaded ${processed_badges_count} out of ${total_badges_in_file} badges.`;
|
||||||
// dispatch('success');
|
|
||||||
if (onsuccess) onsuccess();
|
if (onsuccess) onsuccess();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
upload_status = 'error';
|
upload_status = 'error';
|
||||||
upload_message = `Error processing file: ${error?.message || 'Unknown error'}`;
|
upload_message = `Error processing file: ${error?.message || 'Unknown error'}`;
|
||||||
console.error('Error during file upload:', error);
|
console.error('Error during file upload:', error);
|
||||||
// dispatch('error', error);
|
|
||||||
if (onerror) onerror(error);
|
if (onerror) onerror(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -115,7 +181,6 @@ async function handle_upload(event: Event) {
|
|||||||
upload_status = 'error';
|
upload_status = 'error';
|
||||||
upload_message = `Error reading file: ${reader.error?.message}`;
|
upload_message = `Error reading file: ${reader.error?.message}`;
|
||||||
console.error('FileReader error:', reader.error);
|
console.error('FileReader error:', reader.error);
|
||||||
// dispatch('error', reader.error);
|
|
||||||
if (onerror) onerror(reader.error);
|
if (onerror) onerror(reader.error);
|
||||||
};
|
};
|
||||||
reader.readAsText(selected_file);
|
reader.readAsText(selected_file);
|
||||||
@@ -143,6 +208,31 @@ function handle_cancel() {
|
|||||||
<code>badge_type_code</code>.
|
<code>badge_type_code</code>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<fieldset class="space-y-2">
|
||||||
|
<legend class="label"><span>Upload format</span></legend>
|
||||||
|
<div class="flex gap-4 items-center">
|
||||||
|
<label class="label flex items-center gap-2">
|
||||||
|
<input type="radio" name="upload_mode" value="client_csv" bind:group={upload_mode} />
|
||||||
|
<span>Standard CSV (client-side parse)</span>
|
||||||
|
</label>
|
||||||
|
<label class="label flex items-center gap-2">
|
||||||
|
<input type="radio" name="upload_mode" value="axonius_zoom" bind:group={upload_mode} />
|
||||||
|
<span>Axonius Zoom CSV (server import)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if upload_mode === 'axonius_zoom'}
|
||||||
|
<div class="grid grid-cols-3 gap-2 items-center">
|
||||||
|
<input type="number" min="0" placeholder="begin_at (0)" bind:value={begin_at} class="input" />
|
||||||
|
<input type="number" min="0" placeholder="end_at (20000)" bind:value={end_at} class="input" />
|
||||||
|
<label class="label flex items-center gap-2">
|
||||||
|
<input type="checkbox" bind:checked={return_detail} class="checkbox" />
|
||||||
|
<span>Return detail</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span>Select CSV File</span>
|
<span>Select CSV File</span>
|
||||||
<input
|
<input
|
||||||
|
|||||||
Reference in New Issue
Block a user