ZIP Suche + Kundendaten

This commit is contained in:
Moritz Utcke
2023-04-06 10:54:07 +04:00
parent 4647bebef4
commit 115cfffdc2
7 changed files with 416 additions and 342 deletions

View File

@@ -0,0 +1,260 @@
<script lang="ts">
import ProgressBar from "~/components/Ausweis/Progressbar.svelte";
import HelpLabel from "~/components/HelpLabel.svelte";
import ZipSearch from "../ZIPSearch.svelte";
let deliveryAddress: boolean = false;
let mailAddressCity: string = "";
let mailAddressZipCode: string = "";
let invoiceAddressCity: string = "";
let invoiceAddressZipCode: string = "";
</script>
<div class="col-12">
<div class="row">
<div class="flex flex-row gap-8 items-center mb-8">
<div class="flex flex-col w-full">
<h1>Verbrauchsausweis erstellen - 45€</h1>
<ProgressBar progress={50} />
</div>
</div>
<form
method="post"
target="_self"
novalidate
class="w-full"
action="./kaufabschluss"
>
<fieldset>
<div class="GRB3">
<HelpLabel title="Ansprechpartner" />
<hr />
<div class="grid grid-cols-5 gap-4">
<!-- Anrede -->
<div>
<label>Anrede *</label>
<div>
<select name="Aanrede" class="form-control">
<option>bitte auswählen</option>
<option value="Herr">Herr</option>
<option value="Frau">Frau</option>
</select>
</div>
</div>
<!-- Vorname -->
<div>
<label>Vorname *</label>
<input name="Avorname" type="text" required />
</div>
<!-- Nachname -->
<div>
<label>Nachname *</label>
<input name="Anachname" type="text" required />
</div>
<!-- Telefon -->
<div>
<label>Telefon</label>
<input
name="Atelefon"
class="zahlen"
type="text"
/>
</div>
<!-- Email -->
<div>
<label>E-Mail *</label>
<input name="Aemail" type="email" required />
</div>
</div>
</div>
<hr />
<div class="GRB3">
<HelpLabel title="Rechnungsadresse" />
<hr />
<!-- Empfänger -->
<div class="grid grid-cols-5 gap-4">
<div>
<label>Empfänger *</label>
<input
name="Rempfaenger"
type="text"
required
data-rule-maxlength="100"
data-msg-maxlength="max. 100 Zeichen"
/>
</div>
<!-- Zusatzzeile -->
<div>
<label>Zusatzzeile</label>
<input
name="Rzusatzzeile"
type="text"
data-rule-maxlength="80"
data-msg-maxlength="max. 80 Zeichen"
/>
</div>
<!-- Strasse -->
<div>
<label>Straße, Hausnummer *</label>
<input
name="Rstrasse"
type="text"
required
data-rule-maxlength="40"
data-msg-maxlength="max. 40 Zeichen"
/>
</div>
<!-- PLZ -->
<ZipSearch name="vplz" bind:zip={invoiceAddressZipCode} bind:city={invoiceAddressCity}></ZipSearch>
<!-- Ort -->
<div>
<label>Ort *</label>
<input name="Rort" readonly type="text" required value={invoiceAddressCity} />
</div>
<!-- Telefon -->
<div>
<label>Telefon</label>
<input
name="Rtelefon"
class="zahlen"
type="text"
/>
</div>
<!-- Email -->
<div>
<label>E-Mail</label>
<input name="Remail" type="email" />
</div>
</div>
</div>
<hr />
<div class="GRB3">
<HelpLabel title="Versandadresse" />
<hr />
<div class="grid grid-cols-5 gap-4">
<div class="flex flex-row gap-2 items-center">
<input
class="w-[15px] h-[15px]"
type="checkbox"
id="deliveryAddress"
bind:checked={deliveryAddress}
/>
<label for="deliveryAddress"
>Abweichende Versandadresse</label
>
</div>
<!-- Empfänger -->
<div>
<label>Empfänger *</label>
<input
name="Vempfaenger"
type="text"
readonly={!deliveryAddress}
required
data-rule-maxlength="100"
data-msg-maxlength="max. 100 Zeichen"
/>
</div>
<!-- Zusatzzeile -->
<div>
<label>Zusatzzeile</label>
<input
name="Vzusatzzeile"
type="text"
readonly={!deliveryAddress}
data-rule-maxlength="80"
data-msg-maxlength="max. 80 Zeichen"
/>
</div>
<!-- Strasse -->
<div>
<label>Straße, Hausnummer *</label>
<input
name="Vstrasse"
type="text"
readonly={!deliveryAddress}
required
data-rule-maxlength="40"
data-msg-maxlength="max. 40 Zeichen"
/>
</div>
<!-- PLZ -->
<ZipSearch name="rplz" readonly={!deliveryAddress} bind:zip={mailAddressZipCode} bind:city={mailAddressCity}></ZipSearch>
<!-- Ort -->
<div>
<label>Ort *</label>
<input name="Vort" type="text" readonly required value={mailAddressCity} />
</div>
</div>
</div>
<hr />
<div class="flex flex-row w-full justify-between">
<button>Zurück</button>
<button>Weiter</button>
</div>
</fieldset>
</form>
</div>
</div>
<style>
:global(.GRB) {
@apply border-2 border-[#ffcc03] p-4 flex flex-row rounded-lg justify-between w-full;
background: linear-gradient(
135deg,
rgba(252, 234, 187, 1) 0%,
rgba(253, 235, 189, 1) 52%,
rgba(251, 223, 147, 1) 100%
);
}
:global(.GRB3) {
@apply flex flex-col border-2 border-[#ffcc03] p-4 rounded-lg;
background: linear-gradient(
135deg,
rgba(252, 234, 187, 1) 0%,
rgba(253, 235, 189, 1) 52%,
rgba(251, 223, 147, 1) 100%
);
}
input,
select {
display: block;
width: 100%;
height: calc(2.25rem + 2px);
padding: 0.375rem 0.75rem;
font-size: 1rem;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: 0.25rem;
}
</style>

View File

@@ -23,10 +23,10 @@
}
.tooltip {
@apply absolute left-0 translate-x-[-50%] max-w-[350px] w-max break-words invisible bg-white rounded-lg p-2 shadow-lg top-0 translate-y-[calc(-100%-8px)];
@apply absolute left-0 translate-x-[-50%] max-w-[350px] w-max break-words invisible bg-white rounded-lg p-2 shadow-lg top-0 translate-y-[calc(-100%-8px)] transition-all duration-300 opacity-0;
}
.tooltip-opener:hover .tooltip {
@apply visible;
@apply visible opacity-100;
}
</style>

View File

@@ -0,0 +1,73 @@
<script lang="ts">
import type { ZIPInformation } from "src/lib/ZIPInformation";
export let name: string;
export let readonly: boolean = false;
export let city: string;
export let zip: string = "";
let hideZipDropdown: boolean = true;
let zipCodes: ZIPInformation[] = [];
async function fetchZipCodeInformation() {
const result = await fetch(`/api/zip?zip=${zip}`, {
method: "GET"
}).then(r => r.json());
if (result.success === true) {
zipCodes = result.data;
if (zipCodes.length > 0) {
hideZipDropdown = false;
}
}
}
function clickOutside(element: HTMLElement, callbackFunction: () => any) {
function onClick(event: MouseEvent) {
if (!element.contains(event.target as HTMLElement)) {
callbackFunction();
}
}
document.body.addEventListener('click', onClick);
return {
destroy() {
document.body.removeEventListener('click', onClick);
}
}
}
</script>
<div class="relative" use:clickOutside={() => {
hideZipDropdown = true;
}}>
<label for={name}>PLZ *</label>
<input
name={name}
id={name}
class="zahlen"
type="number"
required
readonly={readonly}
bind:value={zip}
on:input={fetchZipCodeInformation}
on:focus={() => {
if (zipCodes.length > 0) {
hideZipDropdown = false
}
}}
maxlength="5"
/>
<div class="absolute top-[calc(100%+4px)] left-0 w-full bg-white py-2 shadow-md rounded-lg" hidden={hideZipDropdown}>
{#each zipCodes as {zip: z, city: c}}
<div class="hover:bg-gray-100 cursor-pointer px-2 py-0.5" tabindex="-1" on:click={() => {
zip = z;
city = c;
hideZipDropdown = true;
}}>{z}, {c}</div>
{/each}
</div>
</div>

View File

@@ -0,0 +1,47 @@
import { db } from "../shared";
type DatabaseZIPInformation = {
zip: string,
city: string,
state: string
};
export class ZIPInformation {
public constructor(public zip: string, public city: string, public state: string) {
}
public static async fromZipCode(zip: string): Promise<null | ZIPInformation[]> {
if (zip.length > 5) {
return null;
}
let results = await db<DatabaseZIPInformation>("zip_codes").select("*").whereLike("zip", `${zip}%`).limit(10);
if (!results) {
return null;
}
return results.map(result => new ZIPInformation(result.zip, result.city, result.state))
}
public static async fromCity(city: string): Promise<null | ZIPInformation[]> {
let results = await db<DatabaseZIPInformation>("zip_codes").select("*").where("city", city);
if (!results) {
return null;
}
return results.map(result => new ZIPInformation(result.zip, result.city, result.state))
}
public static async fromState(state: string): Promise<null | ZIPInformation[]> {
let results = await db<DatabaseZIPInformation>("zip_codes").select("*").where("state", state);
if (!results) {
return null;
}
return results.map(result => new ZIPInformation(result.zip, result.city, result.state))
}
}

28
src/pages/api/zip.ts Normal file
View File

@@ -0,0 +1,28 @@
import type { APIRoute } from "astro";
import { success, MissingPropertyError, MissingEntityError, InvalidDataError } from "../../lib/APIResponse";
import { ZIPInformation } from "src/lib/ZIPInformation";
/**
* Ruft einen Nutzer anhand seiner uid aus der Datenbank ab.
* @param param0 Die Request mit dem request body. Dieser enthält entweder eine uid mit der der Benutzer identifiziert werden kann.
*/
export const get: APIRoute = async ({ request }) => {
const body = Object.fromEntries(new URLSearchParams(request.url.split("?")[1]))
let result;
if (body.zip) {
result = await ZIPInformation.fromZipCode(body.zip)
} else if (body.city) {
result = await ZIPInformation.fromCity(body.city)
} else if (body.state) {
result = await ZIPInformation.fromState(body.state)
} else {
return MissingPropertyError(["Either 'state', 'city' or 'zip' have to exist in request body."])
}
if (!result) {
return MissingEntityError("zip info")
}
return success(result);
}

View File

@@ -1,3 +1,7 @@
---
console.log(Object.fromEntries(new URLSearchParams(await Astro.request.text())))
return Astro.redirect("/verbrauchsausweis/kundendaten");
---

View File

@@ -1,347 +1,9 @@
---
import PerformanceScore from "~/components/Ausweis/PerformanceScore.svelte";
import ProgressBar from "~/components/Ausweis/Progressbar.svelte";
import HelpLabel from "~/components/HelpLabel.svelte";
import Kundendaten from "~/components/Ausweis/Kundendaten.svelte";
import AusweisLayout from "~/layouts/AusweisLayout.astro";
---
<AusweisLayout title="Kundendaten Aufnehmen - IBCornelsen">
<div class="col-12">
<div class="row">
<div class="flex flex-row gap-8 items-center mb-8">
<div class="flex flex-col w-full">
<h1>Verbrauchsausweis erstellen - 45€</h1>
<ProgressBar progress={50} />
</div>
<PerformanceScore />
</div>
<form
method="post"
target="_self"
novalidate
class="w-full"
action="./kaufabschluss"
>
<fieldset>
<div class="GRB3">
<HelpLabel title="Ansprechpartner" />
<hr />
<div class="flex flex-row w-full justify-between">
<!-- Anrede -->
<div>
<label>Anrede *</label>
<div>
<select name="Aanrede" class="form-control">
<option>bitte auswählen</option>
<option value="Herr">Herr</option>
<option value="Frau">Frau</option>
</select>
</div>
</div>
<!-- Titel -->
<div>
<label>Titel</label>
<div>
<select name="Atitel" class="form-control">
<option>bitte auswählen</option>
<option value="Dr.">Dr.</option>
<option value="Prof.">Prof.</option>
<option value="Prof.Dr."
>Prof.Dr.</option
>
<option value="Dipl.-Ing."
>Dipl.-Ing.</option
>
</select>
</div>
</div>
<!-- Vorname -->
<div>
<label>Vorname *</label>
<div>
<input
name="Avorname"
type="text"
required
/>
</div>
</div>
<!-- Nachname -->
<div>
<label>Nachname *</label>
<div>
<input
name="Anachname"
type="text"
required
/>
</div>
</div>
<!-- Telefon -->
<div>
<label>Telefon</label>
<div>
<input
name="Atelefon"
class="form-control input-md zahlen"
type="text"
/>
</div>
</div>
<!-- Email -->
<div>
<label>E-Mail *</label>
<div>
<input
name="Aemail"
class="form-control input-md"
type="email"
required
/>
</div>
</div>
</div>
</div>
<hr class="trenner_form" />
<div class="GRB3">
<HelpLabel title="Rechnungsadresse" />
<hr>
<!-- Empfänger -->
<div class="flex flex-row w-full justify-between">
<div>
<label>Empfänger *</label>
<div>
<input
name="Rempfaenger"
type="text"
required
data-rule-maxlength="100"
data-msg-maxlength="max. 100 Zeichen"
/>
</div>
</div>
<!-- Zusatzzeile -->
<div>
<label>Zusatzzeile</label>
<div>
<input
name="Rzusatzzeile"
class="form-control input-md"
type="text"
data-rule-maxlength="80"
data-msg-maxlength="max. 80 Zeichen"
/>
</div>
</div>
<!-- Strasse -->
<div class="form-group col-md-12">
<label>Straße, Hausnummer *</label>
<div>
<input
name="Rstrasse"
class="form-control input-md"
type="text"
required
data-rule-maxlength="40"
data-msg-maxlength="max. 40 Zeichen"
/>
</div>
</div>
<!-- PLZ -->
<div class="form-group col-md-4 PLZ">
<label>PLZ *</label>
<div>
<input
name="Rplz"
class="form-control input-md zahlen"
onfocus="RclearOrt()"
type="text"
required
maxlength="5"
/>
<div style="position:relative;"></div>
</div>
</div>
<!-- Ort -->
<div class="form-group col-md-8">
<label>Ort *</label>
<div>
<input
name="Rort"
class="form-control input-md"
readonly
type="text"
required
/>
</div>
</div>
<!-- Telefon -->
<div>
<label>Telefon</label>
<div>
<input
name="Rtelefon"
class="form-control input-md zahlen"
type="text"
/>
</div>
</div>
<!-- Email -->
<div>
<label>E-Mail</label>
<div>
<input
name="Remail"
class="form-control input-md"
type="email"
/>
</div>
</div>
</div>
</div>
<hr class="trenner_form" />
<div class="GRB3">
<HelpLabel title="Versandadresse"></HelpLabel>
<hr>
<div class="flex flex-row w-full justify-between">
<div class="flex flex-row">
<label>Abweichende Versandadresse</label>
<input
type="checkbox"
/>
</div>
<!-- Empfänger -->
<div>
<label>Empfänger *</label>
<div>
<input
name="Vempfaenger"
type="text"
readonly
required
data-rule-maxlength="100"
data-msg-maxlength="max. 100 Zeichen"
/>
</div>
</div>
<!-- Zusatzzeile -->
<div>
<label>Zusatzzeile</label>
<div>
<input
name="Vzusatzzeile"
class="form-control input-md"
type="text"
readonly
data-rule-maxlength="80"
data-msg-maxlength="max. 80 Zeichen"
/>
</div>
</div>
<!-- Strasse -->
<div class="form-group col-md-12">
<label>Straße, Hausnummer *</label>
<div>
<input
name="Vstrasse"
class="form-control input-md"
type="text"
readonly
required
data-rule-maxlength="40"
data-msg-maxlength="max. 40 Zeichen"
/>
</div>
</div>
<!-- PLZ -->
<div class="form-group col-md-4 VPLZ">
<label>PLZ *</label>
<div>
<input
name="Vplz"
class="form-control input-md zahlen"
onfocus="VclearOrt()"
type="text"
readonly
required
maxlength="5"
/>
<div style="position:relative;"></div>
</div>
</div>
<!-- Ort -->
<div class="form-group col-md-8">
<label>Ort *</label>
<div>
<input
name="Vort"
type="text"
readonly
required
/>
</div>
</div>
</div>
</div>
<hr class="trenner_form" />
<div class="flex flex-row w-full justify-between">
<button>Zurück</button>
<button>Weiter</button>
</div>
</fieldset>
</form>
</div>
</div>
<Kundendaten client:load></Kundendaten>
</AusweisLayout>
<style>
.GRB {
@apply border-2 border-[#ffcc03] p-4 flex flex-row rounded-lg justify-between w-full;
background-color: rgba(252, 234, 187, 0.2);
}
.GRB3 {
@apply flex flex-col border-2 border-[#ffcc03] p-4 rounded-lg;
background-color: rgba(252, 234, 187, 0.2);
}
input,
select {
display: block;
width: 100%;
height: calc(2.25rem + 2px);
padding: 0.375rem 0.75rem;
font-size: 1rem;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: 0.25rem;
}
</style>