First
This commit is contained in:
10
frontend/src/routes/chart/[mrn]/orders/+page.js
Normal file
10
frontend/src/routes/chart/[mrn]/orders/+page.js
Normal 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
|
||||
};
|
||||
}
|
166
frontend/src/routes/chart/[mrn]/orders/+page.svelte
Normal file
166
frontend/src/routes/chart/[mrn]/orders/+page.svelte
Normal 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>
|
Reference in New Issue
Block a user