finishes profile page

This commit is contained in:
Christian Beutel
2024-11-05 00:47:47 +01:00
parent bab22301af
commit 87eb879c7f
20 changed files with 541 additions and 187 deletions

View File

@@ -3,10 +3,13 @@ import { pb } from '$lib/pocketbase';
import { error, json, type RequestEvent } from '@sveltejs/kit';
export async function GET(event: RequestEvent) {
const filter = event.url.searchParams.get('filter') ?? ""
try {
const r: SummitLog[] = await pb.collection('summit_logs').getFullList<SummitLog>({
expand: "trails_via_summit_logs.category",
sort: "+date"
sort: "+date",
filter: filter
})
return json(r)
} catch (e: any) {

View File

@@ -1,15 +1,23 @@
<script lang="ts">
import { page } from "$app/stores";
import Calendar from "$lib/components/base/calendar.svelte";
import Datepicker from "$lib/components/base/datepicker.svelte";
import MultiSelect from "$lib/components/base/multi_select.svelte";
import Select, {
type SelectItem,
} from "$lib/components/base/select.svelte";
import SummitLogTable from "$lib/components/summit_log/summit_log_table.svelte";
import { summitLogs } from "$lib/stores/summit_log_store";
import { categories } from "$lib/stores/category_store.js";
import {
summit_logs_index,
summitLogs,
} from "$lib/stores/summit_log_store";
import { currentUser } from "$lib/stores/user_store";
import { getFileURL } from "$lib/util/file_util";
import {
formatDistance,
formatElevation,
formatSpeed,
formatTimeHHMM,
} from "$lib/util/format_util";
import {
@@ -25,6 +33,8 @@
import { Bar, Pie } from "svelte-chartjs";
import { _ } from "svelte-i18n";
export let data;
ChartJS.register(
Title,
Tooltip,
@@ -35,6 +45,13 @@
BarElement,
);
const filter = data.filter;
const categorySelectItems: SelectItem[] = $categories.map((c) => ({
value: c.id,
text: c.name,
}));
const barChartSelectItems: SelectItem[] = [
{
text: $_("distance"),
@@ -65,7 +82,13 @@
"#ffafcc",
];
$: categories = $summitLogs.reduce(
const conversionFactors = {
distance: 0.621371,
elevation_gain: 3.28084,
elevation_loss: 3.28084,
};
$: logCategories = $summitLogs.reduce(
(acc, log) => {
const cat =
log.expand.trails_via_summit_logs?.at(0)?.expand.category
@@ -76,8 +99,8 @@
{} as Record<string, number>,
);
$: categoryLabels = Object.keys(categories).sort();
$: categoryValues = Object.values(categories);
$: categoryLabels = Object.keys(logCategories).sort();
$: categoryValues = Object.values(logCategories);
$: categoryColorMap = Object.fromEntries(
categoryLabels.map((label, index) => [
label,
@@ -95,12 +118,19 @@
],
};
$: barChartUnit =
barChartSelectedValue == "duration"
? "min"
: barChartSelectedValue == "distance"
? "km"
: "m";
function barChartUnit() {
const unit = $page.data.settings?.unit ?? "metric";
switch (barChartSelectedValue) {
case "duration":
return "min";
case "elevation_gain":
case "elevation_loss":
return unit == "metric" ? "m" : "ft";
case "distance":
return unit == "metric" ? "km" : "mi";
}
}
$: barChartDataByDate = $summitLogs.reduce(
(acc, log) => {
@@ -115,6 +145,11 @@
if (barChartSelectedValue === "distance") {
acc[date] = acc[date] / 1000;
}
if ($page.data.settings?.unit !== "metric") {
acc[date] =
acc[date] *
(conversionFactors as any)[barChartSelectedValue];
}
return acc;
},
{} as Record<string, number>,
@@ -156,11 +191,60 @@
(sum, log) => sum + (log.elevation_loss ?? 0),
0,
);
$: averageSpeed =
totalDuration > 0 ? totalDistance / totalDuration : undefined;
function updateFilterCategory(categories: SelectItem[]) {
filter.category = categories.map((c) => c.value);
loadSummitLogs();
}
function handleDateClick(date: Date) {
const datePlusN = new Date();
datePlusN.setDate(date.getDate() + 1);
filter.startDate = datePlusN.toISOString().slice(0, 10);
datePlusN.setDate(date.getDate() + 2);
filter.endDate = datePlusN.toISOString().slice(0, 10);
loadSummitLogs();
}
async function loadSummitLogs() {
const logs = await summit_logs_index(filter);
summitLogs.set(logs);
}
</script>
<div class="grid grid-cols-5 gap-4 items-start max-w-6xl mx-auto">
<svelte:head>
<title>{$_("profile")} | wanderer</title>
</svelte:head>
<div
class="grid grid-cols-1 md:grid-cols-[356px_minmax(0,_1fr)] gap-y-4 items-start max-w-6xl mx-auto"
>
<div
class="border border-input-border rounded-xl p-6 col-span-2 row-span-2 space-y-6"
class="flex flex-wrap md:flex-nowrap col-start-1 md:col-start-2 gap-x-4 justify-end"
>
<MultiSelect
on:change={(e) => updateFilterCategory(e.detail)}
label={$_("categories")}
items={categorySelectItems}
placeholder={`${$_("filter-categories")}...`}
></MultiSelect>
<Datepicker
on:change={loadSummitLogs}
bind:value={filter.startDate}
label={$_("after")}
></Datepicker>
<Datepicker
on:change={loadSummitLogs}
bind:value={filter.endDate}
label={$_("before")}
></Datepicker>
</div>
<div
class="border border-input-border rounded-xl p-6 lg:row-span-3 space-y-6 grow-0 md:mr-4"
>
{#if $currentUser}
<div class="flex items-center gap-x-6">
@@ -188,119 +272,146 @@
</div>
</div>
{/if}
<Calendar logs={$summitLogs} colorMap={categoryColorMap}></Calendar>
<Calendar
on:click={(e) => handleDateClick(e.detail)}
logs={$summitLogs}
colorMap={categoryColorMap}
></Calendar>
</div>
<div
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
>
<span class="text-gray-500 font-semibold text-lg self-start"
><i class="fa fa-hashtag mr-3"></i>{$_("activity", {
values: { n: 2 },
})}</span
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
>
<p class="text-4xl font-bold">{$summitLogs.length}</p>
</div>
<div
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
>
<span class="text-gray-500 font-semibold text-lg self-start"
><i class="fa fa-left-right mr-3"></i>{$_("distance")}</span
>
<p class="text-4xl font-bold">{formatDistance(totalDistance)}</p>
</div>
<div
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
>
<span class="text-gray-500 font-semibold text-lg self-start"
><i class="fa fa-clock mr-3"></i>{$_("duration")}</span
>
<p class="text-4xl font-bold">{formatTimeHHMM(totalDuration)}</p>
</div>
<div
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
>
<span class="text-gray-500 font-semibold text-lg self-start"
><i class="fa fa-arrow-trend-up mr-3"></i>{$_(
"elevation-gain",
)}</span
>
<p class="text-4xl font-bold">{formatElevation(totalElevationGain)}</p>
</div>
<div
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
>
<span class="text-gray-500 font-semibold text-lg self-start"
><i class="fa fa-arrow-trend-down mr-3"></i>{$_(
"elevation-loss",
)}</span
>
<p class="text-4xl font-bold">{formatElevation(totalElevationLoss)}</p>
</div>
<div
class="col-start-1 col-span-1 border border-input-border rounded-xl p-6 space-y-4"
>
<span class="text-gray-500 font-semibold text-lg"
><i class="fa fa-person-hiking mr-3"></i>{$_("categories")}</span
>
<Pie
data={categoryChartData}
options={{
responsive: true,
plugins: {
legend: {
position: "bottom",
},
},
}}
/>
</div>
<div
class="h-full col-span-3 space-y-2 border border-input-border rounded-xl p-6"
>
<div class="flex justify-between">
<span class="text-gray-500 font-semibold text-lg"
><i class="fa fa-calendar mr-3"></i>{$_("activity", {
values: { n: 1 },
<span class="text-gray-500 font-semibold text-lg self-start"
><i class="fa fa-hashtag mr-3"></i>{$_("activity", {
values: { n: 2 },
})}</span
>
<Select
bind:value={barChartSelectedValue}
items={barChartSelectItems}
></Select>
<p class="text-4xl font-bold">{$summitLogs.length}</p>
</div>
<div
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
>
<span class="text-gray-500 font-semibold text-lg self-start"
><i class="fa fa-left-right mr-3"></i>{$_("distance")}</span
>
<p class="text-4xl font-bold">{formatDistance(totalDistance)}</p>
</div>
<Bar
data={barChartData}
options={{
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
label: (item) =>
`${item.dataset.label}: ${item.formattedValue} ${barChartUnit}`,
<div
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
>
<span class="text-gray-500 font-semibold text-lg self-start"
><i class="fa fa-clock mr-3"></i>{$_("duration")}</span
>
<p class="text-4xl font-bold">{formatTimeHHMM(totalDuration)}</p>
</div>
<div
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
>
<span class="text-gray-500 font-semibold text-lg self-start"
><i class="fa fa-arrow-trend-up mr-3"></i>{$_(
"elevation-gain",
)}</span
>
<p class="text-4xl font-bold">
{formatElevation(totalElevationGain)}
</p>
</div>
<div
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
>
<span class="text-gray-500 font-semibold text-lg self-start"
><i class="fa fa-arrow-trend-down mr-3"></i>{$_(
"elevation-loss",
)}</span
>
<p class="text-4xl font-bold">
{formatElevation(totalElevationLoss)}
</p>
</div>
<div
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
>
<span class="text-gray-500 font-semibold text-lg self-start"
><i class="fa fa-arrow-trend-down mr-3"></i>{$_(
"average-speed",
)}</span
>
<p class="text-4xl font-bold">{formatSpeed(averageSpeed)}</p>
</div>
<div
class="col-start-1 col-span-1 border border-input-border rounded-xl p-6 space-y-4"
>
<span class="text-gray-500 font-semibold text-lg"
><i class="fa fa-person-hiking mr-3"></i>{$_(
"categories",
)}</span
>
<Pie
data={categoryChartData}
options={{
responsive: true,
plugins: {
legend: {
position: "bottom",
},
},
},
scales: {
y: {
ticks: {
callback: function (value, index, ticks) {
return value + " " + barChartUnit;
}}
/>
</div>
<div
class="h-full md:col-span-2 space-y-2 border border-input-border rounded-xl p-6"
>
<div class="flex justify-between">
<span class="text-gray-500 font-semibold text-lg"
><i class="fa fa-calendar mr-3"></i>{$_("activity", {
values: { n: 1 },
})}</span
>
<Select
bind:value={barChartSelectedValue}
items={barChartSelectItems}
></Select>
</div>
<Bar
data={barChartData}
options={{
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
label: (item) =>
`${item.dataset.label}: ${item.formattedValue} ${barChartUnit()}`,
},
},
},
},
}}
></Bar>
scales: {
y: {
ticks: {
callback: function (value, index, ticks) {
return value + " " + barChartUnit();
},
},
},
},
}}
></Bar>
</div>
</div>
<div class="col-span-5 border border-input-border rounded-xl p-6 space-y-6">
<div
class="col-span-1 md:col-span-2 border border-input-border rounded-xl p-6 space-y-6"
>
<span class="text-gray-500 font-semibold text-lg"
><i class="fa fa-table mr-3"></i>{$_("all-activities")}</span
>
<SummitLogTable summitLogs={$summitLogs} showCategory></SummitLogTable>
<div class=" overflow-x-scroll">
<SummitLogTable summitLogs={$summitLogs} showCategory showTrail
></SummitLogTable>
</div>
</div>
</div>

View File

@@ -1,23 +1,23 @@
import { summit_logs_index, summitLogs } from "$lib/stores/summit_log_store";
import type { SummitLogFilter } from "$lib/models/summit_log";
import { categories_index } from "$lib/stores/category_store";
import { summit_logs_index } from "$lib/stores/summit_log_store";
import { fetchGPX } from "$lib/stores/trail_store";
import { type ServerLoad } from "@sveltejs/kit";
import { get } from "svelte/store";
export const load: ServerLoad = async ({ params, locals, fetch }) => {
const logs = await summit_logs_index(fetch);
for (const log of logs) {
if (!log.gpx) {
continue
}
const gpxData: string = await fetchGPX(log as any, fetch);
const date = new Date(), y = date.getFullYear(), m = date.getMonth();
const firstDay = new Date(y, m, 1);
const lastDay = new Date(y, m + 1, 0);
if (!log.expand) {
log.expand = {};
}
log.expand.gpx_data = gpxData;
const categories = await categories_index(fetch)
const filter: SummitLogFilter = {
startDate: firstDay.toISOString().slice(0, 10),
endDate: lastDay.toISOString().slice(0, 10),
category: []
}
const logs = await summit_logs_index(filter, fetch);
console.log(logs);
return {filter}
};