From 4d71bf54101b322108e72be5a5b3e8b35f2be0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Tue, 8 Jul 2025 13:07:24 +0200 Subject: [PATCH] Added search to sheets --- src/lib/google/sheets/server.ts | 50 ++++- .../google/sheets/[sheetId]/data/+server.ts | 2 +- .../api/google/sheets/recent/+server.ts | 2 +- .../api/google/sheets/search/+server.ts | 30 +++ .../new/components/GoogleSheetsStep.svelte | 201 ++++++++++++++++-- 5 files changed, 266 insertions(+), 19 deletions(-) create mode 100644 src/routes/private/api/google/sheets/search/+server.ts diff --git a/src/lib/google/sheets/server.ts b/src/lib/google/sheets/server.ts index eaba378..5a2b0df 100644 --- a/src/lib/google/sheets/server.ts +++ b/src/lib/google/sheets/server.ts @@ -1,6 +1,19 @@ import { google } from 'googleapis'; import { getAuthenticatedClient } from '../auth/server.js'; -import { GoogleSheet } from './types.ts' +import { GoogleSheet } from './types.ts'; + +// Type for sheet data +export interface SheetData { + values: string[][]; +} + +// Server-side Google Sheets API handler +export const googleSheetsServer = { + getRecentSpreadsheets, + getSpreadsheetData, + getSpreadsheetInfo, + searchSheets +}; /** * Get a list of recent Google Sheets @@ -77,3 +90,38 @@ export async function getSpreadsheetInfo( return response.data; } + +/** + * Search for Google Sheets by name + * @param refreshToken - Google refresh token + * @param query - Search query + * @param limit - Maximum number of sheets to return + * @returns List of Google Sheets matching the query + */ +export async function searchSheets( + refreshToken: string, + query: string, + limit: number = 20 +): Promise { + const oauth = getAuthenticatedClient(refreshToken); + const drive = google.drive({ version: 'v3', auth: oauth }); + + // Create a query to search for spreadsheets with names containing the search term + const q = `mimeType='application/vnd.google-apps.spreadsheet' and name contains '${query}'`; + + const response = await drive.files.list({ + q, + orderBy: 'modifiedTime desc', + pageSize: limit, + fields: 'files(id,name,modifiedTime,webViewLink)' + }); + + return ( + response.data.files?.map(file => ({ + id: file.id!, // eslint-disable-line @typescript-eslint/no-non-null-assertion + name: file.name!, + modifiedTime: file.modifiedTime!, + webViewLink: file.webViewLink! + })) || [] + ); +} diff --git a/src/routes/private/api/google/sheets/[sheetId]/data/+server.ts b/src/routes/private/api/google/sheets/[sheetId]/data/+server.ts index e35f495..e0ca677 100644 --- a/src/routes/private/api/google/sheets/[sheetId]/data/+server.ts +++ b/src/routes/private/api/google/sheets/[sheetId]/data/+server.ts @@ -1,6 +1,6 @@ import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import { googleSheetsServer } from '$lib/google/server.ts'; +import { googleSheetsServer } from '$lib/google/sheets/server.js'; export const GET: RequestHandler = async ({ params, request }) => { try { diff --git a/src/routes/private/api/google/sheets/recent/+server.ts b/src/routes/private/api/google/sheets/recent/+server.ts index 78d9129..304c575 100644 --- a/src/routes/private/api/google/sheets/recent/+server.ts +++ b/src/routes/private/api/google/sheets/recent/+server.ts @@ -1,6 +1,6 @@ import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import { googleSheetsServer } from '$lib/google/server.ts'; +import { googleSheetsServer } from '$lib/google/sheets/server.js'; export const GET: RequestHandler = async ({ request }) => { try { diff --git a/src/routes/private/api/google/sheets/search/+server.ts b/src/routes/private/api/google/sheets/search/+server.ts new file mode 100644 index 0000000..2c79e6c --- /dev/null +++ b/src/routes/private/api/google/sheets/search/+server.ts @@ -0,0 +1,30 @@ +import { error, json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { googleSheetsServer } from '$lib/google/sheets/server.js'; + +export const GET: RequestHandler = async ({ url, request }) => { + try { + // Get search query from URL + const query = url.searchParams.get('query'); + + if (!query) { + throw error(400, 'Search query is required'); + } + + // Get authorization token from request headers + const authHeader = request.headers.get('Authorization'); + if (!authHeader || !authHeader.startsWith('Bearer ')) { + throw error(401, 'Missing or invalid Authorization header'); + } + const refreshToken = authHeader.substring(7); // Remove "Bearer " prefix + + // Search for sheets using the query + const sheets = await googleSheetsServer.searchSheets(refreshToken, query); + + // Return the search results + return json(sheets); + } catch (err) { + console.error('Error searching Google Sheets:', err); + throw error(500, 'Failed to search Google Sheets'); + } +}; diff --git a/src/routes/private/events/event/new/components/GoogleSheetsStep.svelte b/src/routes/private/events/event/new/components/GoogleSheetsStep.svelte index 5831568..672ec41 100644 --- a/src/routes/private/events/event/new/components/GoogleSheetsStep.svelte +++ b/src/routes/private/events/event/new/components/GoogleSheetsStep.svelte @@ -1,5 +1,5 @@
@@ -69,9 +135,9 @@
{:else} - +
-

Available Sheets

+

Google Sheets

{#if sheetsData.selectedSheet}
-
- {#each sheetsData.availableSheets as sheet} + + +
+ +
+ + + +
+ {#if searchQuery} - {/each} + {/if}
+ + {#if isSearching} + +
+ {#each Array(3) as _} +
+
+
+
+ {/each} +
+ {:else if searchQuery && searchResults.length === 0 && !searchError} + +
+

No sheets found matching "{searchQuery}"

+
+ {:else if searchError} + +
+

{searchError}

+ +
+ {:else if searchQuery && searchResults.length > 0} + +
+ {#each searchResults as sheet} + + {/each} +
+ {:else} + +
+ {#each sheetsData.availableSheets as sheet} + + {/each} +
+ {#if sheetsData.availableSheets.length === 0 && !sheetsData.loading} +
+

No recent sheets found. Try searching above.

+
+ {/if} + {/if} {/if}
{/if} @@ -134,7 +290,7 @@ aria-label={`Select data type for column ${index + 1}`} onclick={(e) => e.stopPropagation()} onchange={(e) => { - const value = e.target.value; + const value = (e.target as HTMLSelectElement).value; if (value === "none") return; // Reset previous selection if this column was already mapped @@ -187,6 +343,7 @@ sheetsData.columnMapping.confirmation === cellIndex + 1 ? 'font-medium text-amber-700' : 'text-gray-700' } + title={cell || ''} > {cell || ''} @@ -197,7 +354,17 @@ -

Showing first 10 rows

+
+

Showing first 10 rows

+ {#if sheetsData.sheetData[0] && sheetsData.sheetData[0].length > 3} +

+ + + + Scroll horizontally to see all {sheetsData.sheetData[0].length} columns +

+ {/if} +
{/if} @@ -211,3 +378,5 @@

{errors.sheetData}

{/if} + +