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 { Gebaeude } from "src/lib/Gebaeude";
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;
// TODO: Das ist scheise
let tags = writable([]);
$: ausweis = gebaeude.ausweis || new Verbrauchsausweis();
</script>
@@ -71,11 +78,27 @@
/>z.B. 1994-2001.
</HelpLabel>
<div>
<input
<TagInput
name="IGheizung"
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
bind:value={ausweis.baujahr_anlage}
autocomplete="off"
bind:tags
/>
</div>
</div>
@@ -92,12 +115,27 @@
-saniert- angeben.
</HelpLabel>
<div>
<input
<TagInput
name="IGbaujahr"
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
autocomplete="off"
bind:value={gebaeude.baujahr}
bind:tags
/>
</div>
</div>

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import cookie from "cookiejs";
import { addNotification } from "./Notifications/shared";
let email: string;
let password: string;
let hasError: boolean;
async function login() {
const response = await fetch("/api/login", {
@@ -23,8 +23,14 @@
localStorage.setItem("expires", json.data.expires);
window.location.href = "/user";
} 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>
@@ -52,13 +58,7 @@
required
/>
</div>
<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}
<button class="button" on:click={login}>Einloggen</button>
<div class="flex-row justify-between" style="margin-top: 10px">
<a href="/signup">Registrieren</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">
let password: string;
import { addNotification } from "./Notifications/shared";
let passwort: string;
let email: string;
let hasError: boolean;
async function login() {
const response = await fetch("/api/user", {
method: "PUT",
body: JSON.stringify({
password, email
passwort, email
})
})
@@ -16,19 +17,22 @@
if (json.success == true) {
window.location.href = "/login";
} 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>
<div style="width:50%;margin: 0 auto">
<h1>Registrieren:</h1>
<div class="login_page">
{#if hasError}
<p>Leider ist diese Email bereits vergeben.</p>
{/if}
<div class="block_4" style="margin-top: 25px;">
<h4 class="heading_3">Email</h4>
<div class="flex flex-col gap-4">
<div>
<h4>Email</h4>
<input
type="text"
placeholder="Email"
@@ -37,26 +41,23 @@
required
/>
</div>
<div class="block_4">
<h4 class="heading_3">Passwort</h4>
<div>
<h4>Passwort</h4>
<input
type="password"
placeholder="********"
class="formInput"
bind:value={password}
bind:value={passwort}
required
/>
</div>
<div class="mt-2 flex flex-row justify-between">
<button on:click={login}
<button class="button" on:click={login}
>Registrieren</button
>
<a class="button"
<div class="flex-row justify-between" style="margin-top: 10px">
<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>
</div>
</div>

View File

@@ -1,4 +1,4 @@
<div class="left-sidebar">
<div class="flex flex-col gap-6">
<nav>
<div class="nav-card">
<div class="card-menu-option dropdown">
@@ -159,13 +159,25 @@
</div>
<style>
.nav-card {
@apply rounded-lg w-full flex flex-col shadow-md border;
}
.infoCard {
@apply bg-white rounded-none;
@apply bg-white rounded-lg border p-4 shadow-md;
}
.dropdown-content > .card-menu-option,
.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,
@@ -175,7 +187,7 @@
}
.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 {
@@ -231,15 +243,11 @@
}
.nav-card .card-menu-option:first-child {
border-top: 0px solid #fff;
border-top-left-radius: 0px;
border-top-right-radius: 0px;
@apply rounded-tr-lg rounded-tl-lg;
}
.nav-card .card-menu-option:last-child {
border-bottom: 0px solid #fff;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
@apply rounded-br-lg rounded-bl-lg;
}
.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">
<h2 style="font-weight: bold; font-size: 1.2em; color: #3A4AB5;">
Rufen Sie uns an<br /> Wir sind gerne für Sie da
@@ -128,6 +128,6 @@
<style>
.infoCard {
@apply bg-white rounded-none;
@apply bg-white rounded-lg border p-4 shadow-md;
}
</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 BilderZusatzsysteme from "../Ausweis/BilderZusatzsysteme.svelte";
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 =
(gebaeude.baujahr < 1978 &&
gebaeude.einheiten <= 4 &&
gebaeude.saniert == false &&
(ausweis.ausstellgrund == "Vermietung" ||
ausweis.ausstellgrund == "Sonstiges")) ||
ausweis.ausstellgrund == "Neubau" ||
ausweis.ausstellgrund == "Modernisierung" ||
ausweis.ausstellgrund == "Verkauf";
$: ausweis = $gebaeude.ausweis || new Verbrauchsausweis();
function automatischAusfüllen() {
gebaeude.baujahr = 1962;
ausweis.baujahr_anlage = 1974;
gebaeude.saniert = true;
gebaeude.einheiten = 1;
$gebaeude.baujahr = 1962;
ausweis.baujahr_anlage = 1952;
$gebaeude.saniert = true;
$gebaeude.einheiten = 1;
ausweis.ausstellgrund = "Vermietung";
ausweis.kennwerte.verbrauch_1 = 15000;
ausweis.kennwerte.verbrauch_2 = 14000;
ausweis.kennwerte.verbrauch_3 = 16000;
gebaeude.wohnflaeche = 152;
gebaeude.keller_beheizt = true;
$gebaeude.wohnflaeche = 152;
$gebaeude.keller_beheizt = true;
ausweis.kennwerte.energietraeger_1 = "Erdgas H";
ausweis.kennwerte.einheit_1 = "kWh";
ausweis.kennwerte.anteil_warmwasser_1 = 18;
ausweis.kennwerte.zeitraum = moment("12.01.2019");
gebaeude.plz = "21039";
gebaeude.ort = "Hamburg";
gebaeude.strasse = "Curslacker Deich 170";
gebaeude.gebaeudeteil = "Gesamtgebäude";
//ausweis.upload();
$gebaeude.plz = "21039";
$gebaeude.ort = "Hamburg";
$gebaeude.strasse = "Curslacker Deich 170";
$gebaeude.gebaeudeteil = "Gesamtgebäude";
}
</script>
@@ -81,29 +94,7 @@
<Label>A - Prüfung der Ausweisart</Label>
<Ausweisart bind: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>
<Ausweisart bind:gebaeude={$gebaeude} />
<hr />
@@ -128,7 +119,7 @@
required
data-msg-minlength="min. 5 Zeichen"
data-msg-maxlength="max. 40 Zeichen"
bind:value={gebaeude.strasse}
bind:value={$gebaeude.strasse}
/>
</div>
</div>
@@ -136,8 +127,8 @@
<!-- PLZ -->
<div class="form-group col-md-4 PLZ">
<ZipSearch
bind:zip={gebaeude.plz}
bind:city={gebaeude.ort}
bind:zip={$gebaeude.plz}
bind:city={$gebaeude.ort}
name="zip"
/>
</div>
@@ -151,7 +142,7 @@
<input
name="IGort"
readonly={true}
bind:value={gebaeude.ort}
bind:value={$gebaeude.ort}
type="text"
/>
</div>
@@ -173,7 +164,7 @@
autocomplete="off"
data-rule-minlength="2"
data-msg-minlength="min. 2 Zeichen"
bind:value={gebaeude.wohnflaeche}
bind:value={$gebaeude.wohnflaeche}
/>
</div>
</div>
@@ -185,7 +176,7 @@
<select
name="IGkeller"
required
bind:value={gebaeude.keller_beheizt}
bind:value={$gebaeude.keller_beheizt}
>
<option>Bitte auswählen</option>
<option value={false}>nicht vorhanden</option>
@@ -214,7 +205,7 @@
<Label>C - Eingabe von 3 zusammenhängenden Verbrauchsjahren</Label>
<div class="GRB">
<Verbrauch bind:ausweis />
<Verbrauch bind:gebaeude={$gebaeude} />
</div>
<hr />
@@ -287,7 +278,7 @@
><input
type="checkbox"
name="IGversorgungssysteme1"
bind:checked={gebaeude.energiequelle_2_nutzung[0]}
bind:checked={$gebaeude.energiequelle_2_nutzung[0]}
value="Heizung"
/>Heizung</label
>
@@ -295,7 +286,7 @@
><input
type="checkbox"
name="IGversorgungssysteme2"
bind:checked={gebaeude.energiequelle_2_nutzung[1]}
bind:checked={$gebaeude.energiequelle_2_nutzung[1]}
value="Warmwasser"
/>Warmwasser</label
>
@@ -303,7 +294,7 @@
><input
type="checkbox"
name="IGversorgungssysteme3"
bind:checked={gebaeude.energiequelle_2_nutzung[2]}
bind:checked={$gebaeude.energiequelle_2_nutzung[2]}
value="Lüftung"
/>Lüftung</label
>
@@ -311,7 +302,7 @@
><input
type="checkbox"
name="IGversorgungssysteme4"
bind:checked={gebaeude.energiequelle_2_nutzung[3]}
bind:checked={$gebaeude.energiequelle_2_nutzung[3]}
value="Kühlung"
/>Kühlung</label
>
@@ -450,7 +441,7 @@
>F - Bitte prüfen Sie hier die Angaben zum Sanierungszustand des
Gebäudes</Label
>
<BilderZusatzsysteme {gebaeude} />
<BilderZusatzsysteme gebaeude={$gebaeude} />
<hr />
<div class="flex flex-row justify-between">
<Hilfe />
@@ -458,3 +449,63 @@
</div>
</fieldset>
</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 Header from '../components/Header.astro';
import SidebarLeft from '../components/SidebarLeft.astro';
import SidebarRight from '../components/SidebarRight.astro';
import NotificationWrapper from "~/components/Notifications/NotificationWrapper.svelte";
export interface Props {
title: string;
@@ -110,8 +110,8 @@ const schema = JSON.stringify({
@apply text-xl font-medium mt-6 mb-4;
}
input, select, textarea {
@apply py-1.5 px-2.5 rounded-lg w-full outline-none text-base text-slate-800 border;
input, select, textarea, .input {
@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 {

View File

@@ -4,6 +4,7 @@ import Footer from '../components/Footer.astro';
import Header from '../components/Header.astro';
import SidebarLeft from '../components/SidebarLeft.astro';
import SidebarRight from '../components/SidebarRight.astro';
import NotificationWrapper from "~/components/Notifications/NotificationWrapper.svelte";
export interface Props {
title: string;
@@ -94,6 +95,7 @@ const schema = JSON.stringify({
<SidebarRight></SidebarRight>
</main>
<Footer />
<NotificationWrapper client:load></NotificationWrapper>
</body>
</html>
@@ -102,7 +104,7 @@ const schema = JSON.stringify({
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;
color: #fff !important;
}
@@ -112,7 +114,11 @@ const schema = JSON.stringify({
}
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 {

View File

@@ -10,7 +10,7 @@ export class Verbrauchsausweis {
public warmwasser_enthalten: boolean = true;
public id?: number;
public uid?: string;
public baujahr_anlage: number = 0;
public baujahr_anlage: number[] = [];
public get energetische_nutzfläche(): number {
return (
@@ -24,7 +24,12 @@ export class Verbrauchsausweis {
public kennwerte: Energiekennwerte = new Energiekennwerte();
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 {
try {

View File

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

View File

@@ -15,7 +15,7 @@ export class User {
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) {
return null;
@@ -29,7 +29,7 @@ export class User {
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) {
return null;
@@ -48,7 +48,7 @@ export class User {
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) {
return null;
@@ -63,11 +63,11 @@ export class User {
}
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,
password: hashedPassword,
passwort: hashedPassword,
uid: uid
}, ["id"])

View File

@@ -4,12 +4,12 @@ export const UserTypeValidator = z.object({
id: z.number(),
uid: z.string().length(36),
email: z.string().max(255),
password: z.string().min(6),
passwort: z.string().min(6),
})
export const UserRegisterValidator = z.object({
email: z.string().max(255),
password: z.string().min(6),
passwort: z.string().min(6),
})
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 { 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 { z } from "zod";
@@ -62,6 +65,10 @@ const AusweisUploadChecker = z.object({
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.
* @param param0
@@ -136,3 +143,39 @@ export const post: APIRoute = async ({ request }) => {
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
if (!validatePassword(user.password, body.password)) {
if (!validatePassword(user.passwort, body.password)) {
return error(["Invalid email or password."]);
}

View File

@@ -1,7 +1,8 @@
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 { UserRegisterValidator, UserType, UserTypeValidator } from "../../lib/User/type";
import { z } from "zod";
/**
* 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 }) => {
const body = await request.json();
const body: z.infer<typeof UserRegisterValidator> = await request.json();
const validate = UserRegisterValidator.safeParse(body);
@@ -32,6 +33,12 @@ export const put: APIRoute = async ({ request }) => {
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);
if (!result) {

View File

@@ -1,44 +1,10 @@
---
import AusweisLayout from "~/layouts/AusweisLayout.astro";
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();
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);
}
}
const uid = Astro.cookies.get("ausweis_uid").value;
---
<AusweisLayout title="Verbrauchsausweis erstellen">
<VerbrauchsausweisContent client:load gebaeude={gebaeude} />
<VerbrauchsausweisContent client:load uid={uid} />
</AusweisLayout>

View File

@@ -3,8 +3,9 @@ header {
}
body {
background-color: #fff;
background-image: url(../images/pattern.png);
background-color: #fafafa;
overflow-x: hidden;
/* background-image: url(../images/pattern.png); */
background-repeat: repeat;
background-attachment: fixed;
}
@@ -37,28 +38,7 @@ body {
}
.mainContent {
position: relative;
-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%;
@apply relative w-full rounded-lg border shadow-md px-6 py-8 bg-white;
}
.right-sidebar {
@@ -81,16 +61,6 @@ body {
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 {
color: #3A4AB5;
text-decoration: none;
@@ -159,23 +129,6 @@ footer a {
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 {
background-color: #444F94;
display: -webkit-box;
@@ -588,12 +541,6 @@ textarea {
margin-top: 2em;
}
.right-sidebar img,
.left-sidebar img {
width: 100%;
height: auto;
}
.clear {
clear: both;
}
@@ -895,17 +842,6 @@ table {
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 {
display: block;
@@ -1330,16 +1266,6 @@ content>*:nth-child(3) {
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 {
background: #3a4ab5;
color: #fff;

View File

@@ -13,15 +13,6 @@
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 {
-ms-grid-row: 1;
-ms-grid-column: 1;
@@ -35,17 +26,6 @@
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 {
display: block;
max-width: 940px;

View File

@@ -20,17 +20,6 @@
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 {
-ms-grid-row: 1;
-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 {
display: block;
max-width: 940px;
@@ -87,16 +65,6 @@
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 {
color: #3A4AB5;
text-decoration: none;
@@ -157,23 +125,6 @@
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 {
background-color: rgb(255, 125, 38);
display: -webkit-box;
@@ -195,7 +146,7 @@
.headerButton {
width: auto;
font-size: 18px;
font-size: 20px;
font-weight: 500;
color: rgb(255, 255, 255);
background-color: rgb(255, 125, 38);
@@ -244,25 +195,6 @@
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 {
background-color: none;
opacity: 1;