Reworked row filter

This commit is contained in:
Roman Krček
2025-07-31 08:28:13 +02:00
parent 052e5975fd
commit b5814ed552

View File

@@ -4,8 +4,7 @@
columnMapping, columnMapping,
rawSheetData, rawSheetData,
filteredSheetData, filteredSheetData,
currentStep, currentStep
sheetData
} from '$lib/stores'; } from '$lib/stores';
import Navigator from './subcomponents/Navigator.svelte'; import Navigator from './subcomponents/Navigator.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@@ -16,30 +15,52 @@
let sortDirection: 'asc' | 'desc' = 'asc'; let sortDirection: 'asc' | 'desc' = 'asc';
let selectedRows = new Set<number>(); let selectedRows = new Set<number>();
let selectAll = false; let selectAll = false;
let processedData: any[] = []; let processedData = $state<any[]>([]);
let filteredData: any[] = [];
let headers: string[] = []; let headers: string[] = [];
let isLoading = false; let isLoading = $state(false);
let showAlreadyPrinted = $state(false);
$: { // Use $state for displayData instead of $derived to avoid TypeScript errors
// Filter data based on search term let displayData = $state<any[]>([]);
// Update displayData whenever relevant values change
$effect(() => {
// Debug log at the start
console.log('Updating displayData from processedData:', processedData);
// If processedData is empty, return empty array
if (!processedData || !processedData.length) {
displayData = [];
return;
}
// Clone array to avoid mutations
let data = [...processedData];
console.log('Initial data length:', data.length);
// 1. Filter by search term
if (searchTerm.trim()) { if (searchTerm.trim()) {
filteredData = processedData.filter((row) => data = data.filter((row) =>
Object.values(row).some((value) => Object.values(row).some((value) =>
String(value).toLowerCase().includes(searchTerm.toLowerCase()) String(value)
.toLowerCase()
.includes(searchTerm.toLowerCase())
) )
); );
} else { console.log('After search term filter:', data.length);
filteredData = processedData;
} }
}
$: { // 2. Filter out already printed rows if collapsed
// Sort data if sort column is selected if (!showAlreadyPrinted) {
if (sortColumn && filteredData.length > 0) { data = data.filter((row) => !isRowAlreadyPrinted(row));
filteredData = [...filteredData].sort((a, b) => { console.log('After already printed filter:', data.length);
const aVal = String(a[sortColumn]).toLowerCase(); }
const bVal = String(b[sortColumn]).toLowerCase();
// 3. Sort the data
if (sortColumn) {
data = [...data].sort((a, b) => {
const aVal = String(a[sortColumn] || '').toLowerCase();
const bVal = String(b[sortColumn] || '').toLowerCase();
if (sortDirection === 'asc') { if (sortDirection === 'asc') {
return aVal.localeCompare(bVal); return aVal.localeCompare(bVal);
@@ -48,7 +69,10 @@
} }
}); });
} }
}
console.log('Final filtered data:', data);
displayData = data;
});
onMount(() => { onMount(() => {
console.log('StepRowFilter mounted'); console.log('StepRowFilter mounted');
@@ -57,13 +81,25 @@
// Fetch raw sheet data from Google Sheets if not already loaded // Fetch raw sheet data from Google Sheets if not already loaded
async function fetchRawSheetData() { async function fetchRawSheetData() {
console.log("Fetching raw sheet data..."); console.log('Fetching raw sheet data...');
const sheetNames = await getSheetNames($selectedSheet.spreadsheetId); try {
if (sheetNames.length === 0) return; if (!$selectedSheet) {
const sheetName = sheetNames[0]; console.error('No sheet selected');
const range = `${sheetName}!A:Z`; return;
const data = await getSheetData($selectedSheet.spreadsheetId, range); }
rawSheetData.set(data);
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);
console.log('Fetched data:', data);
rawSheetData.set(data);
} catch (e) {
console.error('Error fetching raw sheet data:', e);
// Re-throw the error to be caught by the caller
throw e;
}
} }
async function processSheetData() { async function processSheetData() {
@@ -74,6 +110,13 @@
await fetchRawSheetData(); await fetchRawSheetData();
// Debug logging
console.log('Raw sheet data:', $rawSheetData);
console.log('Column mapping:', $columnMapping);
// Clear existing state before processing new data
selectedRows = new Set();
// Process the data starting from row 2 (skip header row) // Process the data starting from row 2 (skip header row)
processedData = $rawSheetData.slice(1).map((row, index) => { processedData = $rawSheetData.slice(1).map((row, index) => {
const processedRow: any = { const processedRow: any = {
@@ -119,9 +162,20 @@
}); });
const initialSelection = rowsToConsider.map((row) => row._rowIndex); const initialSelection = rowsToConsider.map((row) => row._rowIndex);
selectedRows = new Set(initialSelection); // Make sure we create a completely new Set for reactivity
selectedRows = new Set([...initialSelection]);
// Update UI state
updateSelectAllState(); updateSelectAllState();
updateSelectedValidCount();
// Debug logging
console.log('Processed data:', processedData);
console.log('Display data:', displayData);
console.log('Selected rows:', selectedRows);
console.log('Selected valid count after initialization:', selectedValidCount);
} catch (e) {
console.error('Error processing sheet data:', e);
} finally { } finally {
isLoading = false; isLoading = false;
} }
@@ -133,43 +187,62 @@
} else { } else {
selectedRows.add(rowIndex); selectedRows.add(rowIndex);
} }
selectedRows = new Set(selectedRows); // Trigger reactivity // Force reactivity with new Set
selectedRows = new Set([...selectedRows]);
console.log('Toggled row selection, new selectedRows size:', selectedRows.size);
updateSelectAllState(); updateSelectAllState();
updateSelectedValidCount();
console.log('After toggle - canProceed:', canProceed);
console.log('After toggle - selectedValidCount > 0:', selectedValidCount > 0);
console.log('After toggle - selectedValidCount:', selectedValidCount);
} }
function toggleSelectAll() { function toggleSelectAll() {
if (selectAll) { // Toggle selectAll state first
// Deselect all visible valid rows selectAll = !selectAll;
filteredData.forEach((row) => { console.log('Toggle select all clicked, new state:', selectAll);
if (!selectAll) {
// If now unchecked, deselect all visible valid rows
displayData.forEach((row) => {
if (row._isValid) { if (row._isValid) {
selectedRows.delete(row._rowIndex); selectedRows.delete(row._rowIndex);
} }
}); });
} else { } else {
// Select all visible valid rows that aren't already printed // If now checked, select all visible valid rows
const rowsToSelect = filteredData.filter( displayData.forEach((row) => {
(row) => row._isValid && !isRowAlreadyPrinted(row) && !selectedRows.has(row._rowIndex) if (row._isValid) {
); selectedRows.add(row._rowIndex);
}
for (const row of rowsToSelect) { });
selectedRows.add(row._rowIndex);
}
} }
selectedRows = new Set(selectedRows);
updateSelectAllState(); // Force reactivity with new Set
selectedRows = new Set([...selectedRows]);
console.log('Toggled select all, new selectedRows size:', selectedRows.size);
updateSelectedValidCount();
console.log('Selected valid count after toggleSelectAll:', selectedValidCount);
} }
function updateSelectAllState() { function updateSelectAllState() {
const visibleValidUnprintedRows = filteredData.filter( // Find all valid rows that are currently visible
(row) => row._isValid && !isRowAlreadyPrinted(row) const visibleValidRows = displayData.filter(row => row._isValid);
);
const selectedVisibleValidUnprintedRows = visibleValidUnprintedRows.filter((row) => if (visibleValidRows.length === 0) {
selectedRows.has(row._rowIndex) // No valid rows to select
); selectAll = false;
return;
selectAll = }
visibleValidUnprintedRows.length > 0 &&
selectedVisibleValidUnprintedRows.length === visibleValidUnprintedRows.length; // Check if all visible valid rows are selected
const allSelected = visibleValidRows.every(row => selectedRows.has(row._rowIndex));
// Update selectAll state
selectAll = allSelected;
console.log('updateSelectAllState: selectAll =', selectAll,
', visibleValidRows =', visibleValidRows.length,
', selectedRows size =', selectedRows.size);
} }
function handleSort(column: string) { function handleSort(column: string) {
@@ -211,12 +284,41 @@
filteredSheetData.set(selectedData); filteredSheetData.set(selectedData);
} }
$: selectedValidCount = Array.from(selectedRows).filter((rowIndex) => { // Use $state for selectedValidCount
const row = processedData.find((r) => r._rowIndex === rowIndex); let selectedValidCount = $state(0);
return row && row._isValid;
}).length; // Create a dedicated function to recalculate selectedValidCount
function updateSelectedValidCount() {
// Get array of row indices
const rowIndices = Array.from(selectedRows);
console.log('Selected row indices:', rowIndices);
// Count valid rows
let count = 0;
for (const rowIndex of rowIndices) {
// Find the row in processedData
const row = processedData.find(r => r._rowIndex === rowIndex);
if (row && row._isValid) {
count++;
}
}
console.log('Setting selectedValidCount to:', count, 'from', selectedValidCount);
selectedValidCount = count;
}
// Update count when selectedRows or processedData changes
$effect(() => {
// Track dependencies explicitly
const rowsSize = selectedRows.size;
const dataSize = processedData.length;
console.log('Effect triggered - selectedRows size:', rowsSize, 'processedData size:', dataSize);
updateSelectedValidCount();
});
// Allow proceeding only if at least one valid row is selected // Allow proceeding only if at least one valid row is selected
$: canProceed = selectedValidCount > 0; let canProceed = $derived(selectedValidCount > 0);
</script> </script>
<div class="p-6"> <div class="p-6">
@@ -269,41 +371,57 @@
<span class="text-orange-600" <span class="text-orange-600"
>Printed: {processedData.filter((row) => isRowAlreadyPrinted(row)).length}</span >Printed: {processedData.filter((row) => isRowAlreadyPrinted(row)).length}</span
> >
<span>Filtered rows: {filteredData.length}</span> <span>Filtered rows: {displayData.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} <div class="ml-auto flex items-center gap-4">
disabled={isLoading} <button
class="ml-auto inline-flex items-center rounded-md bg-blue-600 px-3 py-1 text-sm font-medium text-white hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:outline-none disabled:cursor-wait disabled:opacity-50" class="text-sm text-gray-600 hover:text-gray-900"
> onclick={() => (showAlreadyPrinted = !showAlreadyPrinted)}
{#if isLoading} >
<svg {showAlreadyPrinted ? 'Hide' : 'Show'} Printed ({processedData.filter((row) =>
class="mr-2 h-4 w-4 animate-spin" isRowAlreadyPrinted(row)
xmlns="http://www.w3.org/2000/svg" ).length})
fill="none" </button>
viewBox="0 0 24 24"
> <button
<circle onclick={processSheetData}
class="opacity-25" disabled={isLoading}
cx="12" class="inline-flex items-center rounded-md bg-blue-600 px-3 py-1 text-sm font-medium text-white hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:outline-none disabled:cursor-wait disabled:opacity-50"
cy="12" >
r="10" {#if isLoading}
stroke="currentColor" <svg
stroke-width="4" class="mr-2 h-4 w-4 animate-spin"
/> xmlns="http://www.w3.org/2000/svg"
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" /> fill="none"
</svg> viewBox="0 0 24 24"
Refreshing... >
{:else} <circle
Refresh Data class="opacity-25"
{/if} cx="12"
</button> 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> </div>
<!-- Data Table --> <!-- Data Table -->
<div class="relative mb-6 overflow-hidden rounded-lg border border-gray-200 bg-white"> <div class="relative mb-6 overflow-hidden rounded-lg border border-gray-200 bg-white">
{#if filteredData.length === 0 && !isLoading} {#if displayData.length === 0 && !isLoading}
<div class="py-12 text-center"> <div class="py-12 text-center">
<svg <svg
class="mx-auto h-12 w-12 text-gray-400" class="mx-auto h-12 w-12 text-gray-400"
@@ -332,8 +450,12 @@
<th class="px-3 py-3 text-left"> <th class="px-3 py-3 text-left">
<input <input
type="checkbox" type="checkbox"
bind:checked={selectAll} checked={selectAll}
onchange={toggleSelectAll} onclick={(e) => {
// Use event.preventDefault() to avoid default checkbox behavior
e.preventDefault();
toggleSelectAll();
}}
class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
disabled={isLoading} disabled={isLoading}
/> />
@@ -406,7 +528,7 @@
{/each} {/each}
{:else} {:else}
<!-- Actual data rows --> <!-- Actual data rows -->
{#each filteredData as row} {#each displayData as row}
<tr <tr
class="hover:bg-gray-50 {!row._isValid ? 'opacity-50' : ''} {isRowAlreadyPrinted( class="hover:bg-gray-50 {!row._isValid ? 'opacity-50' : ''} {isRowAlreadyPrinted(
row row
@@ -420,7 +542,11 @@
<input <input
type="checkbox" type="checkbox"
checked={selectedRows.has(row._rowIndex)} checked={selectedRows.has(row._rowIndex)}
onchange={() => toggleRowSelection(row._rowIndex)} onclick={(e) => {
// Use event.preventDefault() to avoid default checkbox behavior
e.preventDefault();
toggleRowSelection(row._rowIndex);
}}
class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/> />
{:else} {:else}