Badges: hide Save until field is dirty; warning style when unsaved changes
This commit is contained in:
@@ -311,6 +311,33 @@
|
||||
});
|
||||
});
|
||||
|
||||
// --- Dirty detection: compare live edit value against the last-saved badge obj ---
|
||||
// Save button is hidden when the value matches saved state (no unsaved change);
|
||||
// shown with a warning/attention style the moment something changes.
|
||||
// Using string comparison for text fields; boolean comparison for allow_tracking;
|
||||
// null-safe equality for badge_type_code (null vs null must be equal).
|
||||
let is_dirty_name = $derived(
|
||||
edit_full_name_override !== ($lq__event_badge_obj?.full_name_override ?? $lq__event_badge_obj?.full_name ?? '')
|
||||
);
|
||||
let is_dirty_title = $derived(
|
||||
edit_professional_title_override !== ($lq__event_badge_obj?.professional_title_override ?? $lq__event_badge_obj?.professional_title ?? '')
|
||||
);
|
||||
let is_dirty_affiliations = $derived(
|
||||
edit_affiliations_override !== ($lq__event_badge_obj?.affiliations_override ?? $lq__event_badge_obj?.affiliations ?? '')
|
||||
);
|
||||
let is_dirty_location = $derived(
|
||||
edit_location_override !== ($lq__event_badge_obj?.location_override ?? $lq__event_badge_obj?.location ?? '')
|
||||
);
|
||||
let is_dirty_pronouns = $derived(
|
||||
edit_pronouns_override !== ($lq__event_badge_obj?.pronouns_override ?? $lq__event_badge_obj?.pronouns ?? '')
|
||||
);
|
||||
let is_dirty_allow_tracking = $derived(
|
||||
edit_allow_tracking !== ($lq__event_badge_obj?.allow_tracking ?? false)
|
||||
);
|
||||
let is_dirty_badge_type = $derived(
|
||||
edit_badge_type_code !== ($lq__event_badge_obj?.badge_type_code_override ?? $lq__event_badge_obj?.badge_type_code ?? null)
|
||||
);
|
||||
|
||||
// TC modal ref for the lead scanning terms & conditions dialog
|
||||
let tc_dialog_ref: HTMLDialogElement | undefined;
|
||||
|
||||
@@ -371,41 +398,47 @@
|
||||
{/snippet}
|
||||
|
||||
<!-- Save/cancel row (inside accordion edit forms).
|
||||
on_revert is optional — only passed when an override is currently saved;
|
||||
clears the override back to the base imported value. Layout: [Revert] [Save] [X] -->
|
||||
{#snippet field_actions(field_key: string, on_save: () => void, on_cancel: () => void, on_revert?: () => void)}
|
||||
on_revert is optional — shown only when a saved override exists.
|
||||
is_dirty: Save is hidden when clean (unchanged), warning-styled when dirty.
|
||||
Layout when dirty: [Revert?] [⚠ Save] [X] / when clean: [Revert?] [X] -->
|
||||
{#snippet field_actions(field_key: string, on_save: () => void, on_cancel: () => void, on_revert?: () => void, is_dirty = false)}
|
||||
{@const status = field_save_status[field_key]}
|
||||
{@const show_save = is_dirty || (status && status !== 'idle')}
|
||||
<div class="flex gap-2 mt-2">
|
||||
{#if on_revert}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm preset-tonal-warning shrink-0"
|
||||
onclick={on_revert}
|
||||
disabled={field_save_status[field_key] === 'saving'}
|
||||
disabled={status === 'saving'}
|
||||
title="Remove override — restore original imported value"
|
||||
aria-label="Revert to original value"
|
||||
><RotateCcw size="13" /></button>
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm flex-1"
|
||||
class:preset-filled-primary={!field_save_status[field_key] || field_save_status[field_key] === 'idle'}
|
||||
class:preset-filled-success={field_save_status[field_key] === 'done'}
|
||||
class:preset-tonal-error={field_save_status[field_key] === 'error'}
|
||||
disabled={field_save_status[field_key] === 'saving'}
|
||||
onclick={on_save}
|
||||
title="Save changes"
|
||||
aria-label="Save changes"
|
||||
>
|
||||
{#if field_save_status[field_key] === 'saving'}
|
||||
<LoaderCircle size="14" class="animate-spin mr-1" /> Saving…
|
||||
{:else if field_save_status[field_key] === 'done'}
|
||||
<Check size="14" class="mr-1" /> Saved
|
||||
{:else if field_save_status[field_key] === 'error'}
|
||||
Error — retry
|
||||
{:else}
|
||||
<Check size="14" class="mr-1" /> Save
|
||||
{/if}
|
||||
</button>
|
||||
{#if show_save}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm flex-1 transition-colors"
|
||||
class:preset-filled-warning={is_dirty && (!status || status === 'idle')}
|
||||
class:preset-tonal-surface={status === 'saving'}
|
||||
class:preset-filled-success={status === 'done'}
|
||||
class:preset-tonal-error={status === 'error'}
|
||||
disabled={status === 'saving'}
|
||||
onclick={on_save}
|
||||
title="Save changes"
|
||||
aria-label="Save changes"
|
||||
>
|
||||
{#if status === 'saving'}
|
||||
<LoaderCircle size="14" class="animate-spin mr-1" /> Saving…
|
||||
{:else if status === 'done'}
|
||||
<Check size="14" class="mr-1" /> Saved
|
||||
{:else if status === 'error'}
|
||||
Error — retry
|
||||
{:else}
|
||||
<Check size="14" class="mr-1" /> Save
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm preset-tonal-surface"
|
||||
@@ -513,7 +546,8 @@
|
||||
() => cancel_field('name'),
|
||||
$lq__event_badge_obj?.full_name_override
|
||||
? () => save_field('name', { full_name_override: null })
|
||||
: undefined
|
||||
: undefined,
|
||||
is_dirty_name
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -565,7 +599,8 @@
|
||||
() => cancel_field('title'),
|
||||
$lq__event_badge_obj?.professional_title_override
|
||||
? () => save_field('title', { professional_title_override: null })
|
||||
: undefined
|
||||
: undefined,
|
||||
is_dirty_title
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -617,7 +652,8 @@
|
||||
() => cancel_field('affiliations'),
|
||||
$lq__event_badge_obj?.affiliations_override
|
||||
? () => save_field('affiliations', { affiliations_override: null })
|
||||
: undefined
|
||||
: undefined,
|
||||
is_dirty_affiliations
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -669,7 +705,8 @@
|
||||
() => cancel_field('location'),
|
||||
$lq__event_badge_obj?.location_override
|
||||
? () => save_field('location', { location_override: null })
|
||||
: undefined
|
||||
: undefined,
|
||||
is_dirty_location
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -711,29 +748,33 @@
|
||||
/>
|
||||
<span class="text-xs">Allow exhibitor lead scanning</span>
|
||||
</label>
|
||||
<!-- Inline actions — not shared snippet because this field adds a TC info button -->
|
||||
<!-- Inline actions — not shared snippet because this field adds a TC info button.
|
||||
Save hidden until the checkbox value differs from the saved value. -->
|
||||
<div class="flex gap-2 mt-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm flex-1"
|
||||
class:preset-filled-primary={!field_save_status['allow_tracking'] || field_save_status['allow_tracking'] === 'idle'}
|
||||
class:preset-filled-success={field_save_status['allow_tracking'] === 'done'}
|
||||
class:preset-tonal-error={field_save_status['allow_tracking'] === 'error'}
|
||||
disabled={field_save_status['allow_tracking'] === 'saving'}
|
||||
onclick={() => save_field('allow_tracking', { allow_tracking: edit_allow_tracking })}
|
||||
title="Save changes"
|
||||
aria-label="Save changes"
|
||||
>
|
||||
{#if field_save_status['allow_tracking'] === 'saving'}
|
||||
<LoaderCircle size="14" class="animate-spin mr-1" /> Saving…
|
||||
{:else if field_save_status['allow_tracking'] === 'done'}
|
||||
<Check size="14" class="mr-1" /> Saved
|
||||
{:else if field_save_status['allow_tracking'] === 'error'}
|
||||
Error — retry
|
||||
{:else}
|
||||
<Check size="14" class="mr-1" /> Save
|
||||
{/if}
|
||||
</button>
|
||||
{#if is_dirty_allow_tracking || (field_save_status['allow_tracking'] && field_save_status['allow_tracking'] !== 'idle')}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm flex-1 transition-colors"
|
||||
class:preset-filled-warning={is_dirty_allow_tracking && (!field_save_status['allow_tracking'] || field_save_status['allow_tracking'] === 'idle')}
|
||||
class:preset-tonal-surface={field_save_status['allow_tracking'] === 'saving'}
|
||||
class:preset-filled-success={field_save_status['allow_tracking'] === 'done'}
|
||||
class:preset-tonal-error={field_save_status['allow_tracking'] === 'error'}
|
||||
disabled={field_save_status['allow_tracking'] === 'saving'}
|
||||
onclick={() => save_field('allow_tracking', { allow_tracking: edit_allow_tracking })}
|
||||
title="Save changes"
|
||||
aria-label="Save changes"
|
||||
>
|
||||
{#if field_save_status['allow_tracking'] === 'saving'}
|
||||
<LoaderCircle size="14" class="animate-spin mr-1" /> Saving…
|
||||
{:else if field_save_status['allow_tracking'] === 'done'}
|
||||
<Check size="14" class="mr-1" /> Saved
|
||||
{:else if field_save_status['allow_tracking'] === 'error'}
|
||||
Error — retry
|
||||
{:else}
|
||||
<Check size="14" class="mr-1" /> Save
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm preset-tonal-surface"
|
||||
@@ -795,7 +836,8 @@
|
||||
() => cancel_field('pronouns'),
|
||||
$lq__event_badge_obj?.pronouns_override
|
||||
? () => save_field('pronouns', { pronouns_override: null })
|
||||
: undefined
|
||||
: undefined,
|
||||
is_dirty_pronouns
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -864,7 +906,8 @@
|
||||
() => cancel_field('badge_type'),
|
||||
$lq__event_badge_obj?.badge_type_code_override
|
||||
? () => save_field('badge_type', { badge_type_code_override: null, badge_type_override: null })
|
||||
: undefined
|
||||
: undefined,
|
||||
is_dirty_badge_type
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user