Fixed other two components

This commit is contained in:
Roman Krček
2025-07-17 21:00:47 +02:00
parent ffa427d42c
commit 4f119dc121
2 changed files with 135 additions and 87 deletions

View File

@@ -695,13 +695,23 @@
{/if} {/if}
<!-- Navigation --> <!-- Navigation -->
<div class="flex justify-end"> <!-- Navigation -->
<div class="flex justify-between">
<button
onclick={() => currentStep.set(2)}
class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg font-medium hover:bg-gray-300"
>
← Back to Sheet Selection
</button>
<button <button
onclick={handleContinue} onclick={handleContinue}
disabled={!mappingComplete} disabled={!mappingComplete}
class="px-4 py-2 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed" class="px-4 py-2 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
> >
{mappingComplete ? 'Continue →' : 'Complete mapping to continue'} {mappingComplete
? 'Continue →'
: 'Select a column mapping'}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -2,6 +2,7 @@
import { selectedSheet, columnMapping, rawSheetData, filteredSheetData, currentStep, sheetData } from '$lib/stores'; import { selectedSheet, columnMapping, rawSheetData, filteredSheetData, currentStep, sheetData } from '$lib/stores';
import type { RowData } from '$lib/stores'; import type { RowData } from '$lib/stores';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { getSheetNames, getSheetData } from '$lib/google';
let searchTerm = ''; let searchTerm = '';
let sortColumn = ''; let sortColumn = '';
@@ -11,6 +12,7 @@
let processedData: any[] = []; let processedData: any[] = [];
let filteredData: any[] = []; let filteredData: any[] = [];
let headers: string[] = []; let headers: string[] = [];
let isLoading = false;
$: { $: {
// Filter data based on search term // Filter data based on search term
@@ -46,68 +48,87 @@
processSheetData(); processSheetData();
}); });
function processSheetData() { // Fetch raw sheet data from Google Sheets if not already loaded
if (!$rawSheetData || $rawSheetData.length === 0 || !$columnMapping) { async function fetchRawSheetData() {
return; if (!$rawSheetData || $rawSheetData.length === 0) {
if (!$selectedSheet) return;
const sheetNames = await getSheetNames($selectedSheet.spreadsheetId);
if (sheetNames.length === 0) return;
const sheetName = sheetNames[0];
const range = `${sheetName}!A:Z`;
const data = await getSheetData($selectedSheet.spreadsheetId, range);
rawSheetData.set(data);
} }
}
// Get headers from the mapping async function processSheetData() {
headers = Object.keys($columnMapping); isLoading = true;
try {
await fetchRawSheetData();
if (!$rawSheetData || $rawSheetData.length === 0 || !$columnMapping) {
return;
}
// Process the data starting from row 2 (skip header row) // Get headers from the mapping
processedData = $rawSheetData.slice(1).map((row, index) => { headers = Object.keys($columnMapping);
const processedRow: any = {
_rowIndex: index + 1, // Store original row index
_isValid: true
};
// Map each column according to the column mapping // Process the data starting from row 2 (skip header row)
for (const [field, columnIndex] of Object.entries($columnMapping)) { processedData = $rawSheetData.slice(1).map((row, index) => {
if (columnIndex !== -1 && columnIndex !== undefined && columnIndex < row.length) { const processedRow: any = {
processedRow[field] = row[columnIndex] || ''; _rowIndex: index + 1, // Store original row index
} else { _isValid: true
processedRow[field] = ''; };
// Only mark as invalid if it's a required field
if (field !== 'alreadyPrinted') { // Map each column according to the column mapping
processedRow._isValid = false; for (const [field, columnIndex] of Object.entries($columnMapping)) {
if (columnIndex !== -1 && columnIndex !== undefined && columnIndex < row.length) {
processedRow[field] = row[columnIndex] || '';
} else {
processedRow[field] = '';
// Only mark as invalid if it's a required field
if (field !== 'alreadyPrinted') {
processedRow._isValid = false;
}
} }
} }
}
// Check if all required fields have values (excluding alreadyPrinted) // Check if all required fields have values (excluding alreadyPrinted)
const requiredFields = ['name', 'surname', 'nationality', 'birthday', 'pictureUrl']; const requiredFields = ['name', 'surname', 'nationality', 'birthday', 'pictureUrl'];
const hasAllRequiredFields = requiredFields.every(field => const hasAllRequiredFields = requiredFields.every(field =>
processedRow[field] && String(processedRow[field]).trim() !== '' processedRow[field] && String(processedRow[field]).trim() !== ''
);
if (!hasAllRequiredFields) {
processedRow._isValid = false;
}
return processedRow;
});
// Initially select rows based on validity and "Already Printed" status
selectedRows = new Set(
processedData
.filter(row => {
if (!row._isValid) return false;
// Check "Already Printed" column value
const alreadyPrinted = row.alreadyPrinted;
if (alreadyPrinted) {
const value = String(alreadyPrinted).toLowerCase().trim();
// If the value is "true", "yes", "1", or any truthy value, don't select
return !(value === 'true' || value === 'yes' || value === '1' || value === 'x');
}
// If empty or falsy, select the row
return true;
})
.map(row => row._rowIndex)
); );
if (!hasAllRequiredFields) { updateSelectAllState();
processedRow._isValid = false; } finally {
} isLoading = false;
}
return processedRow;
});
// Initially select rows based on validity and "Already Printed" status
selectedRows = new Set(
processedData
.filter(row => {
if (!row._isValid) return false;
// Check "Already Printed" column value
const alreadyPrinted = row.alreadyPrinted;
if (alreadyPrinted) {
const value = String(alreadyPrinted).toLowerCase().trim();
// If the value is "true", "yes", "1", or any truthy value, don't select
return !(value === 'true' || value === 'yes' || value === '1' || value === 'x');
}
// If empty or falsy, select the row
return true;
})
.map(row => row._rowIndex)
);
updateSelectAllState();
} }
function toggleRowSelection(rowIndex: number) { function toggleRowSelection(rowIndex: number) {
@@ -193,6 +214,8 @@
const row = processedData.find(r => r._rowIndex === rowIndex); const row = processedData.find(r => r._rowIndex === rowIndex);
return row && row._isValid; return row && row._isValid;
}).length; }).length;
// Allow proceeding only if at least one valid row is selected
$: canProceed = selectedValidCount > 0;
</script> </script>
<div class="p-6"> <div class="p-6">
@@ -244,17 +267,40 @@
</div> </div>
<!-- Stats --> <!-- Stats -->
<div class="mt-4 flex flex-wrap gap-4 text-sm text-gray-600"> <div class="mt-4 flex items-center flex-wrap gap-4 text-sm text-gray-600">
<span>Total rows: {processedData.length}</span> <span>Total rows: {processedData.length}</span>
<span>Valid rows: {processedData.filter(row => row._isValid).length}</span> <span>Valid rows: {processedData.filter(row => row._isValid).length}</span>
<span class="text-orange-600">Already printed: {processedData.filter(row => isRowAlreadyPrinted(row)).length}</span> <span class="text-orange-600">Printed: {processedData.filter(row => isRowAlreadyPrinted(row)).length}</span>
<span>Filtered rows: {filteredData.length}</span> <span>Filtered rows: {filteredData.length}</span>
<span class="font-medium text-blue-600">Selected: {selectedValidCount}</span> <span class="font-medium text-blue-600">Selected: {selectedValidCount}</span>
<button
onclick={processSheetData}
disabled={isLoading}
class="ml-auto inline-flex items-center px-3 py-1 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-wait"
>
{#if isLoading}
<svg class="h-4 w-4 mr-2 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
Refreshing...
{:else}
Refresh Data
{/if}
</button>
</div> </div>
</div> </div>
<!-- Data Table --> <!-- Data Table -->
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden mb-6"> <div class="bg-white border border-gray-200 rounded-lg overflow-hidden mb-6 relative">
{#if isLoading}
<div class="absolute inset-0 flex items-center justify-center bg-white bg-opacity-75 z-10">
<svg class="h-10 w-10 text-blue-600 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
</div>
{/if}
{#if filteredData.length === 0} {#if filteredData.length === 0}
<div class="text-center py-12"> <div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -275,16 +321,16 @@
<input <input
type="checkbox" type="checkbox"
bind:checked={selectAll} bind:checked={selectAll}
on:change={toggleSelectAll} onchange={toggleSelectAll}
class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/> />
</th> </th>
<!-- Column Headers --> <!-- Column Headers -->
{#each headers as header} {#each headers.filter(h => h !== 'alreadyPrinted') as header}
<th <th
class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100"
on:click={() => handleSort(header)} onclick={() => handleSort(header)}
> >
<div class="flex items-center space-x-1"> <div class="flex items-center space-x-1">
<span>{getFieldLabel(header)}</span> <span>{getFieldLabel(header)}</span>
@@ -316,34 +362,18 @@
<input <input
type="checkbox" type="checkbox"
checked={selectedRows.has(row._rowIndex)} checked={selectedRows.has(row._rowIndex)}
on:change={() => toggleRowSelection(row._rowIndex)} onchange={() => toggleRowSelection(row._rowIndex)}
class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
disabled={isRowAlreadyPrinted(row)}
/> />
{#if isRowAlreadyPrinted(row)}
<div class="text-xs text-orange-600 mt-1">Already printed</div>
{/if}
{:else} {:else}
<div class="w-4 h-4 bg-gray-200 rounded"></div> <div class="w-4 h-4 bg-gray-200 rounded"></div>
{/if} {/if}
</td> </td>
<!-- Data Columns --> <!-- Data Columns -->
{#each headers as header} {#each headers.filter(h => h !== 'alreadyPrinted') as header}
<td class="px-3 py-4 text-sm text-gray-900 max-w-xs truncate"> <td class="px-3 py-4 text-sm text-gray-900 max-w-xs truncate">
{#if header === 'alreadyPrinted'} {row[header] || ''}
{#if isRowAlreadyPrinted(row)}
<span class="inline-flex px-2 py-1 text-xs font-medium bg-orange-100 text-orange-800 rounded-full">
Already Printed
</span>
{:else}
<span class="inline-flex px-2 py-1 text-xs font-medium bg-gray-100 text-gray-600 rounded-full">
Not Printed
</span>
{/if}
{:else}
{row[header] || ''}
{/if}
</td> </td>
{/each} {/each}
@@ -390,13 +420,21 @@
{/if} {/if}
<!-- Navigation --> <!-- Navigation -->
<div class="flex justify-end"> <div class="flex justify-between">
<button <button
on:click={handleContinue} onclick={() => currentStep.set(3)}
disabled={selectedValidCount === 0} class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg font-medium hover:bg-gray-300"
>
← Back to Colum Selection
</button>
<button
onclick={handleContinue}
disabled={!canProceed}
class="px-4 py-2 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed" class="px-4 py-2 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
> >
{selectedValidCount > 0 ? `Continue with ${selectedValidCount} ${selectedValidCount === 1 ? 'row' : 'rows'} ` : 'Select rows to continue'} {canProceed
? `Continue with ${selectedValidCount} ${selectedValidCount === 1 ? 'row' : 'rows'} `
: 'Select rows to continue'}
</button> </button>
</div> </div>
</div> </div>