UX: improvements to opening hours picker, fix some residual errors after refactoring

This commit is contained in:
Pieter Vander Vennet 2024-09-19 18:01:17 +02:00
parent 2569d0cb66
commit b8264a3345
4 changed files with 102 additions and 23 deletions

View file

@ -25,7 +25,7 @@
continue continue
} }
const el = document.elementFromPoint(touch.clientX, touch.clientY) const el = document.elementFromPoint(touch.clientX, touch.clientY)
if (!el) { if (!el || !el.classList.contains("oh-timecell")) {
continue continue
} }
lastElement = <any>el lastElement = <any>el
@ -46,12 +46,18 @@
if (el?.onmouseup) { if (el?.onmouseup) {
el?.onmouseup(<any>ev) el?.onmouseup(<any>ev)
}else{ }else{
// We dragged outside of the table
dispatch("clear") dispatch("clear")
} }
}) })
element.addEventListener("touchmove", ev => { element.addEventListener("touchmove", ev => {
elementUnderTouch(ev)?.onmouseenter(<any>ev) const underTouch = elementUnderTouch(ev)
if(typeof underTouch?.onmouseenter !== "function"){
return
}
underTouch.onmouseenter(<any>ev)
}) })

View file

@ -24,6 +24,8 @@
wd.sunday, wd.sunday,
] ]
let element: HTMLTableElement
function range(n: number) { function range(n: number) {
return Utils.TimesT(n, n => n) return Utils.TimesT(n, n => n)
} }
@ -114,11 +116,29 @@
clearSelection() clearSelection()
} }
let lasttouched: [number, number] = undefined
function moved(weekday: number, hour: number) { function moved(weekday: number, hour: number) {
lasttouched = [weekday, hour]
if (selectionStart) { if (selectionStart) {
clearSelection() clearSelection()
setSelection(selectionStart[0], weekday, selectionStart[1], hour + 0.5) setSelection(selectionStart[0], weekday, selectionStart[1], hour + 0.5)
} }
const allRows = Array.from(element.getElementsByTagName("tr"))
for (const r of allRows) {
r.classList.remove("hover")
r.classList.remove("hovernext")
}
const selectedRow = allRows[hour * 2 + 2]
selectedRow?.classList?.add("hover")
const selectedNextRow = allRows[hour * 2 + 3]
selectedNextRow?.classList?.add("hovernext")
}
function mouseLeft() {
endSelection(...lasttouched)
} }
let totalHeight = 0 let totalHeight = 0
@ -146,14 +166,20 @@
</script> </script>
<table class="oh-table no-weblate w-full" cellspacing="0" cellpadding="0"> <table
bind:this={element}
class="oh-table no-weblate w-full" cellspacing="0" cellpadding="0"
class:hasselection={selectionStart !== undefined} class:hasnoselection={selectionStart === undefined}
on:mouseleave={mouseLeft}>
<tr> <tr>
<!-- Header row -->
<th style="width: 9%"> <th style="width: 9%">
<!-- Top-left cell --> <!-- Top-left cell -->
<slot name="top-left">
<button class="absolute top-0 left-0 p-1 rounded-full" on:click={() => value.set([])} style="z-index: 10"> <button class="absolute top-0 left-0 p-1 rounded-full" on:click={() => value.set([])} style="z-index: 10">
<TrashIcon class="w-5 h-5" /> <TrashIcon class="w-5 h-5" />
</button> </button>
</slot>
</th> </th>
{#each days as wd} {#each days as wd}
<th style="width: 13%"> <th style="width: 13%">
@ -162,14 +188,14 @@
{/each} {/each}
</tr> </tr>
<tr class="h-0"> <tr class="h-0 nobold">
<!-- Virtual row to add the ranges to--> <!-- Virtual row to add the ranges to-->
<td style="width: 9%" /> <td style="width: 9%" />
{#each range(7) as wd} {#each range(7) as wd}
<td style="width: 13%; position: relative;"> <td style="width: 13%; position: relative;">
<div class="h-0 pointer-events-none" style="z-index: 10"> <div class="h-0 pointer-events-none" style="z-index: 10">
{#each $value.filter(oh => oh.weekday === wd) as range } {#each $value.filter(oh => oh.weekday === wd).map(oh => OpeningHours.rangeAs24Hr(oh)) as range }
<div class="absolute pointer-events-none px-1 md:px-2 w-full " <div class="absolute pointer-events-none px-1 md:px-2 w-full "
style={rangeStyle(range, totalHeight)} style={rangeStyle(range, totalHeight)}
> >
@ -180,7 +206,7 @@
<button class="w-fit rounded-full p-1 self-center pointer-events-auto" <button class="w-fit rounded-full p-1 self-center pointer-events-auto"
on:click={() => { on:click={() => {
const cleaned = value.data.filter(v => !OpeningHours.isSame(v, range)) const cleaned = value.data.filter(v => !OpeningHours.isSame(v, range))
console.log("Cleaned", cleaned, value.data) console.log("Cleaned", cleaned, OpeningHours.ToString(value.data))
value.set(cleaned) value.set(cleaned)
}}> }}>
<TrashIcon class="w-6 h-6" /> <TrashIcon class="w-6 h-6" />
@ -202,11 +228,13 @@
{#each range(24) as h} {#each range(24) as h}
<tr style="height: 0.75rem; width: 9%"> <!-- even row, for the hour --> <tr style="height: 0.75rem; width: 9%"> <!-- even row, for the hour -->
<td rowspan={ h < 23 ? 2: 1 } <td rowspan={ h > 0 ? 2: 1 }
class="relative text-sm sm:text-base oh-left-col oh-timecell-full border-box interactive " class="relative text-sm sm:text-base oh-left-col oh-timecell-full border-box interactive "
style={ h < 23 ? "top: 0.75rem" : "height:0; top: 0.75rem"}> style={ h > 0 ? "top: -0.75rem" : "height:0; top: -0.75rem"}>
{#if h < 23} {#if h > 0}
{h + 1}:00 <span class="hour-header w-full">
{h}:00
</span>
{/if} {/if}
</td> </td>
{#each range(7) as wd} {#each range(7) as wd}
@ -215,12 +243,13 @@
{/each} {/each}
</tr> </tr>
<tr style="height: 0.75rem"> <!-- odd row, for the half hour --> <tr style="height: calc( 0.75rem - 1px) "> <!-- odd row, for the half hour -->
{#if h === 23} {#if h === 0}
<td/> <td/> <!-- extra cell to compensate for irregular header-->
{/if} {/if}
{#each range(7) as wd} {#each range(7) as wd}
<OHCell type="half" {h} {wd} on:start={() => startSelection(wd, h + 0.5)} on:end={() => endSelection(wd, h + 0.5)} <OHCell type="half" {h} {wd} on:start={() => startSelection(wd, h + 0.5)}
on:end={() => endSelection(wd, h + 0.5)}
on:move={() => moved(wd, h + 0.5)} on:clear={() => clearSelection()} /> on:move={() => moved(wd, h + 0.5)} on:clear={() => clearSelection()} />
{/each} {/each}
</tr> </tr>
@ -235,4 +264,25 @@
position: sticky; position: sticky;
z-index: 10; z-index: 10;
} }
.hasselection tr:hover .hour-header, .hasselection tr.hover .hour-header {
border-bottom: 2px solid black;
}
.hasselection tr:hover + tr {
font-weight: bold;
}
.hasselection tr.hovernext {
font-weight: bold;
}
.hasnoselection tr:hover, .hasnoselection tr.hover {
font-weight: bold;
}
</style> </style>

View file

@ -9,6 +9,8 @@
import OHTable from "./OpeningHours/OHTable.svelte" import OHTable from "./OpeningHours/OHTable.svelte"
import OpeningHoursState from "../../OpeningHours/OpeningHoursState" import OpeningHoursState from "../../OpeningHours/OpeningHoursState"
import Popup from "../../Base/Popup.svelte" import Popup from "../../Base/Popup.svelte"
import CheckCircle from "@babeard/svelte-heroicons/mini/CheckCircle"
import Check from "@babeard/svelte-heroicons/mini/Check"
export let value: UIEventSource<string> export let value: UIEventSource<string>
export let args: string export let args: string
@ -34,9 +36,9 @@
</script> </script>
<Popup bodyPadding="p-0" shown={expanded}> <Popup bodyPadding="p-0" shown={expanded}>
<OHTable value={state.normalOhs} /> <OHTable value={state.normalOhs} />
<div class="absolute w-full pointer-events-none bottom-0 flex justify-end"> <button on:click={() => expanded.set(false)} class="absolute left-0 bottom-0 primary pointer-events-auto h-8 w-10 rounded-full">
<button on:click={() => expanded.set(false)} class="primary pointer-events-auto">Done</button> <Check class="shrink-0 w-6 h-6 m-0 p-0" color="white"/>
</div> </button>
</Popup> </Popup>
<button on:click={() => expanded.set(true)}>Pick opening hours</button> <button on:click={() => expanded.set(true)}>Pick opening hours</button>
<PublicHolidaySelector value={state.phSelectorValue} /> <PublicHolidaySelector value={state.phSelectorValue} />

View file

@ -916,6 +916,27 @@ This list will be sorted
} }
return ohs return ohs
} }
/**
* Small utility function for the OH-table. if endHour is '0', rewrite this as '24'
*
* const oh = {
* weekday: 0,
* endMinutes: 0,
* endHour: 0,
* startHour: 10,
* startMinutes: 0
* }
* OH.rangeAs24Hr(oh).endHour // => 24
*/
static rangeAs24Hr(oh: OpeningHour) {
if(oh.endHour === 0){
return {
...oh, endHour : 24
}
}
return oh
}
} }
export class ToTextualDescription { export class ToTextualDescription {