Portal: fix schedule type toggle, daily rendering, overnight spill, and mobile grid
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import type { SubmitFunction } from '@sveltejs/kit';
|
||||
import type { PageProps } from './$types';
|
||||
import WeekSchedule from '$lib/components/schedule/WeekSchedule.svelte';
|
||||
@@ -27,19 +26,10 @@
|
||||
proclamations: { id: string; name: string; categoryId: number; groupId: number | null }[];
|
||||
};
|
||||
|
||||
// Local mirrors so the UI updates after actions. They reset whenever the
|
||||
// selected system (and therefore the loaded schedule) changes.
|
||||
let type = $state<'daily' | 'weekly' | null>(null);
|
||||
let blocks = $state<BlockView[]>([]);
|
||||
let lastLicenseId = $state<string | null>(null);
|
||||
$effect(() => {
|
||||
if (data.selectedLicenseId !== lastLicenseId) {
|
||||
lastLicenseId = data.selectedLicenseId;
|
||||
type = data.type;
|
||||
blocks = data.blocks;
|
||||
showTypeConfirm = false;
|
||||
}
|
||||
});
|
||||
// Render straight from the server data. Every action invalidates and reloads
|
||||
// it, so the view stays in sync without a local mirror that can go stale.
|
||||
const type = $derived(data.type);
|
||||
const blocks: BlockView[] = $derived(data.blocks);
|
||||
let typePending = $state<'daily' | 'weekly' | null>(null);
|
||||
let showTypeConfirm = $state(false);
|
||||
let successMessage = $state('');
|
||||
@@ -104,9 +94,8 @@
|
||||
applyType(next);
|
||||
}
|
||||
|
||||
// Submit the hidden ?/setType form. The enhance callback updates the local
|
||||
// view immediately (the server then prefills/clears as needed) and refreshes
|
||||
// the per-system flags without a full navigation.
|
||||
// Submit the hidden ?/setType form. The enhance callback reloads the server
|
||||
// data (prefilling/clearing Blocks as needed) without a full navigation.
|
||||
function applyType(next: 'daily' | 'weekly' | null) {
|
||||
showTypeConfirm = false;
|
||||
errorMessage = '';
|
||||
@@ -124,10 +113,9 @@
|
||||
(result.data as { error?: string })?.error ?? 'Could not change the schedule type.';
|
||||
return;
|
||||
}
|
||||
// Show the new type straight away; invalidateAll() then loads the
|
||||
// server's blocks (e.g. daily→weekly prefill) and hasSchedule flags.
|
||||
// update() applies the action result and reloads all data (it calls
|
||||
// invalidateAll by default), pulling the new type, blocks and flags.
|
||||
await update();
|
||||
await invalidateAll();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -206,7 +194,6 @@
|
||||
copyOpen = false;
|
||||
flash(`Copied to ${targets.join(', ')}.`);
|
||||
await update();
|
||||
await invalidateAll();
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { page } from 'vitest/browser';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render } from 'vitest-browser-svelte';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
vi.mock('$app/forms', () => ({
|
||||
enhance: () => () => {}
|
||||
}));
|
||||
|
||||
import Page from './+page.svelte';
|
||||
|
||||
const LICENSE = {
|
||||
id: 'lk1',
|
||||
key: 'AAAA-BBBB-CCCC-DDDD',
|
||||
systemName: 'Living Room',
|
||||
active: true,
|
||||
hasSchedule: false
|
||||
};
|
||||
|
||||
const block = {
|
||||
id: 'b1',
|
||||
day: null as string | null,
|
||||
startMinutes: 6 * 60,
|
||||
endMinutes: 8 * 60,
|
||||
dynamicAudio: true,
|
||||
proclamations: [{ id: 'p1', name: 'Peace', categoryId: 1, groupId: 1 }]
|
||||
};
|
||||
|
||||
function makeData(overrides: Partial<PageProps['data']> = {}): PageProps['data'] {
|
||||
return {
|
||||
userRoleId: null,
|
||||
licenses: [LICENSE],
|
||||
selectedLicenseId: LICENSE.id,
|
||||
type: null,
|
||||
blocks: [],
|
||||
categories: [{ id: 1, name: 'Physical' }],
|
||||
groups: [{ id: 1, name: 'General' }],
|
||||
proclamations: [{ id: 'p1', name: 'Peace', categoryId: 1, groupId: 1 }],
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
function mountPage(data: PageProps['data']) {
|
||||
return render(Page, { params: {}, data, form: null });
|
||||
}
|
||||
|
||||
describe('schedule page', () => {
|
||||
it('renders a schedule loaded for the selected system (same system, new data)', async () => {
|
||||
const result = mountPage(makeData());
|
||||
|
||||
await expect
|
||||
.element(page.getByText('No schedule. Choose Daily or Weekly to start.'))
|
||||
.toBeInTheDocument();
|
||||
|
||||
// The server reloads `data` after an action while the selected system id
|
||||
// stays the same; the view must follow the new data rather than a stale
|
||||
// local mirror.
|
||||
await result.rerender({ data: makeData({ type: 'daily', blocks: [block] }) });
|
||||
|
||||
await expect.element(page.getByText('Every day')).toBeInTheDocument();
|
||||
await expect.element(page.getByText('Peace')).toBeInTheDocument();
|
||||
await expect.element(page.getByLabelText('Block 6:00 AM to 8:00 AM')).toBeInTheDocument();
|
||||
await expect
|
||||
.element(page.getByText('No schedule. Choose Daily or Weekly to start.'))
|
||||
.not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user