Sheet selection and mapping
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user