Files
pardis/src/components/Timeline.svelte

126 lines
3.1 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script>
import { fmtDuration, fmtTime, roundTs } from '../lib/time.js';
let { entries = [], rounding = 15, now = Date.now(), onselect = null } = $props();
const ordered = $derived([...entries].sort((a, b) => a.seq - b.seq));
/**
* Durations are measured between the *displayed* boundaries, so a row can never
* read "10:45–11:45" next to "1h02". The running entry is the exception: its end
* is not shown as a time, so it keeps a live, unrounded count.
*/
function duration(entry) {
if (entry.endTs == null) return Math.max(0, now - entry.startTs);
return Math.max(
0,
roundTs(entry.endTs, rounding) - roundTs(entry.startTs, rounding),
);
}
</script>
{#if ordered.length}
<ul class="timeline">
{#each ordered as entry (entry.id)}
<li>
<!-- The whole row is the target: correcting a mis-tapped task should not
require hunting for a small pencil. -->
<button
type="button"
class:running={entry.endTs == null}
onclick={() => onselect?.(entry)}
disabled={!onselect}
>
<span class="time tnum">
{fmtTime(entry.startTs, rounding)}–{entry.endTs == null
? '…'
: fmtTime(entry.endTs, rounding)}
</span>
<span class="dur tnum">{fmtDuration(duration(entry))}</span>
<span class="what">
<strong>{entry.kind === 'break' && entry.task !== 'Pause' ? `Pause · ${entry.task}` : entry.task}</strong>
{#if entry.automatic}<span class="dim">Auto</span>{/if}
{#if entry.client}<span class="dim">{entry.client}</span>{/if}
</span>
{#if onselect}<span class="edit" aria-hidden="true">✎</span>{/if}
</button>
</li>
{/each}
</ul>
{/if}
<style>
.timeline {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
}
li + li {
border-top: 2px solid var(--line);
}
/* Times and totals on one line, the task name on its own full-width line
below: at phone width a shared row squeezes long names into mid-word
breaks. */
li button {
width: 100%;
display: grid;
grid-template-columns: auto 1fr auto;
grid-template-areas:
'time dur edit'
'what what edit';
align-items: center;
column-gap: 10px;
row-gap: 2px;
padding: 12px 4px;
border: 0;
border-radius: 0;
text-align: left;
font-weight: 400;
min-height: var(--tap);
}
li button[disabled] {
opacity: 1;
}
li button.running {
font-weight: 800;
}
.time {
grid-area: time;
font-size: 0.95rem;
font-weight: 700;
}
.dur {
grid-area: dur;
justify-self: end;
font-weight: 700;
}
.edit {
grid-area: edit;
padding-left: 6px;
color: var(--fg-dim);
}
.what {
grid-area: what;
display: flex;
flex-direction: column;
/* Let the column shrink inside the grid so long names wrap rather than
overflow — a truncated task name is useless at a glance. */
min-width: 0;
overflow-wrap: break-word;
}
.what span {
font-size: 0.85rem;
}
</style>