Sheet selection and mapping

This commit is contained in:
Roman Krček
2025-07-17 16:15:23 +02:00
parent f83595f3c3
commit c6ea10e1d6
8 changed files with 750 additions and 9 deletions

View File

@@ -0,0 +1,265 @@
<script lang="ts">
import { availableSheets, selectedSheet, currentStep } from '$lib/stores';
import { searchSheets } from '$lib/google';
import { onMount } from 'svelte';
let searchQuery = '';
let isLoading = false;
let error = '';
let searchResults: any[] = [];
let hasSearched = false;
let recentSheets: 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 => ({
id: 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 saveToRecentSheets(sheet) {
// Create a copy of the sheet object with just the properties we need
const sheetToSave = {
id: sheet.id,
name: sheet.name,
url: sheet.webViewLink || sheet.url,
iconLink: sheet.iconLink
};
// Remove this sheet if it already exists in the list
recentSheets = recentSheets.filter(s => s.id !== sheetToSave.id);
// Add the sheet to the beginning of the list
recentSheets = [sheetToSave, ...recentSheets];
// Keep only up to 3 recent sheets
if (recentSheets.length > 3) {
recentSheets = recentSheets.slice(0, 3);
}
// Save to localStorage
try {
localStorage.setItem(RECENT_SHEETS_KEY, JSON.stringify(recentSheets));
} catch (err) {
console.error('Error saving recent sheets:', err);
}
}
function handleSelectSheet(sheet) {
const sheetData = {
id: sheet.id,
name: sheet.name,
url: sheet.webViewLink || sheet.url
};
selectedSheet.set(sheetData);
saveToRecentSheets(sheet);
}
function handleContinue() {
currentStep.set(3); // Move to the column mapping step
}
</script>
<div class="p-6">
<div class="max-w-2xl mx-auto">
<div class="mb-6">
<h2 class="text-xl font-semibold text-gray-900 mb-2">
Select Google Sheet
</h2>
<p class="text-sm text-gray-700 mb-4">
Search for and select the Google Sheet containing your member data.
</p>
</div>
<!-- Search input -->
<div class="mb-6">
<label for="sheet-search" class="block text-sm font-medium text-gray-700 mb-2">
Search sheets
</label>
<div class="flex">
<input
id="sheet-search"
type="text"
bind:value={searchQuery}
placeholder="Type sheet name..."
class="flex-grow px-4 py-2 border border-gray-300 rounded-l-lg focus:ring-2 focus:ring-blue-600 focus:border-transparent"
/>
<button
on:click={handleSearch}
disabled={isLoading || !searchQuery.trim()}
class="px-4 py-2 bg-blue-600 text-white rounded-r-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
>
{#if isLoading}
<div class="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
{:else}
Search
{/if}
</button>
</div>
</div>
{#if error}
<div class="bg-red-50 border border-red-300 rounded-lg p-4 mb-6">
<p class="text-sm text-red-800">{error}</p>
</div>
{/if}
<!-- Results -->
{#if hasSearched}
<div class="mb-6">
<h3 class="text-sm font-medium text-gray-700 mb-3">
{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="border rounded-lg p-4 cursor-pointer transition-colors hover:bg-gray-50
{$selectedSheet?.id === sheet.id ? 'border-blue-500 bg-blue-50' : 'border-gray-200'}"
on:click={() => handleSelectSheet(sheet)}
>
<div class="flex items-center justify-between">
<div>
<p class="font-medium text-gray-900">{sheet.name}</p>
<p class="text-xs text-gray-500 mt-1">ID: {sheet.id}</p>
</div>
<div class="flex items-center">
{#if sheet.iconLink}
<img src={sheet.iconLink} alt="Sheet icon" class="w-5 h-5 mr-2" />
{/if}
{#if $selectedSheet?.id === sheet.id}
<svg class="w-5 h-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="text-center py-8 bg-gray-50 rounded-lg border border-gray-200">
<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="text-sm font-medium text-gray-700 mb-3">
Recent sheets
</h3>
<div class="space-y-3">
{#each recentSheets as sheet}
<div
class="border rounded-lg p-4 cursor-pointer transition-colors hover:bg-gray-50
{$selectedSheet?.id === sheet.id ? 'border-blue-500 bg-blue-50' : 'border-gray-200'}"
on:click={() => handleSelectSheet(sheet)}
>
<div class="flex items-center justify-between">
<div>
<p class="font-medium text-gray-900">{sheet.name}</p>
<p class="text-xs text-gray-500 mt-1">Recently used</p>
</div>
<div class="flex items-center">
{#if sheet.iconLink}
<img src={sheet.iconLink} alt="Sheet icon" class="w-5 h-5 mr-2" />
{/if}
{#if $selectedSheet?.id === sheet.id}
<svg class="w-5 h-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="border-t border-gray-200 mt-4 pt-4">
<p class="text-xs text-gray-500">
Or search for a different sheet above
</p>
</div>
</div>
{:else}
<div class="text-center py-12 bg-gray-50 rounded-lg border border-gray-200 mb-6">
<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}
<!-- Continue button -->
{#if $selectedSheet}
<div class="mt-6 flex justify-end">
<button
on:click={handleContinue}
class="px-4 py-2 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700"
>
Continue →
</button>
</div>
{/if}
</div>
</div>