Notifications + Plausibilitätsprüfung

This commit is contained in:
Moritz Utcke
2023-05-12 23:40:31 +04:00
parent 33c8a1d447
commit 5559f5ca4d
37 changed files with 855 additions and 357 deletions

View File

@@ -2,9 +2,16 @@
import { Verbrauchsausweis } from "src/lib/Ausweis/Verbrauchsausweis"; import { Verbrauchsausweis } from "src/lib/Ausweis/Verbrauchsausweis";
import { Gebaeude } from "src/lib/Gebaeude"; import { Gebaeude } from "src/lib/Gebaeude";
import HelpLabel from "~/components/HelpLabel.svelte"; import HelpLabel from "~/components/HelpLabel.svelte";
import { auditHeizungGebaeudeBaujahr } from "../Verbrauchsausweis/audits/HeizungGebaeudeBaujahr";
import { addNotification, deleteNotification } from "../Notifications/shared";
import TagInput from "../TagInput.svelte";
import { writable } from "svelte/store";
export let gebaeude: Gebaeude; export let gebaeude: Gebaeude;
// TODO: Das ist scheise
let tags = writable([]);
$: ausweis = gebaeude.ausweis || new Verbrauchsausweis(); $: ausweis = gebaeude.ausweis || new Verbrauchsausweis();
</script> </script>
@@ -71,11 +78,27 @@
/>z.B. 1994-2001. />z.B. 1994-2001.
</HelpLabel> </HelpLabel>
<div> <div>
<input <TagInput
name="IGheizung" name="IGheizung"
type="number" type="number"
onlyUnique={true}
onFocusIn={() => {
addNotification({
message: "Info",
subtext: "Wussten sie, dass sie mehrere Jahre angeben können in denen z.B. Renovierungen an ihrer Heizung durchgeführt wurden. Drücken sie dafür einfach <kbd>Enter</kbd> oder <kbd>Space</kbd> nach jedem Jahr.",
dismissable: true,
uid: "HEIZUNG_BAUJAHR",
timeout: 0,
type: "info"
});
}}
onFocusOut={() => {
deleteNotification("HEIZUNG_BAUJAHR")
}}
className="{auditHeizungGebaeudeBaujahr(gebaeude) ? "linked" : ""}"
required required
bind:value={ausweis.baujahr_anlage} autocomplete="off"
bind:tags
/> />
</div> </div>
</div> </div>
@@ -92,12 +115,27 @@
-saniert- angeben. -saniert- angeben.
</HelpLabel> </HelpLabel>
<div> <div>
<input <TagInput
name="IGbaujahr" name="IGbaujahr"
type="number" type="number"
onlyUnique={true}
onFocusIn={() => {
addNotification({
message: "Info",
subtext: "Wussten sie, dass sie mehrere Jahre angeben können in denen z.B. Renovierungen an ihrem Gebäude durchgeführt wurden. Drücken sie dafür einfach <kbd>Enter</kbd> oder <kbd>Space</kbd> nach jedem Jahr.",
dismissable: true,
uid: "GEBAEUDE_BAUJAHR",
timeout: 0,
type: "info"
});
}}
onFocusOut={() => {
deleteNotification("GEBAEUDE_BAUJAHR")
}}
className="{auditHeizungGebaeudeBaujahr(gebaeude) ? "linked" : ""}"
required required
autocomplete="off" autocomplete="off"
bind:value={gebaeude.baujahr} bind:tags
/> />
</div> </div>
</div> </div>

View File

@@ -4,11 +4,11 @@
import Label from "../Label.svelte"; import Label from "../Label.svelte";
import fuelList from "./fuelList"; import fuelList from "./fuelList";
import { Verbrauchsausweis } from "src/lib/Ausweis/Verbrauchsausweis"; import { Verbrauchsausweis } from "src/lib/Ausweis/Verbrauchsausweis";
import { VerbrauchsausweisGewerbe } from "src/lib/Ausweis/VerbrauchsausweisGewerbe"; import { Gebaeude } from "src/lib/Gebaeude";
import { Bedarfsausweis } from "src/lib/Ausweis/Bedarfsausweis"; import { auditVerbrauchAbweichung } from "../Verbrauchsausweis/audits/VerbrauchAbweichung";
let availableYears = [ let availableYears = [
2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2018, 2019,
]; ];
let availableMonths = [ let availableMonths = [
"Januar", "Januar",
@@ -25,7 +25,7 @@
"Dezember", "Dezember",
]; ];
export let ausweis: Verbrauchsausweis |VerbrauchsausweisGewerbe | Bedarfsausweis; export let gebaeude: Gebaeude;
const fuelMap: Record<string, string[]> = {}; const fuelMap: Record<string, string[]> = {};
for (const fuel of fuelList) { for (const fuel of fuelList) {
@@ -37,11 +37,15 @@
let month: string = "01"; let month: string = "01";
let year: string = "2018"; let year: string = "2018";
$: ausweis = gebaeude.ausweis || new Verbrauchsausweis();
$: { $: {
if (month && year) { if (month && year) {
ausweis.energieverbrauch_zeitraum = moment(`${month}.01.${year}`); ausweis.kennwerte.zeitraum = moment(`${month}.01.${year}`);
} }
} }
$: abweichung = auditVerbrauchAbweichung(gebaeude);
</script> </script>
<div class="w-full flex flex-col gap-4"> <div class="w-full flex flex-col gap-4">
@@ -56,7 +60,7 @@
<input <input
type="checkbox" type="checkbox"
class="IGzus1verbrauch1" class="IGzus1verbrauch1"
bind:checked={ausweis.zusaetzliche_heizquelle} bind:checked={ausweis.kennwerte.zusaetzliche_heizquelle}
/> />
</div> </div>
@@ -88,7 +92,7 @@
<b>Koks:</b> Stark kohlenstoffhaltiger Brennstoff.<br /><br /> <b>Koks:</b> Stark kohlenstoffhaltiger Brennstoff.<br /><br />
</HelpLabel> </HelpLabel>
<div> <div>
<select name="energietraeger_1" required bind:value={ausweis.energietraeger_1}> <select name="energietraeger_1" required bind:value={ausweis.kennwerte.energietraeger_1}>
<option>Bitte auswählen</option> <option>Bitte auswählen</option>
{#each Object.keys(fuelMap) as fuel} {#each Object.keys(fuelMap) as fuel}
<option value={fuel}>{fuel}</option> <option value={fuel}>{fuel}</option>
@@ -107,10 +111,10 @@
<select <select
name="energietraeger_einheit_heizquelle_1" name="energietraeger_einheit_heizquelle_1"
required required
bind:value={ausweis.energietraeger_einheit_heizquelle_1} bind:value={ausweis.kennwerte.einheit_1}
> >
<option>Bitte auswählen</option> <option>Bitte auswählen</option>
{#each (fuelMap.hasOwnProperty(ausweis.energietraeger_1) ? fuelMap[ausweis.energietraeger_1] : []) as unit} {#each (fuelMap.hasOwnProperty(ausweis.kennwerte.energietraeger_1) ? fuelMap[ausweis.kennwerte.energietraeger_1] : []) as unit}
<option value={unit}>{unit}</option> <option value={unit}>{unit}</option>
{/each} {/each}
</select> </select>
@@ -122,8 +126,8 @@
<div> <div>
<select <select
name="energietraeger_2" name="energietraeger_2"
bind:value={ausweis.energietraeger_2} bind:value={ausweis.kennwerte.energietraeger_2}
disabled={!ausweis.zusaetzliche_heizquelle} disabled={!ausweis.kennwerte.zusaetzliche_heizquelle}
required required
> >
<option> Bitte auswählen</option> <option> Bitte auswählen</option>
@@ -139,12 +143,12 @@
<div> <div>
<select <select
name="energietraeger_einheit_heizquelle_2" name="energietraeger_einheit_heizquelle_2"
disabled={!ausweis.zusaetzliche_heizquelle} disabled={!ausweis.kennwerte.zusaetzliche_heizquelle}
bind:value={ausweis.energietraeger_einheit_heizquelle_2} bind:value={ausweis.kennwerte.einheit_2}
required required
> >
<option>Bitte auswählen</option> <option>Bitte auswählen</option>
{#each (fuelMap.hasOwnProperty(ausweis.energietraeger_2) ? fuelMap[ausweis.energietraeger_2] : []) as unit} {#each (fuelMap.hasOwnProperty(ausweis.kennwerte.energietraeger_2) ? fuelMap[ausweis.kennwerte.energietraeger_2] : []) as unit}
<option value={unit}>{unit}</option> <option value={unit}>{unit}</option>
{/each} {/each}
</select> </select>
@@ -184,7 +188,7 @@
<span>von</span> <span>von</span>
<input <input
class="klima" class="klima"
value={moment(ausweis.energieverbrauch_zeitraum) value={moment(ausweis.kennwerte.zeitraum)
.add("1", "year") .add("1", "year")
.format("MM.Y")} .format("MM.Y")}
readonly readonly
@@ -194,7 +198,7 @@
<span>von</span> <span>von</span>
<input <input
class="klima" class="klima"
value={moment(ausweis.energieverbrauch_zeitraum) value={moment(ausweis.kennwerte.zeitraum)
.add("2", "years") .add("2", "years")
.format("MM.Y")} .format("MM.Y")}
readonly readonly
@@ -206,7 +210,7 @@
<span>bis</span> <span>bis</span>
<input <input
class="" class=""
value={moment(ausweis.energieverbrauch_zeitraum) value={moment(ausweis.kennwerte.zeitraum)
.add("1", "year") .add("1", "year")
.format("MM.Y")} .format("MM.Y")}
readonly readonly
@@ -216,7 +220,7 @@
<span>bis</span> <span>bis</span>
<input <input
class="" class=""
value={moment(ausweis.energieverbrauch_zeitraum) value={moment(ausweis.kennwerte.zeitraum)
.add("2", "years") .add("2", "years")
.format("MM.Y")} .format("MM.Y")}
readonly readonly
@@ -226,7 +230,7 @@
<span>bis</span> <span>bis</span>
<input <input
class="" class=""
value={moment(ausweis.energieverbrauch_zeitraum) value={moment(ausweis.kennwerte.zeitraum)
.add("3", "years") .add("3", "years")
.format("MM.Y")} .format("MM.Y")}
readonly readonly
@@ -239,7 +243,8 @@
<input <input
name="energieverbrauch_1_heizquelle_1" name="energieverbrauch_1_heizquelle_1"
type="number" type="number"
bind:value={ausweis.energieverbrauch_1_heizquelle_1} class:linked={abweichung.indexOf(1) > -1}
bind:value={ausweis.kennwerte.verbrauch_1}
required required
/> />
</div> </div>
@@ -248,7 +253,8 @@
<input <input
name="energieverbrauch_2_heizquelle_1" name="energieverbrauch_2_heizquelle_1"
type="number" type="number"
bind:value={ausweis.energieverbrauch_2_heizquelle_1} class:linked={abweichung.indexOf(2) > -1}
bind:value={ausweis.kennwerte.verbrauch_2}
required required
/> />
</div> </div>
@@ -257,7 +263,8 @@
<input <input
name="energieverbrauch_3_heizquelle_1" name="energieverbrauch_3_heizquelle_1"
type="number" type="number"
bind:value={ausweis.energieverbrauch_3_heizquelle_1} class:linked={abweichung.indexOf(3) > -1}
bind:value={ausweis.kennwerte.verbrauch_3}
required required
/> />
</div> </div>
@@ -268,8 +275,9 @@
<input <input
name="energieverbrauch_1_heizquelle_2" name="energieverbrauch_1_heizquelle_2"
type="number" type="number"
bind:value={ausweis.energieverbrauch_1_heizquelle_2} bind:value={ausweis.kennwerte.verbrauch_4}
disabled={!ausweis.zusaetzliche_heizquelle} class:linked={abweichung.indexOf(4) > -1}
disabled={!ausweis.kennwerte.zusaetzliche_heizquelle}
/> />
</div> </div>
<div class="column"> <div class="column">
@@ -277,8 +285,9 @@
<input <input
name="energieverbrauch_2_heizquelle_2" name="energieverbrauch_2_heizquelle_2"
type="number" type="number"
bind:value={ausweis.energieverbrauch_2_heizquelle_2} bind:value={ausweis.kennwerte.verbrauch_5}
disabled={!ausweis.zusaetzliche_heizquelle} class:linked={abweichung.indexOf(5) > -1}
disabled={!ausweis.kennwerte.zusaetzliche_heizquelle}
/> />
</div> </div>
<div class="column"> <div class="column">
@@ -286,8 +295,9 @@
<input <input
name="energieverbrauch_3_heizquelle_2" name="energieverbrauch_3_heizquelle_2"
type="number" type="number"
bind:value={ausweis.energieverbrauch_3_heizquelle_2} bind:value={ausweis.kennwerte.verbrauch_6}
disabled={!ausweis.zusaetzliche_heizquelle} class:linked={abweichung.indexOf(6) > -1}
disabled={!ausweis.kennwerte.zusaetzliche_heizquelle}
/> />
</div> </div>
</div> </div>

View File

@@ -18,8 +18,8 @@ const loggedIn = isLoggedIn(Astro);
alt="IBCornelsen - Logo" alt="IBCornelsen - Logo"
onclick="window.location.href = '/'" onclick="window.location.href = '/'"
/> />
<h2 class="MajorHeading">Energieausweis online erstellen</h2> <h2 class="text-secondary font-semibold text-2xl absolute top-8 right-0">Energieausweis online erstellen</h2>
<h2 class="MajorHeading smaller">Energieausweise nach aktueller GEG</h2> <h2 class="text-primary font-semibold text-xl absolute top-16 right-0">Energieausweise nach aktueller GEG</h2>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import cookie from "cookiejs"; import cookie from "cookiejs";
import { addNotification } from "./Notifications/shared";
let email: string; let email: string;
let password: string; let password: string;
let hasError: boolean;
async function login() { async function login() {
const response = await fetch("/api/login", { const response = await fetch("/api/login", {
@@ -23,8 +23,14 @@
localStorage.setItem("expires", json.data.expires); localStorage.setItem("expires", json.data.expires);
window.location.href = "/user"; window.location.href = "/user";
} else { } else {
hasError = true;
setTimeout(() => (hasError = false), 3000); addNotification({
message: "Ups...",
subtext: "Das hat leider nicht geklappt, haben sie ihr Passwort und ihre Email Adresse richtig eingegeben?",
type: "error",
timeout: 6000,
dismissable: true
})
} }
} }
</script> </script>
@@ -52,13 +58,7 @@
required required
/> />
</div> </div>
<button on:click={login}>Einloggen</button> <button class="button" on:click={login}>Einloggen</button>
{#if hasError}
<p>
Das hat leider nicht geklappt, haben sie ihr Passwort und den
Nutzernamen richtig eingegeben?
</p>
{/if}
<div class="flex-row justify-between" style="margin-top: 10px"> <div class="flex-row justify-between" style="margin-top: 10px">
<a href="/signup">Registrieren</a> <a href="/signup">Registrieren</a>
<a href="/user/passwort_vergessen">Passwort Vergessen?</a> <a href="/user/passwort_vergessen">Passwort Vergessen?</a>

View File

@@ -0,0 +1,10 @@
<script lang="ts">
import RawNotification from "./RawNotification.svelte";
import { Notification } from "./shared";
export let notification: Notification & { uid: string };
</script>
<RawNotification {notification}>
<p class="text-gray-600 text-lg">{@html notification.subtext}</p>
</RawNotification>

View File

@@ -0,0 +1,12 @@
<script lang="ts">
import Notification from "./Notification.svelte";
import RawNotificationWrapper from "./RawNotificationWrapper.svelte";
import { notifications } from "./shared";
</script>
<RawNotificationWrapper>
{#each Object.entries($notifications) as [uid, notification] (uid)}
<Notification notification={{...notification, uid }}></Notification>
{/each}
</RawNotificationWrapper>

View File

@@ -0,0 +1,44 @@
<script lang="ts">
import { Notification, deleteNotification } from "./shared";
import { fly } from "svelte/transition";
export let notification: Partial<Notification> & { uid: string };
</script>
<div
class="border rounded-lg bg-white shadow-md flex flex-row border-l-8" in:fly={{x: 200, duration: 200}} out:fly={{x: 200, duration: 200}}
class:border-l-red-400={notification.type == "error"}
class:border-l-blue-400={notification.type == "info"}
class:border-l-green-400={notification.type == "success"}
class:border-l-yellow-400={notification.type == "warning"}
>
<div class="flex flex-col px-4 py-2">
<h2 class="text-xl font-semibold">{@html notification.message}</h2>
<p class="text-gray-600 text-lg"><slot></slot></p>
</div>
{#if notification.dismissable}
<button
class="border-l px-4 py-2 hover:bg-gray-100"
on:click={() => {
deleteNotification(notification.uid);
if (notification.onUserDismiss) {
notification.onUserDismiss()
}
}}>X</button
>
{/if}
</div>
<style>
:global(a) {
@apply text-blue-700;
}
:global(a:hover) {
@apply underline;
}
:global(kbd) {
@apply rounded-lg shadow-md border bg-gray-50 px-1.5 py-1 text-sm;
}
</style>

View File

@@ -0,0 +1,3 @@
<div class="fixed right-8 bottom-8 max-w-[400px] flex flex-col gap-4 z-50">
<slot></slot>
</div>

View File

@@ -0,0 +1,80 @@
import { Writable, writable } from "svelte/store";
import { v4 as uuid } from "uuid";
export const notifications: Writable<Record<string, Notification>> = writable({});
const defaults = {
message: "",
dismissable: false,
timeout: 4000,
subtext: "",
type: "error",
onUserDismiss: () => {}
};
export interface Notification {
message: string;
dismissable: boolean;
timeout: number;
subtext: string;
type: "error" | "success" | "info" | "warning";
onUserDismiss: () => any;
uid?: string;
}
export function updateNotification(uid: string, updater: Partial<Notification>) {
notifications.update((value) => {
value[uid] = { ...defaults, ...value[uid], ...updater } as Notification;
return value;
})
}
export function addNotification(notification: Partial<Notification>): string {
let uid = uuid();
if (notification.uid) {
uid = notification.uid;
}
const object: Notification = { ...defaults, ...notification } as Notification;
notifications.update((value) => {
value[uid] = object;
return value;
})
if (object.timeout) {
setTimeout(() => {
deleteNotification(uid);
}, object.timeout);
}
return uid;
}
export function deleteNotification(uid: string) {
notifications.update((value) => {
delete value[uid];
return value;
})
}
export function showLinkedElement(query: string) {
const element = document.querySelector(query);
if (!element) {
return;
}
element.classList.add("linked");
}
export function hideLinkedElement(query: string) {
const element = document.querySelector(query);
if (!element) {
return;
}
element.classList.remove("linked");
}

View File

@@ -1,13 +1,14 @@
<script lang="ts"> <script lang="ts">
let password: string; import { addNotification } from "./Notifications/shared";
let passwort: string;
let email: string; let email: string;
let hasError: boolean;
async function login() { async function login() {
const response = await fetch("/api/user", { const response = await fetch("/api/user", {
method: "PUT", method: "PUT",
body: JSON.stringify({ body: JSON.stringify({
password, email passwort, email
}) })
}) })
@@ -16,19 +17,22 @@
if (json.success == true) { if (json.success == true) {
window.location.href = "/login"; window.location.href = "/login";
} else { } else {
hasError = true; addNotification({
message: "Ups...",
subtext: "Da ist wohl etwas schiefgelaufen... Diese Email Adresse ist bereits in Benutzung, haben sie vielleicht bereits ein Konto bei uns?",
type: "error",
timeout: 0,
dismissable: true
})
} }
} }
</script> </script>
<div style="width:50%;margin: 0 auto"> <div style="width:50%;margin: 0 auto">
<h1>Registrieren:</h1> <h1>Registrieren:</h1>
<div class="login_page"> <div class="flex flex-col gap-4">
{#if hasError} <div>
<p>Leider ist diese Email bereits vergeben.</p> <h4>Email</h4>
{/if}
<div class="block_4" style="margin-top: 25px;">
<h4 class="heading_3">Email</h4>
<input <input
type="text" type="text"
placeholder="Email" placeholder="Email"
@@ -37,26 +41,23 @@
required required
/> />
</div> </div>
<div class="block_4"> <div>
<h4 class="heading_3">Passwort</h4> <h4>Passwort</h4>
<input <input
type="password" type="password"
placeholder="********" placeholder="********"
class="formInput" class="formInput"
bind:value={password} bind:value={passwort}
required required
/> />
</div> </div>
<div class="mt-2 flex flex-row justify-between"> <button class="button" on:click={login}
<button on:click={login}
>Registrieren</button >Registrieren</button
> >
<a class="button" <div class="flex-row justify-between" style="margin-top: 10px">
<a
href="/login">Einloggen</a href="/login">Einloggen</a
> >
</div>
<div class="flex-row justify-between" style="margin-top: 10px">
<a href="/">Home</a>
<a href="/user/passwort_vergessen">Passwort Vergessen?</a> <a href="/user/passwort_vergessen">Passwort Vergessen?</a>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,4 @@
<div class="left-sidebar"> <div class="flex flex-col gap-6">
<nav> <nav>
<div class="nav-card"> <div class="nav-card">
<div class="card-menu-option dropdown"> <div class="card-menu-option dropdown">
@@ -159,13 +159,25 @@
</div> </div>
<style> <style>
.nav-card {
@apply rounded-lg w-full flex flex-col shadow-md border;
}
.infoCard { .infoCard {
@apply bg-white rounded-none; @apply bg-white rounded-lg border p-4 shadow-md;
} }
.dropdown-content > .card-menu-option, .dropdown-content > .card-menu-option,
.dropdown-content > a { .dropdown-content > a {
@apply block w-[350px] bg-gray-100 border text-lg px-4 py-2; @apply block w-[350px] bg-gray-50 border text-lg px-4 py-2;
}
.dropdown-content > a:first-child {
@apply rounded-tr-lg rounded-tl-lg;
}
.dropdown-content > a:last-child {
@apply rounded-br-lg rounded-bl-lg;
} }
.dropdown-content > .card-menu-option:hover, .dropdown-content > .card-menu-option:hover,
@@ -175,7 +187,7 @@
} }
.dropdown-content { .dropdown-content {
@apply absolute bg-white left-full top-[-1px] shadow-md z-10 hidden; @apply absolute bg-white left-full top-[-1px] shadow-md z-10 hidden rounded-lg;
} }
.dropdown:hover > .dropdown-content { .dropdown:hover > .dropdown-content {
@@ -231,15 +243,11 @@
} }
.nav-card .card-menu-option:first-child { .nav-card .card-menu-option:first-child {
border-top: 0px solid #fff; @apply rounded-tr-lg rounded-tl-lg;
border-top-left-radius: 0px;
border-top-right-radius: 0px;
} }
.nav-card .card-menu-option:last-child { .nav-card .card-menu-option:last-child {
border-bottom: 0px solid #fff; @apply rounded-br-lg rounded-bl-lg;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
} }
.nav-card .card-menu-option a { .nav-card .card-menu-option a {

View File

@@ -1,4 +1,4 @@
<div class="right-sidebar"> <div class="flex flex-col gap-4">
<div class="infoCard"> <div class="infoCard">
<h2 style="font-weight: bold; font-size: 1.2em; color: #3A4AB5;"> <h2 style="font-weight: bold; font-size: 1.2em; color: #3A4AB5;">
Rufen Sie uns an<br /> Wir sind gerne für Sie da Rufen Sie uns an<br /> Wir sind gerne für Sie da
@@ -128,6 +128,6 @@
<style> <style>
.infoCard { .infoCard {
@apply bg-white rounded-none; @apply bg-white rounded-lg border p-4 shadow-md;
} }
</style> </style>

View File

@@ -0,0 +1,89 @@
<script lang="ts">
import { Writable, writable } from "svelte/store";
let tag = "";
export let tags: Writable<string[]> = writable([]);
export let addKeys: number[] = [13];
export let maxTags: number;
export let onlyUnique: boolean = false;
export let removeKeys: number[] = [8];
export let placeholder: string = "";
export let allowPaste: boolean = true;
export let allowDrop: boolean = false;
export let splitWith: string = ",";
export let name: string = "";
export let id: string = "";
export let allowBlur: boolean = true;
export let disable: boolean = false;
export let minChars: number = 0;
export let labelText;
export let labelShow;
export let readonly: boolean = false;
export let onTagClick: Function;
export let onFocusIn: () => any;
export let onFocusOut: () => any;
export let className: string;
function addTag(tag: string) {
if (onlyUnique && $tags.indexOf(tag) > -1) {
tag = "";
return;
}
tags.update(value => {
return [...value, tag];
})
}
function removeTag(i: number) {
tags.update(value => {
value.splice(i, 1)
return value;
})
}
function onKeydown(e: KeyboardEvent) {
if (addKeys.indexOf(e.keyCode) > -1) {
e.preventDefault();
addTag(tag);
tag = "";
} else if (removeKeys.indexOf(e.keyCode) > -1 && tag.length == 0) {
e.preventDefault();
removeTag($tags.length - 1);
tag = ""
}
}
</script>
<div
class="flex flex-row gap-1 input"
style="padding: 0 !important;"
>
{#if $tags.length > 0}
{#each $tags as tag, i}
<button class="rounded-lg bg-white px-1.5 border flex flex-row items-center justify-between gap-2" on:click={onTagClick(tag)}>
{tag}
{#if !disable && !readonly}
<span
class="svelte-tags-input-tag-remove"
on:pointerdown={() => removeTag(i)}
>
&#215;</span
>
{/if}
</button>
{/each}
{/if}
<input
type="text"
bind:value={tag}
on:keydown={onKeydown}
on:focusin={onFocusIn}
on:focusout={onFocusOut}
class="border-none h-full w-full {className}"
disabled={disable || readonly}
autocomplete="off"
{...$$restProps}
/>
</div>

View File

@@ -11,44 +11,57 @@
import moment from "moment"; import moment from "moment";
import BilderZusatzsysteme from "../Ausweis/BilderZusatzsysteme.svelte"; import BilderZusatzsysteme from "../Ausweis/BilderZusatzsysteme.svelte";
import { Gebaeude } from "src/lib/Gebaeude"; import { Gebaeude } from "src/lib/Gebaeude";
import { hideLinkedElement, notifications } from "../Notifications/shared";
import { gebaeude } from "./shared";
import RawNotificationWrapper from "../Notifications/RawNotificationWrapper.svelte";
import RawNotification from "../Notifications/RawNotification.svelte";
import { auditHeizungGebaeudeBaujahr } from "./audits/HeizungGebaeudeBaujahr";
import { AuditType, hidden } from "./audits/hidden";
import { auditBedarfsausweisBenoetigt } from "./audits/BedarfsausweisBenoetigt";
import { auditVerbrauchAbweichung } from "./audits/VerbrauchAbweichung";
export let gebaeude: Gebaeude; export let uid: string = "";
gebaeude.set(new Gebaeude());
$gebaeude.ausweis = new Verbrauchsausweis();
$gebaeude.ausweis.gebaeude = $gebaeude;
if (uid) {
(async () => {
const result = await fetch(`/api/verbrauchsausweis?uid=${uid}`, {
method: "GET"
});
$: ausweis = gebaeude.ausweis || new Verbrauchsausweis(); const json = await result.json();
let needsRequirementCertificate: boolean = false; if (json.success) {
gebaeude.set(new Gebaeude(json.data.gebaeude));
$gebaeude.ausweis = new Verbrauchsausweis(json.data.ausweis);
$gebaeude.ausweis.gebaeude = $gebaeude;
}
})
}
$: needsRequirementCertificate = $: ausweis = $gebaeude.ausweis || new Verbrauchsausweis();
(gebaeude.baujahr < 1978 &&
gebaeude.einheiten <= 4 &&
gebaeude.saniert == false &&
(ausweis.ausstellgrund == "Vermietung" ||
ausweis.ausstellgrund == "Sonstiges")) ||
ausweis.ausstellgrund == "Neubau" ||
ausweis.ausstellgrund == "Modernisierung" ||
ausweis.ausstellgrund == "Verkauf";
function automatischAusfüllen() { function automatischAusfüllen() {
gebaeude.baujahr = 1962; $gebaeude.baujahr = 1962;
ausweis.baujahr_anlage = 1974; ausweis.baujahr_anlage = 1952;
gebaeude.saniert = true; $gebaeude.saniert = true;
gebaeude.einheiten = 1; $gebaeude.einheiten = 1;
ausweis.ausstellgrund = "Vermietung"; ausweis.ausstellgrund = "Vermietung";
ausweis.kennwerte.verbrauch_1 = 15000; ausweis.kennwerte.verbrauch_1 = 15000;
ausweis.kennwerte.verbrauch_2 = 14000; ausweis.kennwerte.verbrauch_2 = 14000;
ausweis.kennwerte.verbrauch_3 = 16000; ausweis.kennwerte.verbrauch_3 = 16000;
gebaeude.wohnflaeche = 152; $gebaeude.wohnflaeche = 152;
gebaeude.keller_beheizt = true; $gebaeude.keller_beheizt = true;
ausweis.kennwerte.energietraeger_1 = "Erdgas H"; ausweis.kennwerte.energietraeger_1 = "Erdgas H";
ausweis.kennwerte.einheit_1 = "kWh"; ausweis.kennwerte.einheit_1 = "kWh";
ausweis.kennwerte.anteil_warmwasser_1 = 18; ausweis.kennwerte.anteil_warmwasser_1 = 18;
ausweis.kennwerte.zeitraum = moment("12.01.2019"); ausweis.kennwerte.zeitraum = moment("12.01.2019");
gebaeude.plz = "21039"; $gebaeude.plz = "21039";
gebaeude.ort = "Hamburg"; $gebaeude.ort = "Hamburg";
gebaeude.strasse = "Curslacker Deich 170"; $gebaeude.strasse = "Curslacker Deich 170";
gebaeude.gebaeudeteil = "Gesamtgebäude"; $gebaeude.gebaeudeteil = "Gesamtgebäude";
//ausweis.upload();
} }
</script> </script>
@@ -81,29 +94,7 @@
<Label>A - Prüfung der Ausweisart</Label> <Label>A - Prüfung der Ausweisart</Label>
<Ausweisart bind:gebaeude /> <Ausweisart bind:gebaeude={$gebaeude} />
<div
class="flex flex-col p-4"
class:hidden={!needsRequirementCertificate}
>
<div class="form-group col-md-9">
<HelpLabel
title="Sie benötigen einen Bedarfsausweis. Bitte führen Sie hier Ihre Eingabe für den Bedarfsausweis fort und klicken auf den Button:"
>
Der Bedarfsausweis ist die etwas umfangreichere Berechnung.
Sie benötigen z.B. Länge, Breite und Geschoßhöhe des
Gebäudes. Auch müssen genauere Angaben zur Anlagentechnik
gemacht werden.
</HelpLabel>
</div>
<div class="form-group col-md-3">
<a class="button" href="/bedarfsausweis"
>Bedarfsausweis erstellen</a
>
</div>
</div>
<hr /> <hr />
@@ -128,7 +119,7 @@
required required
data-msg-minlength="min. 5 Zeichen" data-msg-minlength="min. 5 Zeichen"
data-msg-maxlength="max. 40 Zeichen" data-msg-maxlength="max. 40 Zeichen"
bind:value={gebaeude.strasse} bind:value={$gebaeude.strasse}
/> />
</div> </div>
</div> </div>
@@ -136,8 +127,8 @@
<!-- PLZ --> <!-- PLZ -->
<div class="form-group col-md-4 PLZ"> <div class="form-group col-md-4 PLZ">
<ZipSearch <ZipSearch
bind:zip={gebaeude.plz} bind:zip={$gebaeude.plz}
bind:city={gebaeude.ort} bind:city={$gebaeude.ort}
name="zip" name="zip"
/> />
</div> </div>
@@ -151,7 +142,7 @@
<input <input
name="IGort" name="IGort"
readonly={true} readonly={true}
bind:value={gebaeude.ort} bind:value={$gebaeude.ort}
type="text" type="text"
/> />
</div> </div>
@@ -173,7 +164,7 @@
autocomplete="off" autocomplete="off"
data-rule-minlength="2" data-rule-minlength="2"
data-msg-minlength="min. 2 Zeichen" data-msg-minlength="min. 2 Zeichen"
bind:value={gebaeude.wohnflaeche} bind:value={$gebaeude.wohnflaeche}
/> />
</div> </div>
</div> </div>
@@ -185,7 +176,7 @@
<select <select
name="IGkeller" name="IGkeller"
required required
bind:value={gebaeude.keller_beheizt} bind:value={$gebaeude.keller_beheizt}
> >
<option>Bitte auswählen</option> <option>Bitte auswählen</option>
<option value={false}>nicht vorhanden</option> <option value={false}>nicht vorhanden</option>
@@ -214,7 +205,7 @@
<Label>C - Eingabe von 3 zusammenhängenden Verbrauchsjahren</Label> <Label>C - Eingabe von 3 zusammenhängenden Verbrauchsjahren</Label>
<div class="GRB"> <div class="GRB">
<Verbrauch bind:ausweis /> <Verbrauch bind:gebaeude={$gebaeude} />
</div> </div>
<hr /> <hr />
@@ -287,7 +278,7 @@
><input ><input
type="checkbox" type="checkbox"
name="IGversorgungssysteme1" name="IGversorgungssysteme1"
bind:checked={gebaeude.energiequelle_2_nutzung[0]} bind:checked={$gebaeude.energiequelle_2_nutzung[0]}
value="Heizung" value="Heizung"
/>Heizung</label />Heizung</label
> >
@@ -295,7 +286,7 @@
><input ><input
type="checkbox" type="checkbox"
name="IGversorgungssysteme2" name="IGversorgungssysteme2"
bind:checked={gebaeude.energiequelle_2_nutzung[1]} bind:checked={$gebaeude.energiequelle_2_nutzung[1]}
value="Warmwasser" value="Warmwasser"
/>Warmwasser</label />Warmwasser</label
> >
@@ -303,7 +294,7 @@
><input ><input
type="checkbox" type="checkbox"
name="IGversorgungssysteme3" name="IGversorgungssysteme3"
bind:checked={gebaeude.energiequelle_2_nutzung[2]} bind:checked={$gebaeude.energiequelle_2_nutzung[2]}
value="Lüftung" value="Lüftung"
/>Lüftung</label />Lüftung</label
> >
@@ -311,7 +302,7 @@
><input ><input
type="checkbox" type="checkbox"
name="IGversorgungssysteme4" name="IGversorgungssysteme4"
bind:checked={gebaeude.energiequelle_2_nutzung[3]} bind:checked={$gebaeude.energiequelle_2_nutzung[3]}
value="Kühlung" value="Kühlung"
/>Kühlung</label />Kühlung</label
> >
@@ -450,7 +441,7 @@
>F - Bitte prüfen Sie hier die Angaben zum Sanierungszustand des >F - Bitte prüfen Sie hier die Angaben zum Sanierungszustand des
Gebäudes</Label Gebäudes</Label
> >
<BilderZusatzsysteme {gebaeude} /> <BilderZusatzsysteme gebaeude={$gebaeude} />
<hr /> <hr />
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<Hilfe /> <Hilfe />
@@ -458,3 +449,63 @@
</div> </div>
</fieldset> </fieldset>
</form> </form>
<RawNotificationWrapper>
{#each Object.entries($notifications) as [uid, notification] (uid)}
<RawNotification notification={{...notification, uid }}>
{@html notification.subtext}
</RawNotification>
{/each}
{#if auditBedarfsausweisBenoetigt($gebaeude)}
<RawNotification notification={{
message: "Bedarfsausweis benötigt!",
timeout: 0,
uid: "BEDARFSAUSWEIS",
dismissable: false,
type: "info"
}}>
Sie benötigen einen Bedarfsausweis. <a href='/bedarfsausweis'>Bitte führen Sie hier Ihre Eingabe für den Bedarfsausweis fort</a>.
</RawNotification>
{/if}
{#if auditHeizungGebaeudeBaujahr($gebaeude)}
<RawNotification notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "HEIZUNG_VOR_GEBAEUDE",
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.HEIZUNG_GEBAEUDE_BAUJAHR)
$gebaeude = $gebaeude;
},
type: "warning"
}}>
Sie haben angegeben, dass ihre Heizung vor ihrem Gebäude konstruiert wurde. Sind sie sich sicher, dass das stimmt?
</RawNotification>
{/if}
{#if auditVerbrauchAbweichung($gebaeude).length > 0}
<RawNotification notification={{
message: "Plausibilitätsprüfung",
timeout: 0,
uid: "VERBRAUCH_ABWEICHUNG",
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.VERBRAUCH_ABWEICHUNG)
$gebaeude = $gebaeude;
},
type: "warning"
}}>
Die Abweichung der Verbräuche zwischen Zeitraum {auditVerbrauchAbweichung($gebaeude)[0]} und {auditVerbrauchAbweichung($gebaeude)[1]} beträgt mehr als 25% und sie haben keinen Leerstand angegeben. Sind sie sich sicher, dass das stimmt?
</RawNotification>
{/if}
</RawNotificationWrapper>
<style>
:global(.linked) {
@apply border-2 border-red-400;
}
</style>

View File

@@ -0,0 +1,41 @@
export function lookupAudit(audit: keyof typeof audits): boolean {
if (!audits.hasOwnProperty(audit)) {
return false;
}
const valid = audits[audit].validator(hidden);
if (valid && !hidden.has(audit)) {
audits[audit].trueAction(hidden);
} else {
audits[audit].falseAction(hidden);
}
return valid;
}
type Action = (x: typeof hidden) => void;
export function addAudit(
name: string,
validator: (x: typeof hidden) => boolean,
trueAction: Action,
falseAction: Action
) {
audits[name] = {
validator,
trueAction,
falseAction,
};
}
export const audits: Record<
string,
{
validator: (x: typeof hidden) => boolean;
trueAction: Action;
falseAction: Action;
}
> = {};
export const hidden: Set<keyof typeof audits> = new Set();

View File

@@ -0,0 +1,17 @@
import { Gebaeude } from "src/lib/Gebaeude";
export function auditBedarfsausweisBenoetigt(gebaeude: Gebaeude): boolean {
let ausweis = gebaeude.ausweis;
if (!ausweis) {
return false;
}
return ((gebaeude.baujahr < 1978 &&
gebaeude.einheiten <= 4 &&
gebaeude.saniert == false &&
(ausweis.ausstellgrund == "Vermietung" ||
ausweis.ausstellgrund == "Sonstiges")) ||
ausweis.ausstellgrund == "Neubau" ||
ausweis.ausstellgrund == "Modernisierung" ||
ausweis.ausstellgrund == "Verkauf");
}

View File

@@ -0,0 +1,14 @@
import { Gebaeude } from "src/lib/Gebaeude";
import { AuditType, hidden } from "../audits/hidden";
export function auditHeizungGebaeudeBaujahr(gebaeude: Gebaeude): boolean {
let ausweis = gebaeude.ausweis;
if (!ausweis) {
return false;
}
return ausweis.baujahr_anlage > 1500 &&
gebaeude.baujahr > 1500 &&
ausweis.baujahr_anlage < gebaeude.baujahr
&& !hidden.has(AuditType.HEIZUNG_GEBAEUDE_BAUJAHR)
}

View File

@@ -0,0 +1,37 @@
import { Gebaeude } from "src/lib/Gebaeude";
import { AuditType, hidden } from "./hidden";
export function auditVerbrauchAbweichung(gebaeude: Gebaeude): number[] {
let ausweis = gebaeude.ausweis;
if (!ausweis || gebaeude.leerstand > 0) {
return [];
}
if (hidden.has(AuditType.VERBRAUCH_ABWEICHUNG)) {
return [];
}
if (getAbweichung(ausweis.kennwerte.verbrauch_1, ausweis.kennwerte.verbrauch_2) > 0.25) {
return [1, 2];
}
if (getAbweichung(ausweis.kennwerte.verbrauch_2, ausweis.kennwerte.verbrauch_3) > 0.25) {
return [2, 3];
}
if (getAbweichung(ausweis.kennwerte.verbrauch_4, ausweis.kennwerte.verbrauch_5) > 0.25) {
return [4, 5];
}
if (getAbweichung(ausweis.kennwerte.verbrauch_5, ausweis.kennwerte.verbrauch_6) > 0.25) {
return [5, 6];
}
return [];
}
function getAbweichung(x: number, y: number): number {
console.log(x, y, Math.abs((x - y) / ((x + y) / 2)));
return Math.abs((x - y) / ((x + y) / 2));
}

View File

@@ -0,0 +1,6 @@
export const hidden = new Set<AuditType>()
export enum AuditType {
HEIZUNG_GEBAEUDE_BAUJAHR,
VERBRAUCH_ABWEICHUNG
}

View File

@@ -0,0 +1,4 @@
import { Gebaeude } from "src/lib/Gebaeude";
import { Writable, writable } from "svelte/store";
export const gebaeude: Writable<Gebaeude> = writable();

View File

@@ -3,7 +3,7 @@ import "../style/global.scss"
import Footer from '../components/Footer.astro'; import Footer from '../components/Footer.astro';
import Header from '../components/Header.astro'; import Header from '../components/Header.astro';
import SidebarLeft from '../components/SidebarLeft.astro'; import SidebarLeft from '../components/SidebarLeft.astro';
import SidebarRight from '../components/SidebarRight.astro'; import NotificationWrapper from "~/components/Notifications/NotificationWrapper.svelte";
export interface Props { export interface Props {
title: string; title: string;
@@ -110,8 +110,8 @@ const schema = JSON.stringify({
@apply text-xl font-medium mt-6 mb-4; @apply text-xl font-medium mt-6 mb-4;
} }
input, select, textarea { input, select, textarea, .input {
@apply py-1.5 px-2.5 rounded-lg w-full outline-none text-base text-slate-800 border; @apply py-1.5 px-2.5 rounded-lg w-full outline-none text-base text-slate-800 border bg-white;
} }
input:disabled, input:read-only, select:disabled { input:disabled, input:read-only, select:disabled {

View File

@@ -4,6 +4,7 @@ import Footer from '../components/Footer.astro';
import Header from '../components/Header.astro'; import Header from '../components/Header.astro';
import SidebarLeft from '../components/SidebarLeft.astro'; import SidebarLeft from '../components/SidebarLeft.astro';
import SidebarRight from '../components/SidebarRight.astro'; import SidebarRight from '../components/SidebarRight.astro';
import NotificationWrapper from "~/components/Notifications/NotificationWrapper.svelte";
export interface Props { export interface Props {
title: string; title: string;
@@ -94,6 +95,7 @@ const schema = JSON.stringify({
<SidebarRight></SidebarRight> <SidebarRight></SidebarRight>
</main> </main>
<Footer /> <Footer />
<NotificationWrapper client:load></NotificationWrapper>
</body> </body>
</html> </html>
@@ -102,7 +104,7 @@ const schema = JSON.stringify({
min-height: 100vh; min-height: 100vh;
} }
button, .button { .button {
@apply px-8 py-2 bg-secondary rounded-lg text-white font-medium hover:shadow-lg transition-all hover:underline active:bg-blue-900 text-center cursor-pointer; @apply px-8 py-2 bg-secondary rounded-lg text-white font-medium hover:shadow-lg transition-all hover:underline active:bg-blue-900 text-center cursor-pointer;
color: #fff !important; color: #fff !important;
} }
@@ -112,7 +114,11 @@ const schema = JSON.stringify({
} }
input { input {
@apply py-1 px-2 w-full rounded-lg outline-none text-lg text-slate-800 border; @apply py-1.5 px-4 w-full rounded-lg outline-none text-lg text-slate-700 border bg-gray-50 transition-colors;
}
input:hover, input:focus {
@apply bg-gray-100;
} }
label { label {

View File

@@ -10,7 +10,7 @@ export class Verbrauchsausweis {
public warmwasser_enthalten: boolean = true; public warmwasser_enthalten: boolean = true;
public id?: number; public id?: number;
public uid?: string; public uid?: string;
public baujahr_anlage: number = 0; public baujahr_anlage: number[] = [];
public get energetische_nutzfläche(): number { public get energetische_nutzfläche(): number {
return ( return (
@@ -24,7 +24,12 @@ export class Verbrauchsausweis {
public kennwerte: Energiekennwerte = new Energiekennwerte(); public kennwerte: Energiekennwerte = new Energiekennwerte();
public gebaeude: Gebaeude = new Gebaeude(); public gebaeude: Gebaeude = new Gebaeude();
public constructor() {} public constructor(initializer?: Verbrauchsausweis) {
if (initializer) {
this.ausweisart = initializer.ausweisart;
this.kennwerte = initializer.kennwerte;
}
}
public static fromBase64(base64: string): Verbrauchsausweis | null { public static fromBase64(base64: string): Verbrauchsausweis | null {
try { try {

View File

@@ -1,5 +1,6 @@
import { Ausweis } from "./Ausweis/Ausweis"; import { Ausweis } from "./Ausweis/Ausweis";
import { Verbrauchsausweis } from "./Ausweis/Verbrauchsausweis";
import { Dachgeschoss, Lueftungskonzept } from "./Ausweis/types"; import { Dachgeschoss, Lueftungskonzept } from "./Ausweis/types";
import { BitChecker } from "./BitChecker"; import { BitChecker } from "./BitChecker";
@@ -23,7 +24,7 @@ export class Gebaeude {
public energiequelle_2_nutzung: boolean[] = BitChecker(0); public energiequelle_2_nutzung: boolean[] = BitChecker(0);
public daemmung: boolean[] = BitChecker(0); public daemmung: boolean[] = BitChecker(0);
public ausweis?: Ausweis; public ausweis: Ausweis;
public uid?: string; public uid?: string;

View File

@@ -15,7 +15,7 @@ export class User {
return null; return null;
} }
const user = await db<UserType>("users").select("*").where("uid", uid).first(); const user = await db<UserType>("benutzer").select("*").where("uid", uid).first();
if (!user) { if (!user) {
return null; return null;
@@ -29,7 +29,7 @@ export class User {
return null; return null;
} }
const user = await db<UserType>("users").select("*").where("email", email).first(); const user = await db<UserType>("benutzer").select("*").where("email", email).first();
if (!user) { if (!user) {
return null; return null;
@@ -48,7 +48,7 @@ export class User {
return null; return null;
} }
const user = await db<UserType>("users").select("*").where("id", id).first(); const user = await db<UserType>("benutzer").select("*").where("id", id).first();
if (!user) { if (!user) {
return null; return null;
@@ -63,11 +63,11 @@ export class User {
} }
const uid = uuid(); const uid = uuid();
const hashedPassword = hashPassword(user.password); const hashedPassword = hashPassword(user.passwort);
const result = await db<UserType>("users").insert({ const result = await db<UserType>("benutzer").insert({
email: user.email, email: user.email,
password: hashedPassword, passwort: hashedPassword,
uid: uid uid: uid
}, ["id"]) }, ["id"])

View File

@@ -4,12 +4,12 @@ export const UserTypeValidator = z.object({
id: z.number(), id: z.number(),
uid: z.string().length(36), uid: z.string().length(36),
email: z.string().max(255), email: z.string().max(255),
password: z.string().min(6), passwort: z.string().min(6),
}) })
export const UserRegisterValidator = z.object({ export const UserRegisterValidator = z.object({
email: z.string().max(255), email: z.string().max(255),
password: z.string().min(6), passwort: z.string().min(6),
}) })
export type UserType = z.infer<typeof UserTypeValidator> export type UserType = z.infer<typeof UserTypeValidator>

View File

@@ -0,0 +1,130 @@
import { Ausstellgrund, Ausweisart } from "src/lib/Ausweis/types";
import { Energiekennwerte } from "src/lib/Energiekennwerte";
import { Gebaeude } from "src/lib/Gebaeude";
import { getKlimafaktorenClient } from "src/lib/Klimafaktoren";
import { getHeizwertfaktorClient } from "src/lib/server/Heizwertfaktor";
export default class Verbrauchsausweis {
public ausweisart: Ausweisart = "VA";
public ausstellgrund: Ausstellgrund = "Vermietung";
public warmwasser_enthalten: boolean = true;
public uid?: string;
public baujahr_anlage: number[] = [];
public get energetische_nutzfläche(): number {
return (
this.gebaeude.wohnflaeche *
(this.gebaeude.keller_beheizt ? 1.35 : 1.2)
);
}
public regnummer?: string;
public kennwerte: Energiekennwerte = new Energiekennwerte();
public gebaeude: Gebaeude = new Gebaeude();
public constructor(initializer?: Verbrauchsausweis) {
if (initializer) {
this.ausweisart = initializer.ausweisart;
this.kennwerte = initializer.kennwerte;
}
}
public get primaer_energie_verbrauch(): Promise<number> {
return (async () => {
const Endenergieverbrauch = await this.end_energie_verbrauch;
const brennstoff_1 = getHeizwertfaktorClient(
this.kennwerte.energietraeger_1,
this.kennwerte.einheit_1
);
return Endenergieverbrauch * brennstoff_1.primärenergiefaktor;
})();
}
public get end_energie_verbrauch(): Promise<number> {
return (async () => {
const date = this.kennwerte.zeitraum;
const klimafaktoren = await getKlimafaktorenClient(
date,
this.gebaeude.plz
);
// Endenergieverbrauch
// Um den EEV auszurechnen, müssen die Verbräuche zu kWh konvertiert werden.
let brennstoff_1 = getHeizwertfaktorClient(
this.kennwerte.energietraeger_1,
this.kennwerte.einheit_1
);
let brennstoff_2 = getHeizwertfaktorClient(
this.kennwerte.energietraeger_2,
this.kennwerte.einheit_2
);
let verbrauch_1_kwh =
this.kennwerte.verbrauch_1 * brennstoff_1.umrechnungsfaktor;
let verbrauch_2_kwh =
this.kennwerte.verbrauch_2 * brennstoff_1.umrechnungsfaktor;
let verbrauch_3_kwh =
this.kennwerte.verbrauch_3 * brennstoff_1.umrechnungsfaktor;
let verbrauch_4_kwh =
this.kennwerte.verbrauch_4 * brennstoff_2.umrechnungsfaktor;
let verbrauch_5_kwh =
this.kennwerte.verbrauch_5 * brennstoff_2.umrechnungsfaktor;
let verbrauch_6_kwh =
this.kennwerte.verbrauch_6 * brennstoff_2.umrechnungsfaktor;
let warmwasserZuschlag = 0;
let leerstandsZuschlag = 0;
let kuehlungsZuschlag = 0;
if (this.kennwerte.anteil_warmwasser_1 == 0) {
warmwasserZuschlag = 20 * this.energetische_nutzfläche * 3;
}
if (this.gebaeude.leerstand > 0) {
let durchschnittsKlimafaktor =
klimafaktoren.reduce((a, b) => a + b, 0) / 3;
leerstandsZuschlag =
((verbrauch_1_kwh +
verbrauch_2_kwh +
verbrauch_3_kwh +
verbrauch_4_kwh +
verbrauch_5_kwh +
verbrauch_6_kwh) *
(this.gebaeude.leerstand / 100)) /
durchschnittsKlimafaktor;
}
if (this.gebaeude.energiequelle_2_nutzung[3]) {
kuehlungsZuschlag = 6 * this.energetische_nutzfläche * 3;
}
let anteil_heizung = 1 - this.kennwerte.anteil_warmwasser_1 / 100;
let anteil_warmwasser = this.kennwerte.anteil_warmwasser_1 / 100;
let Energieverbrauchskennwert =
(anteil_heizung *
(verbrauch_1_kwh + verbrauch_4_kwh) *
klimafaktoren[0] +
anteil_warmwasser * (verbrauch_1_kwh + verbrauch_4_kwh) +
anteil_heizung *
(verbrauch_2_kwh + verbrauch_5_kwh) *
klimafaktoren[1] +
anteil_warmwasser * (verbrauch_2_kwh + verbrauch_5_kwh) +
anteil_heizung *
(verbrauch_3_kwh + verbrauch_6_kwh) *
klimafaktoren[2] +
anteil_warmwasser * (verbrauch_3_kwh + verbrauch_6_kwh) +
warmwasserZuschlag +
leerstandsZuschlag +
kuehlungsZuschlag) /
3 /
this.energetische_nutzfläche;
return Energieverbrauchskennwert;
})();
}
}

View File

@@ -0,0 +1 @@
export { default as Verbrauchsausweis } from "./Verbrauchsausweis";

View File

@@ -0,0 +1,36 @@
import { Ausweis } from "src/lib/Ausweis/Ausweis";
import { Dachgeschoss, Lueftungskonzept } from "src/lib/Ausweis/types";
import { BitChecker } from "src/lib/BitChecker";
export class Gebaeude {
public typ: string = "";
public plz: string = "";
public ort: string = "";
public strasse: string = "";
public gebaeudeteil: string = "";
public saniert: boolean = false;
public baujahr: number = 0;
public einheiten: number = 0;
public wohnflaeche: number = 0;
public keller_beheizt: boolean = false;
public dachgeschoss_beheizt: Dachgeschoss = Dachgeschoss.UNBEHEIZT;
public lueftungskonzept: Lueftungskonzept = "Fensterlüftung";
public wird_gekuehlt: boolean = false;
public leerstand: number = 0;
public versorgungssysteme: boolean[] = BitChecker(0);
public fenster_dach: boolean[] = BitChecker(0);
public energiequelle_2_nutzung: boolean[] = BitChecker(0);
public daemmung: boolean[] = BitChecker(0);
public ausweis?: Ausweis;
public uid?: string;
public constructor(initializer?: Gebaeude) {
if (initializer) {
this.typ = initializer.typ;
this.plz = initializer.plz;
this.ort = initializer.ort;
}
}
}

View File

View File

@@ -1,5 +1,8 @@
import type { APIRoute } from "astro"; import type { APIRoute } from "astro";
import { ActionFailedError, error, success } from "src/lib/APIResponse"; import { ActionFailedError, MissingEntityError, error, success } from "src/lib/APIResponse";
import { Ausweis } from "src/lib/Ausweis/Ausweis";
import { Energiekennwerte } from "src/lib/Energiekennwerte";
import { Gebaeude } from "src/lib/Gebaeude";
import { db } from "src/lib/shared"; import { db } from "src/lib/shared";
import { z } from "zod"; import { z } from "zod";
@@ -62,6 +65,10 @@ const AusweisUploadChecker = z.object({
ausweis_uid: z.string().optional(), ausweis_uid: z.string().optional(),
}); });
const AusweisDownloadChecker = z.object({
uid: z.string()
})
/** /**
* Erstellt einen Verbrauchsausweis anhand der gegebenen Daten und trägt ihn in die Datenbank ein. * Erstellt einen Verbrauchsausweis anhand der gegebenen Daten und trägt ihn in die Datenbank ein.
* @param param0 * @param param0
@@ -136,3 +143,39 @@ export const post: APIRoute = async ({ request }) => {
gebaeude: gebaeude[0], gebaeude: gebaeude[0],
}); });
}; };
export const get: APIRoute = async ({ request }) => {
const body: z.infer<typeof AusweisDownloadChecker> = await request.json();
const validation = AusweisDownloadChecker.safeParse(body);
if (!validation.success) {
return error(validation.error.issues);
}
let result = await db<{ gebaeude: Gebaeude, kennwerte: Energiekennwerte, ausweis: Ausweis}>("gebaeude")
.select([
db.raw("(json_agg(gebaeude)->0) AS gebaeude"),
db.raw("(json_agg(energiekennwerte)->0) AS kennwerte"),
db.raw("(json_agg(energieausweise)->0) AS ausweis"),
])
.leftJoin(
"energiekennwerte",
"energiekennwerte.gebaeude_id",
"gebaeude.id"
)
.leftJoin(
"energieausweise",
"energieausweise.gebaeude_id",
"gebaeude.id"
)
.where("gebaeude.uid", body.uid)
.groupBy("gebaeude.id")
.first();
if (!result) {
return MissingEntityError("gebäude");
}
return result;
};

View File

@@ -23,7 +23,7 @@ export const post: APIRoute = async ({ request }) => {
} }
// Validate Password // Validate Password
if (!validatePassword(user.password, body.password)) { if (!validatePassword(user.passwort, body.password)) {
return error(["Invalid email or password."]); return error(["Invalid email or password."]);
} }

View File

@@ -1,7 +1,8 @@
import type { APIRoute } from "astro"; import type { APIRoute } from "astro";
import { success, MissingPropertyError, MissingEntityError, ActionFailedError, InvalidDataError } from "../../lib/APIResponse"; import { success, MissingPropertyError, MissingEntityError, ActionFailedError, InvalidDataError, error } from "../../lib/APIResponse";
import { User } from "../../lib/User"; import { User } from "../../lib/User";
import { UserRegisterValidator, UserType, UserTypeValidator } from "../../lib/User/type"; import { UserRegisterValidator, UserType, UserTypeValidator } from "../../lib/User/type";
import { z } from "zod";
/** /**
* Ruft einen Nutzer anhand seiner uid aus der Datenbank ab. * Ruft einen Nutzer anhand seiner uid aus der Datenbank ab.
@@ -24,7 +25,7 @@ export const get: APIRoute = async ({ request }) => {
} }
export const put: APIRoute = async ({ request }) => { export const put: APIRoute = async ({ request }) => {
const body = await request.json(); const body: z.infer<typeof UserRegisterValidator> = await request.json();
const validate = UserRegisterValidator.safeParse(body); const validate = UserRegisterValidator.safeParse(body);
@@ -32,6 +33,12 @@ export const put: APIRoute = async ({ request }) => {
return InvalidDataError(validate.error); return InvalidDataError(validate.error);
} }
const user = await User.fromEmail(body.email);
if (user) {
return error(["Email address is already being used."]);
}
const result = await User.create(body as UserType); const result = await User.create(body as UserType);
if (!result) { if (!result) {

View File

@@ -1,44 +1,10 @@
--- ---
import AusweisLayout from "~/layouts/AusweisLayout.astro"; import AusweisLayout from "~/layouts/AusweisLayout.astro";
import VerbrauchsausweisContent from "~/components/Verbrauchsausweis/VerbrauchsausweisContent.svelte"; import VerbrauchsausweisContent from "~/components/Verbrauchsausweis/VerbrauchsausweisContent.svelte";
import { Verbrauchsausweis } from "src/lib/Ausweis/Verbrauchsausweis";
import { db } from "src/lib/shared";
import { Gebaeude } from "src/lib/Gebaeude";
import { Energiekennwerte } from "src/lib/Energiekennwerte";
import { Ausweis, getAusweis } from "src/lib/Ausweis/Ausweis";
let gebaeude = new Gebaeude(); const uid = Astro.cookies.get("ausweis_uid").value;
if (Astro.cookies.has("ausweis_uid")) {
const uid = Astro.cookies.get("ausweis_uid").value;
let result = await db<{ gebaeude: Gebaeude, kennwerte: Energiekennwerte, ausweis: Ausweis}>("gebaeude")
.select([
db.raw("(json_agg(gebaeude)->0) AS gebaeude"),
db.raw("(json_agg(energiekennwerte)->0) AS kennwerte"),
db.raw("(json_agg(energieausweise)->0) AS ausweis"),
])
.leftJoin(
"energiekennwerte",
"energiekennwerte.gebaeude_id",
"gebaeude.id"
)
.leftJoin(
"energieausweise",
"energieausweise.gebaeude_id",
"gebaeude.id"
)
.where("gebaeude.uid", uid)
.groupBy("gebaeude.id")
.first();
if (result) {
gebaeude = new Gebaeude(result.gebaeude);
gebaeude.ausweis = getAusweis(result.ausweis.ausweisart, result.ausweis);
gebaeude.ausweis.gebaeude = gebaeude;
gebaeude.ausweis.kennwerte = new Energiekennwerte(result.kennwerte);
}
}
--- ---
<AusweisLayout title="Verbrauchsausweis erstellen"> <AusweisLayout title="Verbrauchsausweis erstellen">
<VerbrauchsausweisContent client:load gebaeude={gebaeude} /> <VerbrauchsausweisContent client:load uid={uid} />
</AusweisLayout> </AusweisLayout>

View File

@@ -3,8 +3,9 @@ header {
} }
body { body {
background-color: #fff; background-color: #fafafa;
background-image: url(../images/pattern.png); overflow-x: hidden;
/* background-image: url(../images/pattern.png); */
background-repeat: repeat; background-repeat: repeat;
background-attachment: fixed; background-attachment: fixed;
} }
@@ -37,28 +38,7 @@ body {
} }
.mainContent { .mainContent {
position: relative; @apply relative w-full rounded-lg border shadow-md px-6 py-8 bg-white;
-webkit-box-shadow: rgb(162, 162, 162) 1px 1px 3px 1px;
box-shadow: rgb(162, 162, 162) 1px 1px 3px 1px;
background-color: rgb(255, 255, 255);
margin-top: 0px;
padding: 0% 2%;
width: 100%;
}
.only-form-mainContent {
padding: 20px 10px;
width: calc(100% - 4px);
}
.left-sidebar {
-ms-grid-row: 1;
-ms-grid-column: 1;
grid-area: 1/1/1/3;
padding-top: 0px;
margin-top: 0px;
width: 100%;
} }
.right-sidebar { .right-sidebar {
@@ -81,16 +61,6 @@ body {
margin: 1em 0em; margin: 1em 0em;
} }
.mainContent h2 {
font-weight: normal;
font-size: 1rem;
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
margin: 1em 0em;
}
.mainContent a { .mainContent a {
color: #3A4AB5; color: #3A4AB5;
text-decoration: none; text-decoration: none;
@@ -159,23 +129,6 @@ footer a {
cursor: pointer; cursor: pointer;
} }
.MajorHeading {
color: rgb(58, 74, 181);
font-weight: 500;
font-size: 12px;
position: absolute;
top: 22px;
right: 0px;
line-height: 1.3em;
}
.MajorHeading.smaller {
color: rgb(255, 125, 38);
margin-top: 59px;
font-size: 15px;
font-weight: 500;
}
.nav-head { .nav-head {
background-color: #444F94; background-color: #444F94;
display: -webkit-box; display: -webkit-box;
@@ -588,12 +541,6 @@ textarea {
margin-top: 2em; margin-top: 2em;
} }
.right-sidebar img,
.left-sidebar img {
width: 100%;
height: auto;
}
.clear { .clear {
clear: both; clear: both;
} }
@@ -895,17 +842,6 @@ table {
flex-direction: column; flex-direction: column;
} }
.right-sidebar img,
.left-sidebar img {
width: 100%;
height: auto;
}
.right-sidebar .large-button,
.left-sidebar .large-button {
white-space: pre-wrap;
text-align: center;
}
block { block {
display: block; display: block;
@@ -1330,16 +1266,6 @@ content>*:nth-child(3) {
display: none; display: none;
} }
/* NOTE: Right Sidebar */
.infoCard {
-webkit-box-shadow: rgb(162, 162, 162) 1px 1px 3px 1px;
box-shadow: rgb(162, 162, 162) 1px 1px 3px 1px;
background: rgba(255, 255, 255, 0.4);
border-radius: 1em;
padding: 1em;
margin-bottom: 1.5em;
}
.large-button { .large-button {
background: #3a4ab5; background: #3a4ab5;
color: #fff; color: #fff;

View File

@@ -13,15 +13,6 @@
grid-template-rows: auto; grid-template-rows: auto;
} }
.mainContent {
display: block;
-webkit-box-shadow: rgb(162, 162, 162) 1px 1px 3px 1px;
box-shadow: rgb(162, 162, 162) 1px 1px 3px 1px;
background-color: rgb(255, 255, 255);
margin-top: 0px;
padding: 20px 1.5%;
}
.stretch-2 { .stretch-2 {
-ms-grid-row: 1; -ms-grid-row: 1;
-ms-grid-column: 1; -ms-grid-column: 1;
@@ -35,17 +26,6 @@
width: 100%; width: 100%;
} }
.left-sidebar {
display: block;
max-width: 940px;
-ms-grid-row: 1;
-ms-grid-column: 1;
grid-area: 1/1/1/1;
padding-top: 0px;
margin-top: 0px;
width: 100%;
}
.right-sidebar { .right-sidebar {
display: block; display: block;
max-width: 940px; max-width: 940px;

View File

@@ -20,17 +20,6 @@
grid-template-rows: auto; grid-template-rows: auto;
} }
.mainContent {
display: block;
position: relative;
box-sizing: border-box;
-webkit-box-shadow: rgb(162, 162, 162) 1px 1px 3px 1px;
box-shadow: rgb(162, 162, 162) 1px 1px 3px 1px;
background-color: rgb(255, 255, 255);
margin-top: 0px;
padding: 40px;
}
.stretch-2 { .stretch-2 {
-ms-grid-row: 1; -ms-grid-row: 1;
-ms-grid-column: 2; -ms-grid-column: 2;
@@ -56,17 +45,6 @@
} }
.left-sidebar {
display: block;
max-width: 940px;
-ms-grid-row: 1;
-ms-grid-column: 1;
grid-area: 1/1/1/1;
padding-top: 0px;
margin-top: 0px;
width: 100%;
}
.right-sidebar { .right-sidebar {
display: block; display: block;
max-width: 940px; max-width: 940px;
@@ -87,16 +65,6 @@
text-align: left; text-align: left;
} }
.mainContent h2 {
font-weight: normal;
font-size: 1.6rem;
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
margin: 1em 0em !important;
}
.mainContent a { .mainContent a {
color: #3A4AB5; color: #3A4AB5;
text-decoration: none; text-decoration: none;
@@ -157,23 +125,6 @@
cursor: pointer; cursor: pointer;
} }
.MajorHeading {
color: rgb(58, 74, 181);
font-weight: 500;
font-size: 26px;
position: absolute;
top: 30px;
right: 0px;
line-height: 1.3em;
}
.MajorHeading.smaller {
color: rgb(255, 125, 38);
margin-top: 35px;
font-size: 20px;
font-weight: 600;
}
.nav-head { .nav-head {
background-color: rgb(255, 125, 38); background-color: rgb(255, 125, 38);
display: -webkit-box; display: -webkit-box;
@@ -195,7 +146,7 @@
.headerButton { .headerButton {
width: auto; width: auto;
font-size: 18px; font-size: 20px;
font-weight: 500; font-weight: 500;
color: rgb(255, 255, 255); color: rgb(255, 255, 255);
background-color: rgb(255, 125, 38); background-color: rgb(255, 125, 38);
@@ -244,25 +195,6 @@
margin-top: 0px; margin-top: 0px;
} }
.nav-card {
width: 100%;
height: auto;
position: relative;
background-color: #fff;
-webkit-box-shadow: rgb(162, 162, 162) 1px 1px 3px 1px;
box-shadow: rgb(162, 162, 162) 1px 1px 3px 1px;
margin-bottom: 1.5em;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
z-index: 1;
}
.active { .active {
background-color: none; background-color: none;
opacity: 1; opacity: 1;