Status route, preparing to make Overlay/Portal component
This commit is contained in:
69
src/lib/icons/BatteryIcon.svelte
Normal file
69
src/lib/icons/BatteryIcon.svelte
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<div class="flex items-center">
|
||||||
|
<div class="outer">
|
||||||
|
<div class="inner"></div>
|
||||||
|
<div class="inner"></div>
|
||||||
|
<div class="inner"></div>
|
||||||
|
</div>
|
||||||
|
<div class="pole"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="css">
|
||||||
|
.outer {
|
||||||
|
width: 50px;
|
||||||
|
height: 25px;
|
||||||
|
border: 3px solid #fff;
|
||||||
|
display: flex;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
flex: 1;
|
||||||
|
border-radius: 0.2px;
|
||||||
|
opacity: 0;
|
||||||
|
animation: ani1 2.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner:nth-child(2) {
|
||||||
|
margin: 0 1px;
|
||||||
|
animation: ani2 2.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner:nth-child(3) {
|
||||||
|
animation: ani3 2.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pole {
|
||||||
|
width: 4px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 0 1px 1px 0;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ani1 {
|
||||||
|
33%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ani2 {
|
||||||
|
0%, 33% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
66%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ani3 {
|
||||||
|
0%, 66% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
58
src/lib/types/ChargeController.ts
Normal file
58
src/lib/types/ChargeController.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
export type ChargeUser = {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LastPower = {
|
||||||
|
chargingstation: number;
|
||||||
|
power: number;
|
||||||
|
timestamp: string; // ISO 8601 datetime string
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ActiveCharge = {
|
||||||
|
id: number;
|
||||||
|
start: string; // ISO 8601 datetime string
|
||||||
|
stop: string | null; // ISO 8601 datetime string
|
||||||
|
user: ChargeUser;
|
||||||
|
last_nonzero_power_time: string | null; // ISO 8601 datetime string
|
||||||
|
energy_wh: number;
|
||||||
|
last_power: LastPower | null;
|
||||||
|
// coupon: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// custom_message_fallback_lang is guaranteed to exist
|
||||||
|
export type CustomMessage = {
|
||||||
|
en?: string,
|
||||||
|
it?: string,
|
||||||
|
de?: string,
|
||||||
|
fr?: string,
|
||||||
|
nl?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChargeController = {
|
||||||
|
qrcodeid: number;
|
||||||
|
active_charge: ActiveCharge | null;
|
||||||
|
park: number;
|
||||||
|
park_bnum: number;
|
||||||
|
evse_id: string;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
accept_guests: boolean;
|
||||||
|
is_payment_configured: boolean;
|
||||||
|
price: number;
|
||||||
|
override_logo_url: string;
|
||||||
|
show_phrases_frontend: boolean;
|
||||||
|
logged_user: ChargeUser | null;
|
||||||
|
has_charge_permission: boolean;
|
||||||
|
show_custom_message: boolean;
|
||||||
|
custom_message: CustomMessage;
|
||||||
|
country_alpha2: string;
|
||||||
|
custom_message_fallback_lang: string;
|
||||||
|
imprint_cents: number;
|
||||||
|
google_pay: boolean;
|
||||||
|
coupon_enabled: boolean;
|
||||||
|
last_power_timestamp: string | null; // ISO 8601 datetime string
|
||||||
|
suspended: boolean;
|
||||||
|
test_mode: boolean;
|
||||||
|
mandatory_email: boolean;
|
||||||
|
};
|
||||||
9
src/lib/types/User.ts
Normal file
9
src/lib/types/User.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export type User = {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
is_active: boolean;
|
||||||
|
is_staff: boolean;
|
||||||
|
is_superuser: boolean;
|
||||||
|
is_readonly: boolean;
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { LayoutLoad } from './$types';
|
import type { LayoutLoad } from './$types';
|
||||||
|
import type { User } from '$lib/types/User';
|
||||||
|
|
||||||
export const load: LayoutLoad = async ({ fetch }) => {
|
export const load: LayoutLoad = async ({ fetch }) => {
|
||||||
const userRes = await fetch('/api/public/1/auth/myself/', {
|
const userRes = await fetch('/api/public/1/auth/myself/', {
|
||||||
@@ -9,9 +10,9 @@ export const load: LayoutLoad = async ({ fetch }) => {
|
|||||||
});
|
});
|
||||||
if (!userRes.ok) return {
|
if (!userRes.ok) return {
|
||||||
user: null,
|
user: null,
|
||||||
chargePermission: [],
|
chargePermission: [] as number[],
|
||||||
};
|
};
|
||||||
const user = await userRes.json();
|
const user: User = await userRes.json();
|
||||||
const chargePermissionRes = await fetch(`/api/v3/users/${user['id']}/charge_permissions/`);
|
const chargePermissionRes = await fetch(`/api/v3/users/${user['id']}/charge_permissions/`);
|
||||||
const chargePermission: number[] = chargePermissionRes.ok ? (await chargePermissionRes.json())['authorized_qrcode'] : [];
|
const chargePermission: number[] = chargePermissionRes.ok ? (await chargePermissionRes.json())['authorized_qrcode'] : [];
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import type { LayoutLoad } from './$types';
|
import type { LayoutLoad } from './$types';
|
||||||
import { error } from "@sveltejs/kit";
|
import { error } from "@sveltejs/kit";
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
|
import type { ChargeController } from "$lib/types/ChargeController";
|
||||||
|
|
||||||
const QrcodeType = type("string.integer.parse");
|
const QrcodeType = type("string.integer.parse");
|
||||||
|
|
||||||
export const load: LayoutLoad = async ({ params, fetch, parent }) => {
|
export const load: LayoutLoad = async ({ params, fetch, parent, depends }) => {
|
||||||
const qrcode = QrcodeType(params.qrcode);
|
const qrcode = QrcodeType(params.qrcode);
|
||||||
if (qrcode instanceof type.errors) error(400, 'invalid qrcode');
|
if (qrcode instanceof type.errors) error(400, 'invalid qrcode');
|
||||||
const cc = await fetch(`/api/v2/chargecontroller/${qrcode}/`, {
|
const cc = await fetch(`/api/v2/chargecontroller/${qrcode}/`, {
|
||||||
@@ -13,10 +14,12 @@ export const load: LayoutLoad = async ({ params, fetch, parent }) => {
|
|||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
depends('app:chargecontroller');
|
||||||
const parentData = await parent();
|
const parentData = await parent();
|
||||||
|
const chargecontroller: ChargeController = await cc.json();
|
||||||
return {
|
return {
|
||||||
...parentData,
|
...parentData,
|
||||||
qrcode: qrcode,
|
qrcode: qrcode,
|
||||||
chargecontroller: await cc.json(),
|
chargecontroller: chargecontroller,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { slide } from 'svelte/transition';
|
import { slide, scale, fade } from 'svelte/transition';
|
||||||
import { scale } from 'svelte/transition';
|
import { goto } from "$app/navigation";
|
||||||
|
|
||||||
|
let { data } = $props();
|
||||||
|
|
||||||
let showOverlay = $state(false);
|
let showOverlay = $state(false);
|
||||||
let startingCharge = $state(false);
|
let startingCharge = $state(false);
|
||||||
@@ -11,6 +13,7 @@
|
|||||||
try {
|
try {
|
||||||
// TODO: StartCharge fetch request
|
// TODO: StartCharge fetch request
|
||||||
console.log("nop");
|
console.log("nop");
|
||||||
|
await goto(`/${data.qrcode}/status`);
|
||||||
} finally {
|
} finally {
|
||||||
startingCharge = false;
|
startingCharge = false;
|
||||||
}
|
}
|
||||||
@@ -20,17 +23,26 @@
|
|||||||
if (startingCharge) return;
|
if (startingCharge) return;
|
||||||
showOverlay = false;
|
showOverlay = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Try to make a real component out of this "modal" using a Portal: https://stackoverflow.com/a/62733195
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="grid grid-rows-6 h-full w-full">
|
<div class="grid grid-rows-6 h-full w-full">
|
||||||
<div class="row-start-5 row-span-1 col-start-1 flex flex-col justify-center items-center">
|
<div class="row-start-5 col-start-1 flex flex-col justify-center items-center">
|
||||||
<div class="pointer-events-auto">
|
<div class="pointer-events-auto">
|
||||||
<button class="btn btn-primary btn-lg uppercase" onclick={() => showOverlay = true}>Attivare la Ricarica</button>
|
<button class="btn btn-primary btn-lg uppercase" onclick={() => showOverlay = true}>Attivare la Ricarica</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-start-1 row-span-6 col-start-1 flex flex-col justify-end items-center z-10">
|
{#if showOverlay}
|
||||||
|
<button type="button" class="row-start-1 row-span-6 col-start-1 z-10 bg-base-100/50 pointer-events-auto"
|
||||||
|
transition:fade={{duration:200}}
|
||||||
|
onclick={closeOverlay}
|
||||||
|
aria-label="Close Overlay"
|
||||||
|
></button>
|
||||||
|
{/if}
|
||||||
|
<div class="row-start-1 row-span-6 col-start-1 flex flex-col justify-end items-center">
|
||||||
{#if showOverlay}
|
{#if showOverlay}
|
||||||
<div class="h-fit flex flex-col items-center gap-2 rounded-t-md pointer-events-auto p-2"
|
<div class="h-fit flex flex-col items-center gap-2 rounded-t-md pointer-events-auto p-2 z-20"
|
||||||
data-theme="light"
|
data-theme="light"
|
||||||
transition:slide>
|
transition:slide>
|
||||||
<!--suppress ALL -->
|
<!--suppress ALL -->
|
||||||
|
|||||||
@@ -1,3 +1,49 @@
|
|||||||
<div class="bg-black pointer-events-auto">
|
<script lang="ts">
|
||||||
STATUS CARD
|
import { onMount } from "svelte";
|
||||||
</div>
|
import { invalidate } from "$app/navigation";
|
||||||
|
import BatteryIcon from "$lib/icons/BatteryIcon.svelte";
|
||||||
|
|
||||||
|
let { data } = $props();
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
invalidate('app:chargecontroller');
|
||||||
|
}, 5000);
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const energykWh: string = $derived.by(() => {
|
||||||
|
const wh = data.chargecontroller.active_charge?.energy_wh ?? 0;
|
||||||
|
return (wh / 1000).toFixed(2);
|
||||||
|
});
|
||||||
|
const powerkWh: string = $derived.by(() => {
|
||||||
|
const wh = data.chargecontroller.active_charge?.last_power?.power ?? 0;
|
||||||
|
return (wh / 1000).toFixed(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Add Modal from bottom to confirm
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="pointer-events-auto bg-green-700/80 h-full w-full flex flex-col justify-center items-center gap-12">
|
||||||
|
<div>
|
||||||
|
<BatteryIcon/>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<p>Energia erogata a {data.user.username}:</p>
|
||||||
|
<p class="text-4xl"><span class="text-5xl">{energykWh}</span> kWh</p>
|
||||||
|
<p class="text-sm">Potenza misurata: {powerkWh} kW</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-[90%]">
|
||||||
|
<button class="btn btn-warning btn-xl w-full">Interrompere la ricarica</button>
|
||||||
|
</div>
|
||||||
|
<div class="text-center w-[70%] flex flex-col gap-5 text-sm">
|
||||||
|
<p>
|
||||||
|
«Ciò che sta accadendo ci pone di fronte all’urgenza di procedere in una coraggiosa rivoluzione culturale.»
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Papa Francesco, Enciclica Laudato Si’
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user