pbw-explore/src/lib/components/TimelineHeatmap.svelte

197 řádky
5.9 KiB
Svelte

<script>
import { compareAsc, addDays, addMinutes, format } from 'date-fns';
import { goto } from '$app/navigation';
import ItemLogo from '$lib/components/ItemLogo.svelte';
export let data;
export let highlightDay = false;
const startDate = '2023-06-02';
const endDate = '2023-06-11';
const segmentMinutes = 60;
const bundle = data.bundle;
const days = [];
let currentDate = startDate;
while (compareAsc(new Date(currentDate), new Date(endDate)) <= 0) {
days.push(format(new Date(currentDate), 'yyyy-MM-dd'));
currentDate = addDays(new Date(currentDate), 1);
}
const segments = [];
let currentSegment = '00:00';
const endSegmentDate = addDays(new Date(startDate), 1);
while (!segments.includes(currentSegment)) {
segments.push(format(new Date(startDate + 'T' + currentSegment), 'HH:mm'));
currentSegment = format(
addMinutes(new Date(startDate + 'T' + currentSegment), segmentMinutes),
'HH:mm'
);
}
const timelineData = {};
for (const day of days) {
for (const segment of segments) {
const score = 0;
timelineData[[day, segment].join(';')] = {
score,
events: []
};
}
}
function updateTimelineEvent(time, eventId, attendees, eventSegmentId) {
for (const day of days) {
for (const segment of segments) {
const sstart = new Date(`${day}T${segment}`);
const send = new Date(addMinutes(sstart, segmentMinutes));
if (compareAsc(sstart, time.end) < 0 && compareAsc(send, time.start) > 0) {
//console.log(timelineData[[day, segment].join(';')])
const baseScore = attendees
? attendees > 3000
? 3000
: attendees < 200
? 200
: attendees
: 0;
const target = timelineData[[day, segment].join(';')];
target.score += baseScore || 0;
target.events.push([eventId, eventSegmentId]);
}
}
}
}
const defaultTimes = '09:00-18:00';
for (const event of bundle.events) {
for (let i = 0; i < event.segments.length; i++) {
const eventSegment = event.segments[i];
const [tstart, tend] = (eventSegment.times || defaultTimes).split('-');
const time = {
start: new Date(`${eventSegment.date}T${tstart}`),
end: new Date(
`${
tend <= tstart
? format(addDays(new Date(eventSegment.date), 1), 'yyyy-MM-dd')
: eventSegment.date
}T${tend}`
)
};
updateTimelineEvent(time, event.id, eventSegment.ecap || event.attendees, i);
}
}
const segmentsMax = Math.max(...Object.keys(timelineData).map((k) => timelineData[k].score));
for (const sgm of Object.keys(timelineData)) {
const it = timelineData[sgm];
it.perc = it.score / (segmentsMax / 100);
}
let selectedSegment = null;
function makeSelected(day, segment, keys) {
const baseDate = new Date(`${day}T${segment}`);
const title =
format(baseDate, 'EEEE MMMM d | HH:mm - ') +
format(addMinutes(baseDate, segmentMinutes), 'HH:mm');
return (event) => {
selectedSegment = {
day,
segment,
data: keys,
event,
title
};
};
}
function makeClick(day, segment, keys) {
const start = new Date(`${day}T${segment}`);
const end = addMinutes(start, segmentMinutes);
//goto(`/${data.params.entry}/events?start=${start.toISOString()}&end=${end.toISOString()}`)
goto(`/${data.params.entry}/day/${day}?start=${start.toISOString()}&end=${end.toISOString()}`);
}
function hiddenSelected() {
selectedSegment = null;
}
function eventDetail(id) {
return bundle.events.find((e) => e.id === id);
}
</script>
<div class="w-full mb-10 relative">
{#if selectedSegment}
<div
class="absolute top-[81px] w-[300px] border bg-white z-50 py-2 px-4 {selectedSegment
? 'hidden md:block'
: 'hidden'}"
style="left: {selectedSegment.event.layerX}px;"
>
<div class="uppercase">{selectedSegment.title}</div>
<div class="text-xl mt-4">
{#each selectedSegment.data.events.map((e) => {
return [eventDetail(e[0]), e[1]];
}) as [item, segmentId]}
<div class="flex gap-2 items-center mb-1">
<div class="">
<ItemLogo {item} width="h-8" />
</div>
<div class="">
{item.shortname || item.name}
{#if item.segments[segmentId].title}
- {item.segments[segmentId].title}
{/if}
</div>
</div>
{/each}
</div>
</div>
{/if}
<div class="flex text-center text-gray-400 text-sm items-end h-10">
{#each days as day}
<div
class="mb-1 uppercase {highlightDay && highlightDay !== day
? 'text-gray-400 text-lg'
: 'text-pbw-red text-lg'}"
style="width: {1 / (days.length / 100)}%;"
>
<a href="/23/day/{format(new Date(day), 'yyyy-MM-dd')}"
><span class="hidden md:inline-block">{format(new Date(day), 'eee ')}</span>
{format(new Date(day), 'd')}</a
>
</div>
{/each}
</div>
<div class="w-full border {highlightDay ? 'border-gray-300' : 'border-pbw-red'} flex">
{#each days as day, i}
<div
class="h-10 flex flex-grow hover:bg-pbw-yellow/20"
style="width: {1 / (days.length / 100)}%; {highlightDay && highlightDay === day
? 'border: 1px solid rgb(255, 22, 22);'
: !highlightDay
? 'border-right: 1px solid rgb(255, 22, 22);'
: days.length - i > 1
? 'border-right: 1px solid silver;'
: ''}"
>
{#each segments as segment}
<div
id="{day}-{segment}"
data-events={timelineData[[day, segment].join(';')].events}
data-score={timelineData[[day, segment].join(';')].score}
class="md:hover:border md:hover:border-pbw-yellow flex-grow cursor-pointer"
style="width: {1 / (segments.length / 50)}%; background-color: rgba({!highlightDay ||
highlightDay === day
? '255, 22, 22'
: '115, 115, 115'}, {timelineData[[day, segment].join(';')].perc}%);"
on:click={makeClick(day, segment, timelineData[[day, segment].join(';')])}
on:mouseenter={makeSelected(day, segment, timelineData[[day, segment].join(';')])}
on:mouseleave={hiddenSelected}
/>
{/each}
</div>
{/each}
</div>
</div>