diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 317d1c4..a69ccc3 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -4,4 +4,5 @@ - It's important to only use modern Svelte 5 syntax, runes, and features. - Use styling from ".github/styling.md" for any UI components. - Refer to the ".github/core-instructions.md" for the overall structure of the application. -- Generate ".github/done.md" file to see what is done and what is not. Check it when you start and finish a task. \ No newline at end of file +- Generate ".github/done.md" file to see what is done and what is not. Check it when you start and finish a task. +- Remain consistent in styling and code structure. \ No newline at end of file diff --git a/.github/core-instructions.md b/.github/core-instructions.md index 3ef5445..2d3ca24 100644 --- a/.github/core-instructions.md +++ b/.github/core-instructions.md @@ -18,7 +18,6 @@ | Face detection | **Mediapipe BlazeFace via TF.js (lite)** in a Web Worker. Confidence ≥ 0.8. | | Virtual list | `svelte-virtual` (react‑window equivalent). | | PDF | **pdf-lib** (via ESM import). Embed custom font **Lato** (ttf provided in `/static/fonts`). | - --- ## 1. User Stories & Acceptance Criteria diff --git a/package-lock.json b/package-lock.json index 399a93a..86a51c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,10 @@ "@types/gapi.client.drive": "^3.0.15", "@types/gapi.client.sheets": "^4.0.20201031", "@types/google.accounts": "^0.0.17", + "@types/uuid": "^10.0.0", "idb": "^8.0.3", - "pdf-lib": "^1.17.1" + "pdf-lib": "^1.17.1", + "uuid": "^11.1.0" }, "devDependencies": { "@sveltejs/adapter-node": "^5.2.12", @@ -1565,6 +1567,12 @@ "integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==", "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, "node_modules/@webgpu/types": { "version": "0.1.38", "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz", @@ -3191,7 +3199,6 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "dev": true, "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" diff --git a/package.json b/package.json index e5bd68f..829bc70 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,9 @@ "@types/gapi.client.drive": "^3.0.15", "@types/gapi.client.sheets": "^4.0.20201031", "@types/google.accounts": "^0.0.17", + "@types/uuid": "^10.0.0", "idb": "^8.0.3", - "pdf-lib": "^1.17.1" + "pdf-lib": "^1.17.1", + "uuid": "^11.1.0" } } diff --git a/src/lib/components/Wizard.svelte b/src/lib/components/Wizard.svelte index 5a1e140..5b7a100 100644 --- a/src/lib/components/Wizard.svelte +++ b/src/lib/components/Wizard.svelte @@ -1,14 +1,20 @@ +
-

Map Columns

-

Column mapping functionality will be implemented here.

+
+
+

+ Select Sheet and Map Columns +

+ +

+ First, select which sheet contains your member data, then map the columns to the required fields. +

+
+ + +
+

+ Step 1: Select Sheet +

+ + {#if isLoadingSheets} +
+
+ Loading sheets... +
+ {:else if error} +
+

{error}

+ +
+ {:else if availableSheets.length === 0} +
+ + + +

No sheets found

+

+ This spreadsheet doesn't appear to contain any sheets. +

+
+ {:else} +
+

+ Spreadsheet: {$selectedSheet?.name} +

+ +
+ + +
+ {#each availableSheets as sheetName} +
handleSheetSelect(sheetName)} + > +
+
+
+ {#if selectedSheetName === sheetName} +
+ {/if} +
+
+ +
+

{sheetName}

+
+ + {#if selectedSheetName === sheetName} +
+ + + +
+ {/if} +
+
+ {/each} +
+
+
+ {/if} +
+ + +
+

+ Step 2: Map Columns +

+ + {#if isLoadingData} +
+
+ Loading sheet data... +
+ {:else if sheetHeaders.length === 0} +
+

Select a sheet above to map columns

+
+ {:else} +
+

+ Map the columns from your sheet to the required fields: +

+ + +
+ {#each requiredFields as field} +
+
+ +
+ +
+ +
+
+ {/each} +
+ + + {#if previewData.length > 0} +
+

Data Preview:

+
+ + + + {#each sheetHeaders as header, index} + + {/each} + + + + {#each previewData as row} + + {#each row as cell, index} + + {/each} + + {/each} + +
+ {header} + {#if Object.values(mappedIndices).includes(index)} +
+ {requiredFields.find(f => mappedIndices[f.key] === index)?.label} +
+ {/if} +
+ {cell} +
+
+
+ {/if} + + + {#if mappingComplete} +
+

+ ✓ All required fields are mapped! You can continue to the next step. +

+
+ {:else} +
+

+ Please map all required fields to continue. +

+
+ {/if} +
+ {/if} +
+ + +
+ +
+
diff --git a/src/lib/components/wizard/StepSheetSearch.svelte b/src/lib/components/wizard/StepSheetSearch.svelte index e69de29..94feff7 100644 --- a/src/lib/components/wizard/StepSheetSearch.svelte +++ b/src/lib/components/wizard/StepSheetSearch.svelte @@ -0,0 +1,265 @@ + + +
+
+
+

+ Select Google Sheet +

+ +

+ Search for and select the Google Sheet containing your member data. +

+
+ + +
+ + +
+ + + +
+
+ + {#if error} +
+

{error}

+
+ {/if} + + + {#if hasSearched} +
+

+ {searchResults.length + ? `Found ${searchResults.length} matching sheets` + : 'No matching sheets found'} +

+ + {#if searchResults.length} +
+ {#each searchResults as sheet} +
handleSelectSheet(sheet)} + > +
+
+

{sheet.name}

+

ID: {sheet.id}

+
+ +
+ {#if sheet.iconLink} + Sheet icon + {/if} + + {#if $selectedSheet?.id === sheet.id} + + + + {/if} +
+
+
+ {/each} +
+ {:else} +
+ + + +

Try a different search term

+
+ {/if} +
+ {:else} + + {#if recentSheets.length > 0 && !hasSearched} +
+

+ Recent sheets +

+ +
+ {#each recentSheets as sheet} +
handleSelectSheet(sheet)} + > +
+
+

{sheet.name}

+

Recently used

+
+ +
+ {#if sheet.iconLink} + Sheet icon + {/if} + + {#if $selectedSheet?.id === sheet.id} + + + + {/if} +
+
+
+ {/each} +
+ +
+

+ Or search for a different sheet above +

+
+
+ {:else} +
+ + + +

Search for your sheet

+

+ Enter a name or keyword to find your Google Sheets +

+
+ {/if} + {/if} + + + {#if $selectedSheet} +
+ +
+ {/if} +
+
diff --git a/src/lib/google.ts b/src/lib/google.ts index 91ec77c..acf9bdd 100644 --- a/src/lib/google.ts +++ b/src/lib/google.ts @@ -100,6 +100,22 @@ export async function searchSheets(query: string) { return response.result.files || []; } +export async function getSheetNames(spreadsheetId: string) { + if (!gapi.client.sheets) { + throw new Error('Google Sheets API not loaded'); + } + const response = await gapi.client.sheets.spreadsheets.get({ + spreadsheetId, + fields: 'sheets.properties' + }); + + if (!response.result.sheets) { + return []; + } + + return response.result.sheets.map(sheet => sheet.properties?.title || ''); +} + export async function getSheetData(spreadsheetId: string, range: string) { if (!gapi.client.sheets) { throw new Error('Google Sheets API not loaded');