bryanmylee / svelte-headless-table

Unopinionated and extensible data tables for Svelte
https://svelte-headless-table.bryanmylee.com/
487 stars 32 forks source link

Incorrect rows being updated #121

Closed ethanfox closed 1 year ago

ethanfox commented 1 year ago

In the video below you can see that I have a table with a thumbnail cell that allows you to change the picture when clicking it. As shown in the video the UI does not correctly update but the data does. When closing the table the correct pictures show in the correct rows.

Also, the loading indicator also does not show on the right cell even though they are all the same component iterated through an array.

I am using the svelte-headless-table library to make this table, and the data is in JSON format from supabase.

https://youtube.com/shorts/8kakKWNrIMI?feature=share

Here is the code that I think might be the issue but since that data gets updated correctly it might be something else. I need the UI to update correctly.

Update Database

async function updateThumbnail() {
        console.log('updating thumbnail', $rowIndex);

        // This is the line that adds the new pictureURL to the database
        $documentBody[$rowIndex].thumbnail_URL = pictureURL;

        const { error } = await supabaseClient
            .from('documents')
            .update({ document_body: $documentBody })
            .eq('document_id', $documentID);
        if (error) {
            console.log('THERE WAS AN ERROR');
            showError = true;
            errorMessage = 'There was an error updating your contact. Error: ' + error;
            console.log(error);
        } else if (error == null) {
            showError = false;
            console.log('Update successful');
            getMaterialSchedule();
            setTimeout(getAvatarURL, 3000);
        } else {
            showError = true;
            errorMessage = 'There was an error updating your contact. Error: ' + error;
            console.log('Update failed');
        }
    }

Cell Component

<script lang="ts">
    import { supabaseClient } from '$lib/db';

    import { slide, fade, fly } from 'svelte/transition';
    import { onMount } from 'svelte';
    import { loadingAction } from 'svelte-legos';
    import {
        documentID,
        documentName,
        documentProjectID,
        documentFirmID,
        documentType,
        documentLastModified,
        documentLastModifiedBy,
        documentNotes,
        documentBody,
        showDocumentFrame,
        showCreateNewDocument,
        rowIndex
    } from '/src/stores/documentStore';
    export let pictureURL;

    export let thumbnailFit;

    //export let row; /*: BodyRow<Item>*/

    let showError = false;
    let errorMessage = '';
    let showUI = false;
    let showFitAndFill = false;
    let src: string;
    let loading = true;
    let realPhotoUrl = '';

    let hasPhoto = false;

    let size = 10;
    let url: string;
    let uploading = false;
    let files: FileList;

     // handle file upload and only allow one file and for the file to be either .jpg or .png
    // if the file is larger then 5mb then it will not be accepted

    function handleFileChange(e) {
        files = e.target.files;
        if (files.length > 1) {
            console.log('Only one file allowed');
            return;
        }
        const file = files[0];
        if (file.type !== 'image/jpeg' && file.type !== 'image/png') {
            showError = true;
            errorMessage = 'Only .jpg and .png files allowed';
            console.log('Only .jpg and .png files allowed');
            return;
        }
        if (file.size > 5000000) {
            showError = true;
            errorMessage = 'File size too large (5mb max)';
            console.log('File size too large');
            return;
        }
        uploadAvatar();
    }
    // GET URL
    async function getAvatarURL() {
        if (pictureURL == 'null' || pictureURL == undefined || pictureURL == '--') {
            hasPhoto = false;

            console.log('no picture');
        } else {
            const { data } = await supabaseClient.storage.from('documents').getPublicUrl(pictureURL);
            if (data) {
                realPhotoUrl = data.publicUrl;
                src = realPhotoUrl;
                console.log('HAS picture');
                loading = false;

                hasPhoto = true;
            }
        }
    }

    // HANDLE UPLOAD
    async function uploadAvatar() {
        console.log('UPLOADING', $rowIndex);
        loading = true;
        const file = files[0];
        const fileExt = file.name.split('.').pop();
        const filePath = `${$documentID}_${Date.now()}.${fileExt}`;

        const { data, error } = await supabaseClient.storage
            .from('documents')
            .upload('materialScheduleRows/' + filePath, file, {
                upsert: true
            });
        if (data) {
            url = 'materialScheduleRows/' + filePath;

            pictureURL = url;
            updateThumbnail();
        } else if (error) {
            showError = true;
            errorMessage = 'There was an error uploading your photo. Error: ' + error;
            console.log(error);
        }
    }

    async function updateThumbnail() {
        console.log('updating thumbnail', $rowIndex);
        console.log('OLD DOCUMENT BODY', $documentBody);
        $documentBody[$rowIndex].thumbnail_URL = pictureURL;
        console.log('NEW DOCUMENT BODY', $documentBody);
        const { error } = await supabaseClient
            .from('documents')
            .update({ document_body: $documentBody })
            .eq('document_id', $documentID);
        if (error) {
            console.log('THERE WAS AN ERROR UPDATING THE USER PROFILE');
            showError = true;
            errorMessage = 'There was an error updating your contact. Error: ' + error;
            console.log(error);
        } else if (error == null) {
            showError = false;
            console.log('Update successful');
            getMaterialSchedule();
            setTimeout(getAvatarURL, 3000);
        } else {
            showError = true;
            errorMessage = 'There was an error updating your contact. Error: ' + error;
            console.log('Update failed');
        }
    }

    async function getMaterialSchedule() {
        const { data: documentJSON, error } = await supabaseClient
            .from('documents')
            .select('document_body')
            .eq('document_id', $documentID);

        [];
        if (error) {
            // showEmptyState = true;
            console.log('Error fetching contacts', error);
            // rowData= roleArray
        } else {
            const [fetchedRows] = documentJSON;
            documentBody.set(fetchedRows.document_body);
            getAvatarURL();
        }
    }

    onMount(() => {
        getAvatarURL();
    });

    function handleMouseOver() {
        showUI = true;
    }

    function handleMouseOut() {
        showUI = false;
    }

    function showButtons() {
        showFitAndFill = !showFitAndFill;
    }

    function fitImage() {
        thumbnailFit = true;
        $documentBody[$rowIndex].thumbnail_fit = true;
        showFitAndFill = false;
        updateThumbnail();
    }

    function fillImage() {
        thumbnailFit = false;
        $documentBody[$rowIndex].thumbnail_fit = false;
        showFitAndFill = false;
        updateThumbnail();
    }
</script>

<div class="flex gap-4 flex-row align-middle content-center">
    {#if pictureURL == '' || pictureURL == 'null' || pictureURL == undefined || pictureURL == '--'}
        <div>
            <label for="nonExisting">
                <div
                    class=" transition-all bg-neutral-50 border-2 rounded-sm border-neutral-300 h-24 w-24 flex text-center text-neutral-700 hover:bg-neutral-100 hover:border-neutral-400 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-50 hover:text-neutral-700 "
                >
                    <svg
                        class="self-center mx-auto "
                        width="20"
                        height="20"
                        viewBox="0 0 20 20"
                        fill="none"
                        xmlns="http://www.w3.org/2000/svg"
                    >
                        <path
                            opacity="0.3"
                            fill-rule="evenodd"
                            clip-rule="evenodd"
                            d="M9.99984 18.3332C14.6022 18.3332 18.3332 14.6022 18.3332 9.99984C18.3332 5.39746 14.6022 1.6665 9.99984 1.6665C5.39746 1.6665 1.6665 5.39746 1.6665 9.99984C1.6665 14.6022 5.39746 18.3332 9.99984 18.3332Z"
                            fill="currentColor"
                        />
                        <path
                            fill-rule="evenodd"
                            clip-rule="evenodd"
                            d="M10.8333 5.83333C10.8333 5.3731 10.4602 5 10 5C9.53976 5 9.16667 5.3731 9.16667 5.83333V9.16667H5.83333C5.3731 9.16667 5 9.53976 5 10C5 10.4602 5.3731 10.8333 5.83333 10.8333H9.16667V14.1667C9.16667 14.6269 9.53976 15 10 15C10.4602 15 10.8333 14.6269 10.8333 14.1667V10.8333H14.1667C14.6269 10.8333 15 10.4602 15 10C15 9.53976 14.6269 9.16667 14.1667 9.16667H10.8333V5.83333Z"
                            fill="currentColor"
                        />
                        <rect width="20" height="20" />
                    </svg>
                </div>
            </label>

            <input
                style="visibility: hidden; position:absolute;"
                type="file"
                id="nonExisting"
                accept="image/*"
                bind:files
                on:change={handleFileChange}
                disabled={uploading}
            />
        </div>
    {:else}
        <div class="flex flex-col text-center">
            <div
                on:mouseover={handleMouseOver}
                on:mouseout={handleMouseOut}
                on:focus={handleMouseOver}
                on:blur={handleMouseOut}
                class="flex flex-row "
            >
                {#if showUI}
                    <button
                        on:click={showButtons}
                        transition:fade
                        class="flex text-neutral-400 transition-all  hover:text-neutral-500  my-auto mx-auto"
                    >
                        <svg
                            class="self-center"
                            width="24"
                            height="24"
                            viewBox="0 0 48 48"
                            fill="none"
                            xmlns="http://www.w3.org/2000/svg"
                        >
                            <circle cx="24" cy="10" r="4" fill="currentColor" />
                            <circle cx="24" cy="24" r="4" fill="currentColor" />
                            <circle cx="24" cy="38" r="4" fill="currentColor" />
                        </svg>
                    </button>
                {/if}
                <label for="existing">
                    <div
                        use:loadingAction={loading}
                        class=" transition-all bg-neutral-50 border-2 rounded-sm border-neutral-300 h-24 w-24 flex text-center text-neutral-700 hover:bg-neutral-100 hover:border-neutral-400 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-50 hover:text-neutral-700 "
                    >
                        <img
                            {src}
                            alt={realPhotoUrl ? 'Avatar' : 'No image'}
                            class=" mx-auto transition-all {thumbnailFit === true
                                ? 'object-contain   '
                                : 'w-full h-full object-cover'} "
                        />
                    </div>
                </label>

                <input
                    style="visibility: hidden; position:absolute;"
                    type="file"
                    id="existing"
                    accept="image/*"
                    bind:files
                    on:change={handleFileChange}
                    disabled={uploading}
                />
            </div>

            {#if showFitAndFill}
                <div transition:slide class="flex w-full justify-center mx-auto gap-4 flex-row">
                    <button on:click={fitImage} class="p-2 mt-2 rounded-md hover:bg-neutral-200 ">
                        <svg
                            width="24"
                            height="24"
                            viewBox="0 0 48 48"
                            fill="none"
                            xmlns="http://www.w3.org/2000/svg"
                        >
                            <rect
                                x="4"
                                y="12"
                                width="40"
                                height="24"
                                rx="1.5"
                                stroke="currentColor"
                                stroke-width="4"
                            />
                            <rect
                                x="14"
                                y="12"
                                width="20"
                                height="24"
                                rx="1.5"
                                fill="currentColor"
                                fill-opacity="0.3"
                            />
                        </svg>
                    </button>
                    <button on:click={fillImage} class="p-2 mt-2 rounded-md hover:bg-neutral-200 ">
                        <svg
                            width="24"
                            height="24"
                            viewBox="0 0 48 48"
                            fill="none"
                            xmlns="http://www.w3.org/2000/svg"
                        >
                            <rect
                                x="4"
                                y="8"
                                width="40"
                                height="32"
                                rx="1.5"
                                fill="currentColor"
                                fill-opacity="0.3"
                            />
                            <rect
                                x="4"
                                y="12"
                                width="40"
                                height="24"
                                rx="1.5"
                                stroke="currentColor"
                                stroke-width="4"
                            />
                        </svg>
                    </button>
                </div>
            {/if}
        </div>
    {/if}

    <!--  ERROR MESSAGE -->
    {#if showError === true}
        <div
            transition:slide
            class="p-4 mb-8 border gap-2 border-pink-700 border-t-4 bg-pink-100 flex flex-row"
        >
            <h1 class=" text-sm font-display uppercase text-pink-700">Error:</h1>
            <p class="text-pink-700 text-sm ">{errorMessage}</p>
        </div>
    {/if}

    <!--  END ERROR MESSAGE -->
</div>
bryanmylee commented 1 year ago

Thanks for the report! I'll look into the issue.

bryanmylee commented 1 year ago

Unfortunately I've not been able to figure this one out. Any luck on your end @ethanfox?

ethanfox commented 1 year ago

I fixed this. I don’t have the solution on hand but it had to do with Sveltes reactivity