Compare commits
3 Commits
1e96668e48
...
2f730fdbbb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f730fdbbb | ||
|
|
b5814ed552 | ||
|
|
052e5975fd |
@@ -4,6 +4,7 @@
|
|||||||
import StepSheetSearch from './wizard/StepSheetSearch.svelte';
|
import StepSheetSearch from './wizard/StepSheetSearch.svelte';
|
||||||
import StepColumnMap from './wizard/StepColumnMap.svelte';
|
import StepColumnMap from './wizard/StepColumnMap.svelte';
|
||||||
import StepRowFilter from './wizard/StepRowFilter.svelte';
|
import StepRowFilter from './wizard/StepRowFilter.svelte';
|
||||||
|
import StepCardDetails from './wizard/StepCardDetails.svelte';
|
||||||
import StepGallery from './wizard/StepGallery.svelte';
|
import StepGallery from './wizard/StepGallery.svelte';
|
||||||
import StepGenerate from './wizard/StepGenerate.svelte';
|
import StepGenerate from './wizard/StepGenerate.svelte';
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
StepSheetSearch,
|
StepSheetSearch,
|
||||||
StepColumnMap,
|
StepColumnMap,
|
||||||
StepRowFilter,
|
StepRowFilter,
|
||||||
|
StepCardDetails,
|
||||||
StepGallery,
|
StepGallery,
|
||||||
StepGenerate
|
StepGenerate
|
||||||
];
|
];
|
||||||
@@ -21,6 +23,7 @@
|
|||||||
'Select Sheet',
|
'Select Sheet',
|
||||||
'Map Columns',
|
'Map Columns',
|
||||||
'Filter Rows',
|
'Filter Rows',
|
||||||
|
'Card Details',
|
||||||
'Review Photos',
|
'Review Photos',
|
||||||
'Generate PDFs'
|
'Generate PDFs'
|
||||||
];
|
];
|
||||||
|
|||||||
72
src/lib/components/wizard/StepCardDetails.svelte
Normal file
72
src/lib/components/wizard/StepCardDetails.svelte
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { currentStep, cardDetails } from '$lib/stores';
|
||||||
|
import Navigator from './subcomponents/Navigator.svelte';
|
||||||
|
|
||||||
|
// Initialize from localStorage if available, otherwise from the store
|
||||||
|
let homeSection = $state(
|
||||||
|
(typeof window !== 'undefined' && window.localStorage.getItem('homeSection')) ||
|
||||||
|
$cardDetails.homeSection
|
||||||
|
);
|
||||||
|
let validityStart = $state($cardDetails.validityStart || new Date().toISOString().split('T')[0]);
|
||||||
|
|
||||||
|
// Persist homeSection to localStorage whenever it changes
|
||||||
|
$effect(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem('homeSection', homeSection);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let canProceed = $derived(homeSection.trim() !== '' && validityStart.trim() !== '');
|
||||||
|
|
||||||
|
function handleContinue() {
|
||||||
|
cardDetails.set({ homeSection, validityStart });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="mb-6">
|
||||||
|
<h2 class="mb-2 text-xl font-semibold text-gray-900">Enter Card Details</h2>
|
||||||
|
<p class="mb-4 text-sm text-gray-700">
|
||||||
|
Please provide the following details to be printed on the cards.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label for="homeSection" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Home Section
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="homeSection"
|
||||||
|
type="text"
|
||||||
|
bind:value={homeSection}
|
||||||
|
placeholder="e.g., ESN VUT Brno"
|
||||||
|
class="w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-gray-900 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="validityStart" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Card Validity Start Date
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="validityStart"
|
||||||
|
type="date"
|
||||||
|
bind:value={validityStart}
|
||||||
|
class="w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-gray-900 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
||||||
|
/>
|
||||||
|
<p class="mt-2 text-xs text-gray-500">Default date is today, but future date can be selected.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-10">
|
||||||
|
<Navigator
|
||||||
|
canProceed={canProceed}
|
||||||
|
currentStep={currentStep}
|
||||||
|
onForward={handleContinue}
|
||||||
|
textBack="Back to Row Filtering"
|
||||||
|
textForwardEnabled="Continue to Photo Review"
|
||||||
|
textForwardDisabled="Please fill out all fields"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -488,42 +488,42 @@
|
|||||||
|
|
||||||
{#if hasSavedMapping && !showMappingEditor}
|
{#if hasSavedMapping && !showMappingEditor}
|
||||||
<!-- Simplified view when we have saved mapping -->
|
<!-- Simplified view when we have saved mapping -->
|
||||||
<div class="mb-6 rounded-lg border border-green-200 bg-green-50 p-6">
|
<div class="mb-6 rounded-lg border border-blue-200 bg-blue-50 p-4">
|
||||||
<div class="text-center">
|
<div class="flex">
|
||||||
<svg class="mx-auto mb-4 h-16 w-16 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
<div class="flex-shrink-0">
|
||||||
<path
|
<svg
|
||||||
fill-rule="evenodd"
|
class="h-5 w-5 text-blue-400"
|
||||||
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"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
clip-rule="evenodd"
|
viewBox="0 0 20 20"
|
||||||
/>
|
fill="currentColor"
|
||||||
</svg>
|
aria-hidden="true"
|
||||||
<h3 class="mb-3 text-xl font-semibold text-green-800">Configuration Complete</h3>
|
>
|
||||||
<p class="mb-2 text-green-700">
|
|
||||||
<span class="font-medium">Spreadsheet:</span>
|
|
||||||
{savedSheetInfo?.name}
|
|
||||||
</p>
|
|
||||||
<p class="mb-2 text-green-700">
|
|
||||||
<span class="font-medium">Sheet:</span>
|
|
||||||
{selectedSheetName}
|
|
||||||
</p>
|
|
||||||
<p class="mb-6 text-green-700">
|
|
||||||
Column mapping loaded from your previous session.<br />
|
|
||||||
Everything is ready to proceed to the next step.
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onclick={handleShowEditor}
|
|
||||||
class="inline-flex items-center rounded-lg border border-green-300 px-4 py-2 text-sm font-medium text-green-700 transition-colors hover:bg-green-100 hover:text-green-900"
|
|
||||||
>
|
|
||||||
<svg class="mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
fill-rule="evenodd"
|
||||||
stroke-linejoin="round"
|
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||||
stroke-width="2"
|
clip-rule="evenodd"
|
||||||
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Make changes if needed
|
</div>
|
||||||
</button>
|
<div class="ml-3 flex-1 md:flex md:justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-medium text-blue-800">Saved Configuration Found</h3>
|
||||||
|
<div class="mt-2 text-sm text-blue-700">
|
||||||
|
<p>
|
||||||
|
Using saved mapping for sheet <span class="font-semibold">"{selectedSheetName}"</span> from
|
||||||
|
spreadsheet <span class="font-semibold">"{savedSheetInfo?.name}"</span>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 md:mt-0 md:ml-6">
|
||||||
|
<button
|
||||||
|
onclick={handleShowEditor}
|
||||||
|
class="whitespace-nowrap rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||||
|
>
|
||||||
|
Edit Mapping
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -735,16 +735,55 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Mapping status -->
|
|
||||||
{#if mappingComplete}
|
{#if mappingComplete}
|
||||||
<div class="rounded border border-green-200 bg-green-50 p-3">
|
<div class="rounded-md border border-green-200 bg-green-50 p-4">
|
||||||
<p class="text-sm text-green-800">
|
<div class="flex">
|
||||||
✓ All required fields are mapped! You can continue to the next step.
|
<div class="flex-shrink-0">
|
||||||
</p>
|
<svg
|
||||||
|
class="h-5 w-5 text-green-400"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<p class="text-sm font-medium text-green-800">
|
||||||
|
All required fields are mapped. You can now proceed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="rounded border border-yellow-200 bg-yellow-50 p-3">
|
<div class="rounded-md bg-yellow-50 p-4">
|
||||||
<p class="text-sm text-yellow-800">Please map all required fields to continue.</p>
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<svg
|
||||||
|
class="h-5 w-5 text-yellow-400"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 3.01-1.742 3.01H4.42c-1.53 0-2.493-1.676-1.743-3.01l5.58-9.92zM10 5a1 1 0 011 1v3a1 1 0 01-2 0V6a1 1 0 011-1zm1 5a1 1 0 10-2 0v2a1 1 0 102 0v-2z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<p class="text-sm text-yellow-800">
|
||||||
|
Please map all required fields (<span class="text-red-500">*</span>) to continue.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
import {
|
import {
|
||||||
BORDER_CONFIG,
|
BORDER_CONFIG,
|
||||||
TEXT_CONFIG,
|
TEXT_CONFIG,
|
||||||
PLACEHOLDER_CONFIG,
|
|
||||||
calculateGrid,
|
calculateGrid,
|
||||||
getAbsolutePositionPt,
|
getAbsolutePositionPt,
|
||||||
getAbsolutePhotoDimensionsPt,
|
getAbsolutePhotoDimensionsPt,
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -144,3 +144,12 @@ export const pdfGenerationStatus = writable<{
|
|||||||
stage: 'preparing',
|
stage: 'preparing',
|
||||||
progress: 0
|
progress: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Card details for generation
|
||||||
|
export const cardDetails = writable<{
|
||||||
|
homeSection: string;
|
||||||
|
validityStart: string;
|
||||||
|
}>({
|
||||||
|
homeSection: '',
|
||||||
|
validityStart: ''
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user