282 lines
8.1 KiB
Svelte
282 lines
8.1 KiB
Svelte
<script lang="ts">
|
|
import { availableSheets, selectedSheet, currentStep } from '$lib/stores';
|
|
import { searchSheets } from '$lib/google';
|
|
import { onMount } from 'svelte';
|
|
|
|
let searchQuery = $state('');
|
|
let isLoading = $state(false);
|
|
let error = $state('');
|
|
let searchResults = $state<any[]>([]);
|
|
let hasSearched = $state(false);
|
|
let recentSheets = $state<any[]>([]);
|
|
|
|
const RECENT_SHEETS_KEY = 'esn-recent-sheets';
|
|
|
|
onMount(() => {
|
|
loadRecentSheets();
|
|
});
|
|
|
|
async function handleSearch() {
|
|
if (!searchQuery.trim()) return;
|
|
|
|
isLoading = true;
|
|
error = '';
|
|
|
|
try {
|
|
searchResults = await searchSheets(searchQuery);
|
|
availableSheets.set(
|
|
searchResults.map((sheet) => ({
|
|
spreadsheetId: sheet.spreadsheetId || sheet.id,
|
|
name: sheet.name,
|
|
url: sheet.webViewLink
|
|
}))
|
|
);
|
|
hasSearched = true;
|
|
} catch (err) {
|
|
console.error('Error searching sheets:', err);
|
|
error = 'Failed to search sheets. Please check your connection and try again.';
|
|
searchResults = [];
|
|
availableSheets.set([]);
|
|
} finally {
|
|
isLoading = false;
|
|
}
|
|
}
|
|
|
|
function loadRecentSheets() {
|
|
try {
|
|
const saved = localStorage.getItem(RECENT_SHEETS_KEY);
|
|
if (saved) {
|
|
recentSheets = JSON.parse(saved);
|
|
}
|
|
} catch (err) {
|
|
console.error('Error loading recent sheets:', err);
|
|
// If there's an error, clear the stored value
|
|
localStorage.removeItem(RECENT_SHEETS_KEY);
|
|
recentSheets = [];
|
|
}
|
|
}
|
|
|
|
function handleSelectSheet(sheet) {
|
|
const sheetData = {
|
|
spreadsheetId: sheet.spreadsheetId || sheet.id,
|
|
name: sheet.name,
|
|
url: sheet.webViewLink || sheet.url
|
|
};
|
|
selectedSheet.set(sheetData);
|
|
}
|
|
|
|
let canProceed = $derived($selectedSheet !== null);
|
|
|
|
function handleContinue() {
|
|
if (!canProceed) return;
|
|
currentStep.set(3); // Move to the column mapping step
|
|
}
|
|
</script>
|
|
|
|
<div class="p-6">
|
|
<div class="mb-6">
|
|
<h2 class="mb-2 text-xl font-semibold text-gray-900">Select Google Sheet</h2>
|
|
|
|
<p class="mb-4 text-sm text-gray-700">
|
|
Search for and select the Google Sheet containing your member data.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Search input -->
|
|
<div class="mb-6">
|
|
<label for="sheet-search" class="mb-2 block text-sm font-medium text-gray-700">
|
|
Search sheets
|
|
</label>
|
|
|
|
<div class="flex">
|
|
<input
|
|
id="sheet-search"
|
|
type="text"
|
|
bind:value={searchQuery}
|
|
placeholder="Type sheet name..."
|
|
class="flex-grow rounded-l-lg border border-gray-300 px-4 py-2 focus:border-transparent focus:ring-2 focus:ring-blue-600"
|
|
onkeydown={(e) => {
|
|
if (e.key === 'Enter') handleSearch();
|
|
}}
|
|
/>
|
|
|
|
<button
|
|
onclick={handleSearch}
|
|
disabled={isLoading || !searchQuery.trim()}
|
|
class="rounded-r-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:bg-gray-400"
|
|
>
|
|
{#if isLoading}
|
|
<div
|
|
class="h-5 w-5 animate-spin rounded-full border-2 border-white border-t-transparent"
|
|
></div>
|
|
{:else}
|
|
Search
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{#if error}
|
|
<div class="mb-6 rounded-lg border border-red-300 bg-red-50 p-4">
|
|
<p class="text-sm text-red-800">{error}</p>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Results -->
|
|
{#if hasSearched}
|
|
<div class="mb-6">
|
|
<h3 class="mb-3 text-sm font-medium text-gray-700">
|
|
{searchResults.length
|
|
? `Found ${searchResults.length} matching sheets`
|
|
: 'No matching sheets found'}
|
|
</h3>
|
|
|
|
{#if searchResults.length}
|
|
<div class="space-y-3">
|
|
{#each searchResults as sheet}
|
|
<div
|
|
class="cursor-pointer rounded-lg border p-4 transition-colors hover:bg-gray-50 {$selectedSheet?.spreadsheetId ===
|
|
(sheet.spreadsheetId || sheet.id)
|
|
? 'border-blue-500 bg-blue-50'
|
|
: 'border-gray-200'}"
|
|
onclick={() => handleSelectSheet(sheet)}
|
|
tabindex="0"
|
|
role="button"
|
|
onkeydown={(e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') handleSelectSheet(sheet);
|
|
}}
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="font-medium text-gray-900">{sheet.name}</p>
|
|
<p class="mt-1 text-xs text-gray-500">ID: {sheet.id}</p>
|
|
</div>
|
|
|
|
<div class="flex items-center">
|
|
{#if sheet.iconLink}
|
|
<img src={sheet.iconLink} alt="Sheet icon" class="mr-2 h-5 w-5" />
|
|
{/if}
|
|
|
|
{#if $selectedSheet?.spreadsheetId === (sheet.spreadsheetId || sheet.id)}
|
|
<svg class="h-5 w-5 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
clip-rule="evenodd"
|
|
/>
|
|
</svg>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{:else}
|
|
<div class="rounded-lg border border-gray-200 bg-gray-50 py-8 text-center">
|
|
<svg
|
|
class="mx-auto h-12 w-12 text-gray-400"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
|
/>
|
|
</svg>
|
|
<p class="mt-2 text-sm text-gray-500">Try a different search term</p>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{:else}
|
|
<!-- If we have recent sheets and haven't searched yet, show them -->
|
|
{#if recentSheets.length > 0 && !hasSearched}
|
|
<div class="mb-6">
|
|
<h3 class="mb-3 text-sm font-medium text-gray-700">Recent sheets</h3>
|
|
|
|
<div class="space-y-3">
|
|
{#each recentSheets as sheet}
|
|
<div
|
|
class="cursor-pointer rounded-lg border p-4 transition-colors hover:bg-gray-50 {$selectedSheet?.spreadsheetId ===
|
|
(sheet.spreadsheetId || sheet.id)
|
|
? 'border-blue-500 bg-blue-50'
|
|
: 'border-gray-200'}"
|
|
onclick={() => handleSelectSheet(sheet)}
|
|
tabindex="0"
|
|
role="button"
|
|
onkeydown={(e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') handleSelectSheet(sheet);
|
|
}}
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="font-medium text-gray-900">{sheet.name}</p>
|
|
<p class="mt-1 text-xs text-gray-500">Recently used</p>
|
|
</div>
|
|
|
|
<div class="flex items-center">
|
|
{#if sheet.iconLink}
|
|
<img src={sheet.iconLink} alt="Sheet icon" class="mr-2 h-5 w-5" />
|
|
{/if}
|
|
|
|
{#if $selectedSheet?.spreadsheetId === (sheet.spreadsheetId || sheet.id)}
|
|
<svg class="h-5 w-5 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
clip-rule="evenodd"
|
|
/>
|
|
</svg>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
|
|
<div class="mt-4 border-t border-gray-200 pt-4">
|
|
<p class="text-xs text-gray-500">Or search for a different sheet above</p>
|
|
</div>
|
|
</div>
|
|
{:else}
|
|
<div class="mb-6 rounded-lg border border-gray-200 bg-gray-50 py-12 text-center">
|
|
<svg
|
|
class="mx-auto h-16 w-16 text-gray-300"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
/>
|
|
</svg>
|
|
<h3 class="mt-2 text-lg font-medium text-gray-900">Search for your sheet</h3>
|
|
<p class="mt-1 text-sm text-gray-500">Enter a name or keyword to find your Google Sheets</p>
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
|
|
<!-- Navigation -->
|
|
<div class="flex justify-between">
|
|
<button
|
|
onclick={() => currentStep.set(1)}
|
|
class="rounded-lg bg-gray-200 px-4 py-2 font-medium text-gray-700 hover:bg-gray-300"
|
|
>
|
|
← Back to Auth
|
|
</button>
|
|
|
|
<button
|
|
onclick={handleContinue}
|
|
disabled={!canProceed}
|
|
class="rounded-lg bg-blue-600 px-4 py-2 font-medium text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:bg-gray-400"
|
|
>
|
|
{canProceed ? 'Continue →' : 'Select a sheet to continue'}
|
|
</button>
|
|
</div>
|
|
</div>
|