diff --git a/src/routes/(protected)/(user)/schedule/+page.svelte b/src/routes/(protected)/(user)/schedule/+page.svelte index f9981e2..5dc3bab 100644 --- a/src/routes/(protected)/(user)/schedule/+page.svelte +++ b/src/routes/(protected)/(user)/schedule/+page.svelte @@ -4,7 +4,7 @@ import DaySchedule from '$lib/components/schedule/DaySchedule.svelte'; import BlockModal from '$lib/components/schedule/BlockModal.svelte'; - let { data }: PageProps = $props(); + let { data, form }: PageProps = $props(); const WEEK_DAYS: { key: string; label: string }[] = [ { key: 'mon', label: 'Monday' }, @@ -55,6 +55,34 @@ let typeForm: HTMLFormElement | undefined = $state(); let closeTimer: ReturnType | undefined; + // Copy-to dialog state. + let copyOpen = $state(false); + let copyTargets = $state([]); + let copyError = $state(''); + let copyForm: HTMLFormElement | undefined = $state(); + let copyTimer: ReturnType | undefined; + + const copyCandidates = $derived(data.licenses.filter((l) => l.id !== data.selectedLicenseId)); + const overwritingTargets = $derived( + copyCandidates.filter((l) => copyTargets.includes(l.id) && l.hasSchedule) + ); + const needsCopyConfirm = $derived(overwritingTargets.length > 0); + + // Surface the copy action's result (summary or error) from the form action. + $effect(() => { + if (!form || (form as { action?: string }).action !== 'copy') return; + if ((form as { success?: boolean }).success) { + const targets = (form as { targets?: { label: string }[] }).targets ?? []; + copyOpen = false; + flash(`Copied to ${targets.map((t) => t.label).join(', ')}.`); + // Refresh so hasSchedule flags and the selected system are current. + clearTimeout(copyTimer); + copyTimer = setTimeout(() => window.location.reload(), 800); + } else { + copyError = (form as { error?: string }).error ?? 'Copy failed.'; + } + }); + const selectedLicense = $derived( data.licenses.find((l) => l.id === data.selectedLicenseId) ?? null ); @@ -137,6 +165,29 @@ modalOpen = false; flash(modalMode === 'edit' ? 'Block saved.' : 'Block created.'); } + + function openCopy() { + copyTargets = []; + copyError = ''; + copyOpen = true; + } + + function toggleCopyTarget(id: string, checked: boolean) { + copyTargets = checked + ? [...new Set([...copyTargets, id])] + : copyTargets.filter((t) => t !== id); + } + + function submitCopy() { + copyError = ''; + if (copyTargets.length === 0) { + copyError = 'Pick at least one system to copy to.'; + return; + } + const form = copyForm; + if (!form) return; + form.requestSubmit(); + } {#if typeForm === undefined}{/if} @@ -235,6 +286,13 @@ class="ml-2 text-sm text-red-600 hover:underline dark:text-red-400" onclick={() => requestType(null)}>Delete schedule + {#if copyCandidates.length > 0} + + {/if} {/if} @@ -330,3 +388,110 @@ {/if} + +{#if copyOpen} +
+
+
+

+ Copy schedule to… +

+ +
+ +
+

+ Copy {selectedLicense ? (selectedLicense.systemName ?? selectedLicense.key) : ''}'s schedule to other systems. Copies are independent snapshots. +

+ + {#if copyError} + + {/if} + +
    + {#each copyCandidates as candidate (candidate.id)} +
  • + +
  • + {/each} +
+ + {#if needsCopyConfirm} +
+ This will overwrite the existing schedule on + {overwritingTargets.map((t) => t.systemName ?? t.key).join(', ')}. +
+ {/if} +
+ +
+ + +
+ + +
+
+{/if}