Use a 12-hour clock with AM/PM selects in the block modal
This commit is contained in:
@@ -56,12 +56,15 @@
|
||||
let step = $state<'details' | 'browse'>('details');
|
||||
let startHour = $state('9');
|
||||
let startMinute = $state('0');
|
||||
let startPeriod = $state<'AM' | 'PM'>('AM');
|
||||
let endHour = $state('10');
|
||||
let endMinute = $state('0');
|
||||
let endPeriod = $state<'AM' | 'PM'>('AM');
|
||||
let selectedIds = $state<string[]>([]);
|
||||
let error = $state('');
|
||||
|
||||
const hourOptions = Array.from({ length: 24 }, (_, i) => String(i));
|
||||
const hourOptions = Array.from({ length: 12 }, (_, i) => String(i + 1));
|
||||
const periodOptions: ('AM' | 'PM')[] = ['AM', 'PM'];
|
||||
|
||||
function minuteOptions(current: string): string[] {
|
||||
const base = Array.from({ length: 60 / MINUTE_STEP }, (_, i) => String(i * MINUTE_STEP));
|
||||
@@ -69,20 +72,38 @@
|
||||
return [...base, current].sort((a, b) => Number(a) - Number(b));
|
||||
}
|
||||
|
||||
function to12Hour(totalMinutes: number): { hour: number; minute: number; period: 'AM' | 'PM' } {
|
||||
const hours24 = Math.floor(totalMinutes / 60) % 24;
|
||||
return {
|
||||
hour: hours24 % 12 === 0 ? 12 : hours24 % 12,
|
||||
minute: totalMinutes % 60,
|
||||
period: hours24 < 12 ? 'AM' : 'PM'
|
||||
};
|
||||
}
|
||||
|
||||
function to24Hour(hour12: string, minute: string, period: 'AM' | 'PM'): number {
|
||||
const hours = (Number(hour12) % 12) + (period === 'PM' ? 12 : 0);
|
||||
return (hours * 60 + Number(minute)) | 0;
|
||||
}
|
||||
|
||||
// Re-seed the fields whenever the modal is (re)opened.
|
||||
$effect(() => {
|
||||
if (!open) return;
|
||||
startHour = String(Math.floor(initialStartMinutes / 60));
|
||||
startMinute = String(initialStartMinutes % 60);
|
||||
endHour = String(Math.floor(initialEndMinutes / 60));
|
||||
endMinute = String(initialEndMinutes % 60);
|
||||
const start = to12Hour(initialStartMinutes);
|
||||
startHour = String(start.hour);
|
||||
startMinute = String(start.minute);
|
||||
startPeriod = start.period;
|
||||
const end = to12Hour(initialEndMinutes);
|
||||
endHour = String(end.hour);
|
||||
endMinute = String(end.minute);
|
||||
endPeriod = end.period;
|
||||
selectedIds = [...initialProclamationIds];
|
||||
error = '';
|
||||
step = 'details';
|
||||
});
|
||||
|
||||
const startMinutes = $derived((Number(startHour) * 60 + Number(startMinute)) | 0);
|
||||
const endMinutes = $derived((Number(endHour) * 60 + Number(endMinute)) | 0);
|
||||
const startMinutes = $derived(to24Hour(startHour, startMinute, startPeriod));
|
||||
const endMinutes = $derived(to24Hour(endHour, endMinute, endPeriod));
|
||||
const overnight = $derived(
|
||||
startMinutes !== endMinutes && isOvernight({ startMinutes, endMinutes })
|
||||
);
|
||||
@@ -213,9 +234,7 @@
|
||||
class="rounded-md border border-slate-300 bg-white px-2 py-2 text-sm text-slate-900 shadow-sm focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
|
||||
>
|
||||
{#each hourOptions as hour (hour)}
|
||||
<option value={hour} selected={startHour === hour}>
|
||||
{hour.padStart(2, '0')}
|
||||
</option>
|
||||
<option value={hour} selected={startHour === hour}>{hour}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<span class="text-slate-400">:</span>
|
||||
@@ -230,6 +249,15 @@
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<select
|
||||
aria-label="Start period"
|
||||
onchange={(e) => (startPeriod = e.currentTarget.value as 'AM' | 'PM')}
|
||||
class="rounded-md border border-slate-300 bg-white px-2 py-2 text-sm text-slate-900 shadow-sm focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
|
||||
>
|
||||
{#each periodOptions as period (period)}
|
||||
<option value={period} selected={startPeriod === period}>{period}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -244,9 +272,7 @@
|
||||
class="rounded-md border border-slate-300 bg-white px-2 py-2 text-sm text-slate-900 shadow-sm focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
|
||||
>
|
||||
{#each hourOptions as hour (hour)}
|
||||
<option value={hour} selected={endHour === hour}>
|
||||
{hour.padStart(2, '0')}
|
||||
</option>
|
||||
<option value={hour} selected={endHour === hour}>{hour}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<span class="text-slate-400">:</span>
|
||||
@@ -261,6 +287,15 @@
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<select
|
||||
aria-label="End period"
|
||||
onchange={(e) => (endPeriod = e.currentTarget.value as 'AM' | 'PM')}
|
||||
class="rounded-md border border-slate-300 bg-white px-2 py-2 text-sm text-slate-900 shadow-sm focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
|
||||
>
|
||||
{#each periodOptions as period (period)}
|
||||
<option value={period} selected={endPeriod === period}>{period}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -67,6 +67,32 @@ describe('BlockModal', () => {
|
||||
await expect.element(page.getByRole('combobox', { name: /^End minute$/ })).toHaveValue('15');
|
||||
});
|
||||
|
||||
it('uses a 12-hour clock with an AM/PM select for each time', async () => {
|
||||
mount({ initialStartMinutes: 21 * 60, initialEndMinutes: 0 });
|
||||
|
||||
await expect.element(page.getByRole('combobox', { name: /^Start hour$/ })).toHaveValue('9');
|
||||
await expect.element(page.getByRole('combobox', { name: /^Start period$/ })).toHaveValue('PM');
|
||||
await expect.element(page.getByRole('combobox', { name: /^End hour$/ })).toHaveValue('12');
|
||||
await expect.element(page.getByRole('combobox', { name: /^End period$/ })).toHaveValue('AM');
|
||||
|
||||
// No 24-hour option exists any more.
|
||||
await expect
|
||||
.element(page.getByRole('combobox', { name: /^Start hour$/ }))
|
||||
.not.toHaveValue('21');
|
||||
});
|
||||
|
||||
it('recomputes the time when the AM/PM switch changes', async () => {
|
||||
mount({ initialStartMinutes: 9 * 60, initialEndMinutes: 10 * 60 });
|
||||
|
||||
const period = (await page
|
||||
.getByRole('combobox', { name: /^End period$/ })
|
||||
.element()) as HTMLSelectElement;
|
||||
period.value = 'PM';
|
||||
period.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
|
||||
await expect.element(page.getByText('9:00 AM – 10:00 PM')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the review times and selected proclamation titles', async () => {
|
||||
mount();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user