adds profile page

This commit is contained in:
Christian Beutel
2024-11-03 15:13:25 +01:00
parent 0d8ff5c83b
commit bab22301af
31 changed files with 882 additions and 133 deletions

View File

@@ -4,7 +4,10 @@ import { error, json, type RequestEvent } from '@sveltejs/kit';
export async function GET(event: RequestEvent) {
try {
const r: SummitLog[] = await pb.collection('summit_logs').getFullList<SummitLog>()
const r: SummitLog[] = await pb.collection('summit_logs').getFullList<SummitLog>({
expand: "trails_via_summit_logs.category",
sort: "+date"
})
return json(r)
} catch (e: any) {
throw error(e.status, e);

View File

@@ -0,0 +1,306 @@
<script lang="ts">
import Calendar from "$lib/components/base/calendar.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 { currentUser } from "$lib/stores/user_store";
import { getFileURL } from "$lib/util/file_util";
import {
formatDistance,
formatElevation,
formatTimeHHMM,
} from "$lib/util/format_util";
import {
ArcElement,
BarElement,
CategoryScale,
Chart as ChartJS,
Legend,
LinearScale,
Title,
Tooltip,
} from "chart.js";
import { Bar, Pie } from "svelte-chartjs";
import { _ } from "svelte-i18n";
ChartJS.register(
Title,
Tooltip,
Legend,
ArcElement,
CategoryScale,
LinearScale,
BarElement,
);
const barChartSelectItems: SelectItem[] = [
{
text: $_("distance"),
value: "distance",
},
{
text: $_("duration"),
value: "duration",
},
{
text: $_("elevation-gain"),
value: "elevation_gain",
},
{
text: $_("elevation-loss"),
value: "elevation_loss",
},
];
let barChartSelectedValue = barChartSelectItems[0].value;
const categoryColors = [
"#fb8500",
"#ffb703",
"#06d6a0",
"#219ebc",
"#8ecae6",
"#ffafcc",
];
$: categories = $summitLogs.reduce(
(acc, log) => {
const cat =
log.expand.trails_via_summit_logs?.at(0)?.expand.category
?.name ?? "unknown";
acc[$_(cat)] = (acc[$_(cat)] || 0) + 1;
return acc;
},
{} as Record<string, number>,
);
$: categoryLabels = Object.keys(categories).sort();
$: categoryValues = Object.values(categories);
$: categoryColorMap = Object.fromEntries(
categoryLabels.map((label, index) => [
label,
categoryColors[index % categoryColors.length],
]),
);
$: categoryChartData = {
labels: categoryLabels,
datasets: [
{
data: categoryValues,
backgroundColor: categoryColors,
},
],
};
$: barChartUnit =
barChartSelectedValue == "duration"
? "min"
: barChartSelectedValue == "distance"
? "km"
: "m";
$: barChartDataByDate = $summitLogs.reduce(
(acc, log) => {
const date = new Date(log.date).toLocaleDateString(undefined, {
month: "2-digit",
day: "2-digit",
year: "numeric",
timeZone: "UTC",
});
acc[date] =
(acc[date] || 0) + ((log as any)[barChartSelectedValue] ?? 0);
if (barChartSelectedValue === "distance") {
acc[date] = acc[date] / 1000;
}
return acc;
},
{} as Record<string, number>,
);
$: barChartLabels = Object.keys(barChartDataByDate); // Dates as labels
$: barChartValues = Object.values(barChartDataByDate);
$: barChartData = {
labels: barChartLabels,
datasets: [
{
label: barChartSelectItems.find(
(i) => i.value === barChartSelectedValue,
)?.text,
data: barChartValues,
backgroundColor: "#3388ff",
borderRadius: 15,
},
],
};
$: totalDistance = $summitLogs.reduce(
(sum, log) => sum + (log.distance ?? 0),
0,
);
$: totalDuration = $summitLogs.reduce(
(sum, log) => sum + (log.duration ?? 0),
0,
);
$: totalElevationGain = $summitLogs.reduce(
(sum, log) => sum + (log.elevation_gain ?? 0),
0,
);
$: totalElevationLoss = $summitLogs.reduce(
(sum, log) => sum + (log.elevation_loss ?? 0),
0,
);
</script>
<div class="grid grid-cols-5 gap-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"
>
{#if $currentUser}
<div class="flex items-center gap-x-6">
<img
class="rounded-full w-24 aspect-square overflow-hidden"
src={getFileURL($currentUser, $currentUser.avatar) ||
`https://api.dicebear.com/7.x/initials/svg?seed=${$currentUser.username}&backgroundType=gradientLinear`}
alt="avatar"
/>
<div>
<h4 class="text-2xl font-semibold col-start-2">
{$currentUser.username}
</h4>
<p class="text-sm">
<span class="text-gray-500">Joined:</span>
{new Date(
$currentUser.created ?? "",
).toLocaleDateString(undefined, {
month: "2-digit",
day: "2-digit",
year: "numeric",
timeZone: "UTC",
})}
</p>
</div>
</div>
{/if}
<Calendar 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
>
<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
>
<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}`,
},
},
},
scales: {
y: {
ticks: {
callback: function (value, index, ticks) {
return value + " " + barChartUnit;
},
},
},
},
}}
></Bar>
</div>
<div class="col-span-5 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>
</div>

View File

@@ -0,0 +1,23 @@
import { summit_logs_index, summitLogs } 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);
if (!log.expand) {
log.expand = {};
}
log.expand.gpx_data = gpxData;
}
console.log(logs);
};

View File

@@ -428,7 +428,6 @@
function saveSummitLog(e: CustomEvent<SummitLog>) {
const savedSummitLog = e.detail;
console.log(savedSummitLog);
let editedSummitLogIndex = $form.expand.summit_logs.findIndex(
(s) => s.id == savedSummitLog.id,