This commit is contained in:
2024-03-02 00:34:29 -05:00
committed by inportb
commit 7be5ebcdaa
49 changed files with 3907 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
import { get_api_orders } from '$lib/backend.js';
/** @type {import('./$types').PageLoad} */
export async function load({ params, fetch }) {
let mrn = params.mrn, offset = 30;
let reports = await get_api_orders({ fetch, mrn, alpha: 'T-' + offset, omega: 'N' });
return {
mrn, reports, offset
};
}

View File

@@ -0,0 +1,166 @@
<script>
import { tick } from 'svelte';
import { debounce, escapeHTML, escapeRegExp, strHashHSL, isInViewport, filter_pattern, filter_test, filter_mark } from '$lib/util.js';
import { get_api_orders } from '$lib/backend.js';
export let data;
let query = '', pattern = null, selection = null, all_reports = decorate(data.reports);
let debounced_pattern = debounce((/*query*/) => (pattern = query ? filter_pattern(escapeHTML(query)) : null), 200);
$: debounced_pattern(query); // argument `query` is for reactivity hinting only
function decorate(xs) {
return xs.map(x => Object.assign({ _content: escapeHTML(Object.values(x).join('\x00')), _uid: x.body, _ts: new Date(x.datetime_entered) }, x));
}
async function loadmore(evt, factor = 1.5) {
if(loadmore.loading) return;
loadmore.loading = true;
try {
let reports = await get_api_orders({ mrn: data.mrn, omega: 'T-' + (data.offset + 1), alpha: 'T-' + (data.offset += (loadmore.limit = (factor*loadmore.limit)|0)) });
Array.prototype.push.apply(all_reports, decorate(reports));
all_reports = all_reports; // reactivity hint
} finally {
loadmore.loading = false;
}
}
loadmore.loading = false;
loadmore.limit = 30;
(async function loadinit(target = 16, requests = 4) {
for(let i = 0; (i < requests) && (all_reports.length < target); ++i) await loadmore();
})();
const observer = new IntersectionObserver((entries) => { if((!query) && (entries[0].isIntersecting)) loadmore(null); }, { root: null, rootMargin: '0px', threshold: 0.5 });
let bottom = null;
$: {
observer.disconnect();
if(bottom) observer.observe(bottom);
}
let reportlist;
async function scroll(selection) {
if(selection) {
await tick();
const el = reportlist.querySelector('.active');
if((el) && (!isInViewport(el, true))) el.scrollIntoView({ block: 'center' });
} else {
const items = reportlist.children;
for(let i = 0, el; i < items.length; ++i) if(isInViewport(el = items[i])) {
await tick();
el.scrollIntoView({ block: 'start' });
break;
}
}
}
$: if(reportlist) scroll(selection);
</script>
<svelte:head>
<title>Orders</title>
</svelte:head>
{#if selection}
<div class="halfpane rightpane">
<nav class="navbar bg-body-secondary">
<div class="container-fluid">
<span class="navbar-brand">{selection.text || ''}</span>
<button type="button" class="btn btn-outline-light" on:click={() => selection = null}>❌</button>
</div>
</nav>
<div class="container-fluid"><dl class="report">{#each Object.entries(selection) as entry}{#if entry[0].charAt(0) != '_'}<dt>{entry[0]}</dt><dd>{entry[1]}</dd>{/if}{/each}</dl></div>
</div>
{/if}
<div class={selection ? 'halfpane leftpane' : ''}>
<div class="card {selection ? '' : 'mb-3 shadow'}">
<nav class="navbar bg-body-tertiary">
<form class="container-fluid">
<div class="input-group">
<span class="input-group-text">Orders</span>
<input type="text" class="form-control" placeholder="Filter..." bind:value={query}>
{#if query}<button type="button" class="btn btn-outline-secondary" on:click={() => query = ''}>❌</button>{/if}
</div>
</form>
</nav>
<ul class="list-group list-group-flush" bind:this={reportlist}>
{#if pattern}
{#each all_reports as row}
{#if filter_test(pattern, row._content)}
<li class="list-group-item" class:active={(selection) && (selection._uid == row._uid)} on:click={() => selection = selection !== row ? row : null}>
<div class="singleline" style="font-weight: bold;">{row.text || ''}</div>
<div class="report">{@html pattern ? filter_mark(pattern, escapeHTML(row.body)) : escapeHTML(row.body)}</div>
</li>
{/if}
{/each}
{:else}
{#each all_reports as row}
<li class="list-group-item" class:active={(selection) && (selection._uid == row._uid)} on:click={() => selection = selection !== row ? row : null}>
<div class="singleline" style="font-weight: bold;">{row.text || ''}</div>
<div class="report">{@html pattern ? filter_mark(pattern, escapeHTML(row.body)) : escapeHTML(row.body)}</div>
</li>
{/each}
{/if}
<li class="list-group-item" style="padding: 0;" bind:this={bottom}>{#if loadmore.loading}<button type="button" class="btn btn-primary w-100" disabled>Loading...</button>{:else}<button type="button" class="btn btn-primary w-100" on:click={loadmore}>Load more</button>{/if}</li>
</ul>
</div>
</div>
<style>
:global(div.report mark) {
padding: 0;
font-weight: bold;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: auto;
}
}
.navbar {
position: sticky;
z-index: 1020;
top: 3.5rem;
}
.leftpane {
display: none;
}
li.active {
scroll-margin-top: 3.5rem;
}
div.singleline {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.report {
font-family: monospace;
white-space: pre-wrap;
}
@media screen and (min-width: 720px) {
.halfpane {
position: absolute;
top: 3.5rem;
bottom: 0;
width: 50%;
overflow: auto;
}
.leftpane {
display: block;
width: 33%;
left: 0;
z-index: -1;
direction: rtl;
}
.leftpane > * {
direction: ltr;
}
.rightpane {
width: 67%;
right: 0;
box-shadow: var(--bs-box-shadow);
}
.halfpane .navbar {
top: 0;
}
}
</style>