feat(pres_mgmt): replace time_hours/time_format/datetime_format with single use_12h toggle

Three redundant store fields encoding the same AM/PM choice replaced with a single
`use_12h: boolean` in PresMgmtLocState. iso_datetime_formatter gains a third param
(use_12h: boolean | null = null) that auto-resolves 24h↔12h format name variants via
a symmetric FORMAT_PAIRS lookup — null default leaves all ~100 existing call sites intact.

Toggle surfaces in three places: Clock icon in session time chip (hidden button, same
visual), event Options modal Display section, and session Options modal Display section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-15 14:29:57 -04:00
parent 1296b1077e
commit 631a77158c
6 changed files with 104 additions and 33 deletions

View File

@@ -1,9 +1,33 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
// Format pairs: [24h base, 12h variant]. Only formats with both variants are listed.
// Formats without a counterpart (ISO, date-only, week, etc.) are intentionally omitted —
// iso_datetime_formatter passes those through unchanged regardless of use_12h.
const FORMAT_PAIRS: [string, string][] = [
['datetime_iso_no_seconds', 'datetime_iso_12_no_seconds'],
['datetime_short', 'datetime_12_short'],
['datetime_medium', 'datetime_12_medium'],
['datetime_long', 'datetime_12_long'],
['datetime_medium_sec', 'datetime_12_medium_sec'],
['time_long', 'time_12_long'],
['time_short', 'time_12_short'],
['time_short_no_leading', 'time_12_short_no_leading'],
];
// Build lookup maps from the pairs above. Both directions are derived from the same source.
const TO_12H: Record<string, string> = Object.fromEntries(
FORMAT_PAIRS.map(([h24, h12]) => [h24, h12])
);
const TO_24H: Record<string, string> = Object.fromEntries(
FORMAT_PAIRS.map(([h24, h12]) => [h12, h24])
);
export const iso_datetime_formatter = function iso_datetime_formatter( export const iso_datetime_formatter = function iso_datetime_formatter(
raw_datetime: null | string | Date = null, raw_datetime: null | string | Date = null,
named_format: string = 'datetime_iso_no_seconds', // date_iso, datetime_iso_no_seconds named_format: string = 'datetime_iso_no_seconds', // date_iso, datetime_iso_no_seconds
time_24_hours: boolean = false // Pass true/false to resolve to the correct 12h or 24h variant automatically.
// null (default) leaves named_format unchanged — all existing call sites unaffected.
use_12h: boolean | null = null
) { ) {
// console.log('*** iso_datetime_formatter() ***'); // console.log('*** iso_datetime_formatter() ***');
@@ -50,6 +74,12 @@ export const iso_datetime_formatter = function iso_datetime_formatter(
raw_datetime = new Date(); // Get the current datetime if one was not passed. raw_datetime = new Date(); // Get the current datetime if one was not passed.
} }
if (use_12h !== null) {
named_format = use_12h
? (TO_12H[named_format] ?? named_format)
: (TO_24H[named_format] ?? named_format);
}
let datetime_string = null; let datetime_string = null;
switch (named_format) { switch (named_format) {

View File

@@ -84,9 +84,7 @@ export interface PresMgmtLocState {
lock_config: boolean; lock_config: boolean;
// --- Query / search preferences --- // --- Query / search preferences ---
datetime_format: string; use_12h: boolean;
time_format: string;
time_hours: 12 | 24;
qry_enabled: 'all' | 'not_enabled' | 'enabled'; qry_enabled: 'all' | 'not_enabled' | 'enabled';
qry_hidden: 'all' | 'hidden' | 'not_hidden'; qry_hidden: 'all' | 'hidden' | 'not_hidden';
qry_limit__files: number; qry_limit__files: number;
@@ -265,9 +263,7 @@ export const pres_mgmt_loc_defaults: PresMgmtLocState = {
lock_config: false, lock_config: false,
// Query / search // Query / search
datetime_format: 'datetime_12_long', use_12h: true,
time_format: 'time_12_short',
time_hours: 12,
qry_enabled: 'enabled', qry_enabled: 'enabled',
qry_hidden: 'not_hidden', qry_hidden: 'not_hidden',
qry_limit__files: 75, qry_limit__files: 75,

View File

@@ -10,6 +10,7 @@ import { goto } from '$app/navigation';
import { Modal } from 'flowbite-svelte'; import { Modal } from 'flowbite-svelte';
import { import {
Archive, Archive,
Clock,
Info, Info,
MapPin, MapPin,
Plane, Plane,
@@ -194,6 +195,25 @@ async function on_delete(method: 'delete' | 'disable') {
</span> </span>
</button> </button>
<button
type="button"
onclick={() => {
pres_mgmt_loc.current.use_12h = !pres_mgmt_loc.current.use_12h;
}}
class="btn btn-sm w-full justify-between"
class:ae_btn_surface={pres_mgmt_loc.current.use_12h}
class:ae_btn_surface_outlined={!pres_mgmt_loc.current.use_12h}>
{#if pres_mgmt_loc.current.use_12h}<ToggleRight
size="1em"
class="mr-1" />{:else}<ToggleLeft
size="1em"
class="mr-1" />{/if}
<span class="grow">
<Clock size="1em" class="mr-1" />
{pres_mgmt_loc.current.use_12h ? '12-Hour Time' : '24-Hour Time'}
</span>
</button>
<!-- <button <!-- <button
type="button" type="button"
onclick={() => { onclick={() => {

View File

@@ -15,9 +15,10 @@ let {
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { Modal } from 'flowbite-svelte'; import { Modal } from 'flowbite-svelte';
import { Info, Send, Settings, ToggleRight, X } from '@lucide/svelte'; import { Clock, Info, Send, Settings, ToggleLeft, ToggleRight, X } from '@lucide/svelte';
import { ae_loc, ae_api } from '$lib/stores/ae_stores'; import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { events_loc, events_slct } from '$lib/stores/ae_events_stores'; import { events_loc, events_slct } from '$lib/stores/ae_events_stores';
import { pres_mgmt_loc } from '$lib/stores/ae_events_stores__pres_mgmt.svelte';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import { api } from '$lib/api/api'; import { api } from '$lib/api/api';
@@ -166,6 +167,32 @@ async function toggle_hide_launcher() {
{/snippet} {/snippet}
<div class="flex flex-col gap-4 p-4"> <div class="flex flex-col gap-4 p-4">
<!-- Display -->
<section>
<h4
class="text-surface-500 mb-2 text-xs font-semibold tracking-wider uppercase">
Display
</h4>
<button
type="button"
onclick={() => {
pres_mgmt_loc.current.use_12h = !pres_mgmt_loc.current.use_12h;
}}
class="btn btn-sm w-full justify-between"
class:ae_btn_surface={pres_mgmt_loc.current.use_12h}
class:ae_btn_surface_outlined={!pres_mgmt_loc.current.use_12h}>
{#if pres_mgmt_loc.current.use_12h}<ToggleRight
size="1em"
class="mr-1" />{:else}<ToggleLeft
size="1em"
class="mr-1" />{/if}
<span class="grow">
<Clock size="1em" class="mr-1" />
{pres_mgmt_loc.current.use_12h ? '12-Hour Time' : '24-Hour Time'}
</span>
</button>
</section>
<!-- Launcher Settings --> <!-- Launcher Settings -->
{#if $ae_loc.trusted_access} {#if $ae_loc.trusted_access}
<section> <section>

View File

@@ -220,8 +220,8 @@ $effect(() => {
</div> </div>
{:else} {:else}
<!-- Skeleton placeholder while LiveQuery resolves --> <!-- Skeleton placeholder while LiveQuery resolves -->
<div class="bg-surface-200-800 h-7 w-2/3 animate-pulse rounded"> <!-- <div class="bg-surface-200-800 h-7 w-2/3 animate-pulse rounded">
</div> </div> -->
{/if} {/if}
<!-- Date/Time + Room as info chips --> <!-- Date/Time + Room as info chips -->
@@ -229,19 +229,28 @@ $effect(() => {
<div class="flex flex-wrap items-center gap-2"> <div class="flex flex-wrap items-center gap-2">
<span <span
class="bg-primary-500/10 text-primary-700 dark:text-primary-300 inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-semibold transition-colors duration-200"> class="bg-primary-500/10 text-primary-700 dark:text-primary-300 inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-semibold transition-colors duration-200">
<Clock size="1em" class="text-xs" /> <button
type="button"
onclick={() => (pres_mgmt_loc.current.use_12h = !pres_mgmt_loc.current.use_12h)}
title={pres_mgmt_loc.current.use_12h ? 'Switch to 24-hour time' : 'Switch to 12-hour time'}
class="cursor-pointer rounded focus-visible:ring-1 focus-visible:ring-current"
aria-label={pres_mgmt_loc.current.use_12h ? 'Switch to 24-hour time' : 'Switch to 12-hour time'}>
<Clock size="1em" class="text-xs" />
</button>
{ae_util.iso_datetime_formatter( {ae_util.iso_datetime_formatter(
$lq__event_session_obj.start_datetime, $lq__event_session_obj.start_datetime,
'dddd' 'dddd'
)}, )},
{ae_util.iso_datetime_formatter( {ae_util.iso_datetime_formatter(
$lq__event_session_obj.start_datetime, $lq__event_session_obj.start_datetime,
pres_mgmt_loc.current.datetime_format 'datetime_long',
pres_mgmt_loc.current.use_12h
)} )}
&ndash; &ndash;
{ae_util.iso_datetime_formatter( {ae_util.iso_datetime_formatter(
$lq__event_session_obj.end_datetime, $lq__event_session_obj.end_datetime,
pres_mgmt_loc.current.time_format 'time_short',
pres_mgmt_loc.current.use_12h
)} )}
</span> </span>
</div> </div>
@@ -270,7 +279,7 @@ $effect(() => {
})}> })}>
<span class="text-xs font-semibold opacity-60"> <span class="text-xs font-semibold opacity-60">
<Clock size="0.9em" class="inline mr-1" />Start: <Clock size="0.9em" class="inline mr-1" />Start:
{ae_util.iso_datetime_formatter($lq__event_session_obj.start_datetime, pres_mgmt_loc.current.datetime_format)} {ae_util.iso_datetime_formatter($lq__event_session_obj.start_datetime, 'datetime_long', pres_mgmt_loc.current.use_12h)}
</span> </span>
</Element_ae_obj_field_editor> </Element_ae_obj_field_editor>
</div> </div>
@@ -290,7 +299,7 @@ $effect(() => {
})}> })}>
<span class="text-xs font-semibold opacity-60"> <span class="text-xs font-semibold opacity-60">
<Clock size="0.9em" class="inline mr-1" />End: <Clock size="0.9em" class="inline mr-1" />End:
{ae_util.iso_datetime_formatter($lq__event_session_obj.end_datetime, pres_mgmt_loc.current.datetime_format)} {ae_util.iso_datetime_formatter($lq__event_session_obj.end_datetime, 'datetime_long', pres_mgmt_loc.current.use_12h)}
</span> </span>
</Element_ae_obj_field_editor> </Element_ae_obj_field_editor>
</div> </div>

View File

@@ -171,11 +171,10 @@ import {
'dddd' 'dddd'
)} )}
@ @
<!-- , -->
<!-- {ae_util.iso_datetime_formatter(event_presentation_obj.start_datetime, pres_mgmt_loc.current.datetime_format)} -->
{ae_util.iso_datetime_formatter( {ae_util.iso_datetime_formatter(
event_presentation_obj.start_datetime, event_presentation_obj.start_datetime,
pres_mgmt_loc.current.time_format 'time_short',
pres_mgmt_loc.current.use_12h
)} )}
</span> </span>
@@ -247,19 +246,7 @@ import {
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
if (pres_mgmt_loc.current.time_hours == 12) { pres_mgmt_loc.current.use_12h = !pres_mgmt_loc.current.use_12h;
pres_mgmt_loc.current.time_hours = 24;
pres_mgmt_loc.current.datetime_format =
'datetime_long';
pres_mgmt_loc.current.time_format =
'time_short';
} else {
pres_mgmt_loc.current.time_hours = 12;
pres_mgmt_loc.current.datetime_format =
'datetime_12_long';
pres_mgmt_loc.current.time_format =
'time_12_short';
}
}}> }}>
time time
</button> </button>
@@ -283,7 +270,8 @@ import {
)} )}
{ae_util.iso_datetime_formatter( {ae_util.iso_datetime_formatter(
event_presentation_obj.start_datetime, event_presentation_obj.start_datetime,
pres_mgmt_loc.current.time_format 'time_short',
pres_mgmt_loc.current.use_12h
)} )}
</Element_ae_obj_field_editor> </Element_ae_obj_field_editor>
- -
@@ -301,7 +289,8 @@ import {
})}> })}>
{ae_util.iso_datetime_formatter( {ae_util.iso_datetime_formatter(
event_presentation_obj.end_datetime, event_presentation_obj.end_datetime,
pres_mgmt_loc.current.time_format 'time_short',
pres_mgmt_loc.current.use_12h
)} )}
</Element_ae_obj_field_editor> </Element_ae_obj_field_editor>
</div> </div>