backend & types
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { location } from "../store"
|
||||
import { location } from "./lib/store"
|
||||
|
||||
export let url = ""
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") console.log("App initialized")
|
||||
if (typeof window !== "undefined") console.log("App initialized")
|
||||
</script>
|
||||
|
||||
<h1>Hello World</h1>
|
||||
12
frontend/src/api.ts
Normal file
12
frontend/src/api.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { apiRequest } from "../../api/hooks/lib/ssr"
|
||||
|
||||
export const api = async <T>(
|
||||
endpoint: string,
|
||||
options?: ApiOptions,
|
||||
body?: any
|
||||
): Promise<{ data: T; count: number } | any> => {
|
||||
let data = await apiRequest(endpoint, options, body)
|
||||
// @ts-ignore
|
||||
console.log(data, "data")
|
||||
return data
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import configClient from "../../api/hooks/config-client"
|
||||
|
||||
export const apiBaseURL = "/api/"
|
||||
export const release = configClient.release
|
||||
console.log("Release: ", release)
|
||||
|
||||
export const sentryDSN = "https://5063f9b5564d0fdece4e47a8e2e63672@sentry.basehosts.de/3"
|
||||
export const sentryTracingOrigins = ["localhost", "project-domain.tld", /^\//]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import App from "./components/App.svelte"
|
||||
import { location } from "./store"
|
||||
import App from "./App.svelte"
|
||||
import { location } from "./lib/store"
|
||||
|
||||
const publishLocation = (_p?: string) => {
|
||||
let _s: string
|
||||
@@ -17,11 +17,8 @@ const publishLocation = (_p?: string) => {
|
||||
}
|
||||
|
||||
const newLocation = {
|
||||
path:
|
||||
_p || (typeof window !== "undefined" && window.location?.pathname),
|
||||
search: _p
|
||||
? _s
|
||||
: typeof window !== "undefined" && window.location?.search,
|
||||
path: _p || (typeof window !== "undefined" && window.location?.pathname),
|
||||
search: _p ? _s : typeof window !== "undefined" && window.location?.search,
|
||||
hash: _p ? _h : typeof window !== "undefined" && window.location?.hash,
|
||||
push: !!_p,
|
||||
pop: !_p,
|
||||
@@ -35,9 +32,7 @@ if (typeof history !== "undefined") {
|
||||
if (typeof Proxy !== "undefined") {
|
||||
// modern browser
|
||||
const historyApply = (target, thisArg, argumentsList) => {
|
||||
publishLocation(
|
||||
argumentsList && argumentsList.length >= 2 && argumentsList[2]
|
||||
)
|
||||
publishLocation(argumentsList && argumentsList.length >= 2 && argumentsList[2])
|
||||
Reflect.apply(target, thisArg, argumentsList)
|
||||
}
|
||||
|
||||
@@ -57,11 +52,7 @@ if (typeof history !== "undefined") {
|
||||
publishLocation(url)
|
||||
return pushStateFn.apply(history, arguments)
|
||||
}
|
||||
history.replaceState = function (
|
||||
data: any,
|
||||
title: string,
|
||||
url?: string
|
||||
) {
|
||||
history.replaceState = function (data: any, title: string, url?: string) {
|
||||
publishLocation(url)
|
||||
return replaceStateFn.apply(history, arguments)
|
||||
}
|
||||
@@ -83,4 +74,4 @@ const app = new App({
|
||||
hydrate,
|
||||
})
|
||||
|
||||
export default app
|
||||
export default app
|
||||
|
||||
18
frontend/src/lib/components/pagebuilder/form/Checkbox.svelte
Normal file
18
frontend/src/lib/components/pagebuilder/form/Checkbox.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import type { Writable } from "svelte/store"
|
||||
|
||||
export let label = "" // Der Text, der neben der Checkbox angezeigt wird
|
||||
export let bindValue: any // Der Wert der Checkbox
|
||||
export let formValues: Writable<FormValues> // Das formValues-Objekt aus dem übergeordneten Komponenten
|
||||
export let id = "" // Eindeutige ID für die Checkbox, um den Label korrekt zuzuweisen
|
||||
export let name = "" // Name des Formularelements
|
||||
</script>
|
||||
|
||||
<label class="checkbox-container" for="{id}">
|
||||
<input type="checkbox" bind:checked="{bindValue}" {id} {name} bind:this="{formValues[name]}" />
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
|
||||
<style>
|
||||
/* Sie können hier den Stil für Ihre Checkbox anpassen */
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import type { Writable } from "svelte/store"
|
||||
export let groupTitle: string
|
||||
export let checkboxes: { name: string; emailName: string }[]
|
||||
export let formValues: Writable<FormValues>
|
||||
export let rowNr: number
|
||||
</script>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<h3>{groupTitle}</h3>
|
||||
<div class="containerr">
|
||||
{#each checkboxes as checkbox, i}
|
||||
<label class="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox-input checkit"
|
||||
bind:this="{$formValues[`checkbox_${groupTitle}_${rowNr}_${checkbox.emailName}`]}"
|
||||
/>
|
||||
<span class="checkit-span"></span>
|
||||
{checkbox.name}
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
.checkbox-group {
|
||||
.checkbox-input {
|
||||
vertical-align: middle;
|
||||
margin-right: 8px;
|
||||
width: fit-content !important;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
width: 100%;
|
||||
vertical-align: middle;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
& > .containerr {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
133
frontend/src/lib/components/pagebuilder/form/Datepicker.svelte
Normal file
133
frontend/src/lib/components/pagebuilder/form/Datepicker.svelte
Normal file
@@ -0,0 +1,133 @@
|
||||
<script lang="ts">
|
||||
import { CalendarView } from "fluent-svelte"
|
||||
import type { Writable } from "svelte/store"
|
||||
|
||||
|
||||
export let groupTitle: string
|
||||
export let datePickerProps: DatePickerProps
|
||||
export let formValues: Writable<FormValues>
|
||||
export let rowNr: number
|
||||
export let formCol: FormColumn
|
||||
|
||||
const today = new Date()
|
||||
const oneYearFromNow = new Date(today)
|
||||
oneYearFromNow.setFullYear(today.getFullYear() + 1)
|
||||
const indexToDay: Record<number, string> = {
|
||||
0: "sunday",
|
||||
1: "monday",
|
||||
2: "tuesday",
|
||||
3: "wednesday",
|
||||
4: "thursday",
|
||||
5: "friday",
|
||||
6: "saturday",
|
||||
}
|
||||
|
||||
function generateBlackoutDates(props: DatePickerProps): Date[] {
|
||||
let blackoutDates = []
|
||||
|
||||
// Funktion, um einen ISO-String in ein vereinfachtes Date-Objekt umzuwandeln
|
||||
const dateFromISOString = (isoString: string) => {
|
||||
const date = new Date(isoString)
|
||||
return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))
|
||||
}
|
||||
|
||||
const dayToIndex: Record<string, number> = {
|
||||
sunday: 0,
|
||||
monday: 1,
|
||||
tuesday: 2,
|
||||
wednesday: 3,
|
||||
thursday: 4,
|
||||
friday: 5,
|
||||
saturday: 6,
|
||||
}
|
||||
|
||||
// Initialisiere ein aktuelles Datum auf heute
|
||||
let currentDate = new Date(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate()))
|
||||
|
||||
// Schleife durch die nächsten 365 Tage
|
||||
for (let i = 0; i <= 365; i++) {
|
||||
// Überprüfen, ob dieses Datum erlaubt ist
|
||||
const isAllowed = props.allowedDateRanges.some((range) => {
|
||||
const fromDate = dateFromISOString(range.from)
|
||||
const toDate = dateFromISOString(range.to)
|
||||
|
||||
return currentDate >= fromDate && currentDate <= toDate
|
||||
})
|
||||
|
||||
// Überprüfen, ob der Wochentag ausgeschlossen ist
|
||||
const currentDayIndex = currentDate.getUTCDay()
|
||||
const currentDayString = indexToDay[currentDayIndex]
|
||||
const isExcluded = props.excludeDays?.includes(currentDayString)
|
||||
|
||||
// Wenn nicht erlaubt oder ausgeschlossen, zu den Blackout-Daten hinzufügen
|
||||
if (!isAllowed || isExcluded) {
|
||||
blackoutDates.push(new Date(currentDate))
|
||||
}
|
||||
|
||||
// Zum nächsten Tag wechseln
|
||||
currentDate.setUTCDate(currentDate.getUTCDate() + 1)
|
||||
}
|
||||
|
||||
return blackoutDates
|
||||
}
|
||||
|
||||
let value = new Date()
|
||||
const blackoutDates = generateBlackoutDates(datePickerProps)
|
||||
|
||||
function setFormValues() {
|
||||
$formValues[`datepicker_${groupTitle}_${rowNr}_${formCol.datePickerEmailTitle}`] = {
|
||||
value: value.toLocaleDateString("de-DE"),
|
||||
required: !formCol.datePickerNotRequired,
|
||||
}
|
||||
}
|
||||
//needs email conntection
|
||||
$: {
|
||||
if (value) {
|
||||
setFormValues()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="container">
|
||||
<div
|
||||
class="datepicker"
|
||||
on:click|preventDefault|stopPropagation
|
||||
on:keydown|stopPropagation
|
||||
on:submit|stopPropagation|preventDefault
|
||||
>
|
||||
<CalendarView
|
||||
weekStart="{1}"
|
||||
bind:vaue="{value}"
|
||||
on:change="{(e) => {
|
||||
value = e.detail
|
||||
}}"
|
||||
min="{today}"
|
||||
max="{oneYearFromNow}"
|
||||
blackout="{blackoutDates}"
|
||||
locale="de-DE"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
@import url("https://unpkg.com/fluent-svelte/theme.css");
|
||||
.container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
--fds-solid-background-quarternary: var(--background-color);
|
||||
--fds-text-primary: var(--normal-font-color);
|
||||
--fds-text-secondary: var(--normal-font-color-80);
|
||||
--fds-text-tertiary: var(--normal-font-color-50);
|
||||
--fds-text-disabled: var(--normal-font-color-30);
|
||||
--fds-accent-disabled: var(--opposite-bg-color-5);
|
||||
--fds-control-strong-stroke-default: var(--opposite-bg-color-80);
|
||||
.datepicker {
|
||||
width: fit-content;
|
||||
margin: 5px 0px;
|
||||
box-shadow: 0 0 25px 10px var(--opposite-bg-color-5);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
200
frontend/src/lib/components/pagebuilder/form/Form.svelte
Normal file
200
frontend/src/lib/components/pagebuilder/form/Form.svelte
Normal file
@@ -0,0 +1,200 @@
|
||||
<script lang="ts">
|
||||
import CheckboxGroup from "./CheckboxGroup.svelte"
|
||||
import Datepicker from "./Datepicker.svelte"
|
||||
import FormLabelNumberBlock from "./FormLabelNumberBlock.svelte"
|
||||
import type { Writable } from "svelte/store"
|
||||
import Select from "./Select.svelte"
|
||||
|
||||
export let formRow: FormRow
|
||||
export let index: number
|
||||
|
||||
export let formValues: Writable<FormValues>
|
||||
/* function getSortedFields(column: FormColumn) {
|
||||
const fields = [
|
||||
...(column.text.length ? [{ text: column.text, type: "text", order: column.textfieldOrder ?? 3 }] : []),
|
||||
...(column.showTimes ? [{ type: "times", order: column.timesfieldOrder ?? 3, times: column.times }] : []),
|
||||
...(column.showDate
|
||||
? [{ type: "date", order: column.datefieldOrder ?? 3, datePlaceholder: column.datePlaceholder }]
|
||||
: []),
|
||||
]
|
||||
return fields.sort((a, b) => a.order - b.order)
|
||||
}*/
|
||||
//formRow.columns = formRow.columns.map((e) => getSortedFields(e))
|
||||
function removeInvalid(e: Event) {
|
||||
let element = e.currentTarget as HTMLElement
|
||||
if (element) element.classList.remove("invalid")
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="form-row">
|
||||
<h3 style="margin-bottom: -0.5rem;">
|
||||
{#if formRow.showRowName}{formRow.rowName || ""}{/if}
|
||||
</h3>
|
||||
<div class="form-cols">
|
||||
{#each formRow.columns as column, columnIndex}
|
||||
<div class="form-column">
|
||||
<h3>{column?.title ?? ""}</h3>
|
||||
{#if column?.annotation}
|
||||
<p>{column?.annotation}</p>
|
||||
{/if}
|
||||
{#if column.showLabelNumber}
|
||||
<FormLabelNumberBlock {column} {formValues} rowIndex="{index}" />
|
||||
{/if}
|
||||
|
||||
{#if column?.showTimes}
|
||||
<label bind:this="{$formValues[`times_${column?.title ? column.title + '_' : ''}label`]}">
|
||||
<select
|
||||
id="time-select"
|
||||
bind:this="{$formValues[
|
||||
`times_${column?.title ?? 'Zeiten'}_${column.timesfieldOrder}_${index}_${
|
||||
column.emailNameTimes || columnIndex + 'invalidtimes'
|
||||
}`
|
||||
]}"
|
||||
required="{column?.timeNotRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
>
|
||||
<option value="" disabled selected>Bitte Uhrzeit wählen</option>
|
||||
{#each column?.times ?? [] as time}
|
||||
<option value="{time?.timeFrom}-{time?.timeTo}">
|
||||
{time?.timeFrom} - {time?.timeTo}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
{#if column?.showSelect}
|
||||
<label bind:this="{$formValues[`select_${column?.title ? column.title + '_' : ''}label`]}">
|
||||
<select
|
||||
required="{column?.dateSelectNotRequired !== true}"
|
||||
bind:this="{$formValues[
|
||||
`select_${column?.title ?? 'Auswahl'}_${column.datefieldOrder}_${index}_${
|
||||
column?.emailNameTime || columnIndex + 'invalidtime'
|
||||
}`
|
||||
]}"
|
||||
on:change="{removeInvalid}"
|
||||
>
|
||||
<option value="" disabled selected>{column.selectTitle}</option>
|
||||
{#each column?.selectEntries as entry}
|
||||
<option value="{entry?.leftSide}-{entry?.rightSide}">
|
||||
{entry?.leftSide}-{entry?.rightSide}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
{#if column.showDate}
|
||||
<label bind:this="{$formValues[`date_${column?.title ? column.title + '_' : ''}label`]}">
|
||||
<input
|
||||
class="date"
|
||||
type="date"
|
||||
required="{column?.dateNotRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
bind:this="{$formValues[
|
||||
`date_${column?.title ?? 'Datum'}_${column.datefieldOrder}_${index}_${
|
||||
column?.emailNameDate || columnIndex + 'invaliddate'
|
||||
}`
|
||||
]}"
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
{#if column.showNumber}
|
||||
<label
|
||||
bind:this="{$formValues[
|
||||
`input_${column.title ? column.title + '_' : ''}${column?.numberPlaceholder}_label`
|
||||
]}"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
step="any"
|
||||
required="{column?.numberNotRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
placeholder="{column?.numberPlaceholder}"
|
||||
bind:this="{$formValues[
|
||||
`input_${column.title ? column.title + '_' : ''}${column?.numberPlaceholder}_${
|
||||
column.numberfieldOrder
|
||||
}_${index}_${column?.emailNameNumber || columnIndex + 'invalidnumber'}`
|
||||
]}"
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
{#if column.showCheckboxGroup}
|
||||
<CheckboxGroup
|
||||
checkboxes="{column.checkboxes}"
|
||||
groupTitle="{column.groupTitle}"
|
||||
{formValues}
|
||||
rowNr="{index}"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if column.showDatePicker}
|
||||
<Datepicker
|
||||
datePickerProps="{column.datePickerProps}"
|
||||
groupTitle="{column.groupTitle}"
|
||||
{formValues}
|
||||
rowNr="{index}"
|
||||
formCol="{column}"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if column.showMultiSelect}
|
||||
<Select
|
||||
groupTitle="{column.groupTitle}"
|
||||
{formValues}
|
||||
rowNr="{index}"
|
||||
formCol="{column}"
|
||||
options="{column.multiSelectOptions}"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#each column.text ?? [] as textField, textFieldIndex}
|
||||
<div>
|
||||
<h3 class="textTitle">{textField.textTitle || ""}</h3>
|
||||
{#if textField?.textArea}
|
||||
<label bind:this="{$formValues[`textarea_Nachricht_label`]}">
|
||||
<textarea
|
||||
placeholder="{textField?.textPlaceholder}"
|
||||
required="{textField?.notRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
bind:this="{$formValues[
|
||||
`textarea_Nachricht_${textField.textfieldOrder}_${index}_${
|
||||
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
|
||||
}`
|
||||
]}"
|
||||
></textarea>
|
||||
</label>
|
||||
{:else}
|
||||
<label
|
||||
bind:this="{$formValues[
|
||||
`input_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_label`
|
||||
]}"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="{textField?.textPlaceholder}"
|
||||
on:change="{removeInvalid}"
|
||||
required="{textField?.notRequired !== true}"
|
||||
bind:this="{$formValues[
|
||||
`${
|
||||
textField?.telValidation
|
||||
? 'Telefon'
|
||||
: textField?.emailValidation
|
||||
? 'Email'
|
||||
: 'input'
|
||||
}_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_${
|
||||
textField.textfieldOrder
|
||||
}_${index}_${
|
||||
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
|
||||
}`
|
||||
]}"
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import type { Writable } from "svelte/store"
|
||||
export let column: FormColumn
|
||||
export let formValues: Writable<FormValues>
|
||||
|
||||
export let rowIndex: number
|
||||
let blockContainer: HTMLDivElement
|
||||
$formValues["blockGroups"] = new Set(column.labelNumber.map((e) => e.group))
|
||||
</script>
|
||||
|
||||
<div class="blockContainer" bind:this="{blockContainer}">
|
||||
{#each column.labelNumber as outerblock, i}
|
||||
<div class="{`block`}" bind:this="{$formValues['blockGroups'][i]}">
|
||||
<h3>{outerblock.title}</h3>
|
||||
<div class="innterBlockContainer">
|
||||
{#each outerblock.block as innerBlock}
|
||||
<div class="innerBlock">
|
||||
<div class="label">{innerBlock.label}</div>
|
||||
<input
|
||||
placeholder="0"
|
||||
on:change="{(e) => {
|
||||
let element = e.currentTarget
|
||||
element.classList.remove('border-red')
|
||||
blockContainer.classList.remove('invalidBlocks')
|
||||
}}"
|
||||
type="number"
|
||||
name="{outerblock.group}"
|
||||
class="{`group-${outerblock.group}`}"
|
||||
bind:this="{$formValues[
|
||||
`numberLabel_${outerblock.title ?? ''}_${innerBlock.label}_${rowIndex}_${
|
||||
innerBlock.emailName
|
||||
}`
|
||||
]}"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
.block {
|
||||
width: 100%;
|
||||
padding: 25px 0px;
|
||||
h3 {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.innerBlock {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.label {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
input {
|
||||
text-align: center;
|
||||
width: 80px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
401
frontend/src/lib/components/pagebuilder/form/Formular.svelte
Normal file
401
frontend/src/lib/components/pagebuilder/form/Formular.svelte
Normal file
@@ -0,0 +1,401 @@
|
||||
<script lang="ts">
|
||||
import Form from "./Form.svelte"
|
||||
import MobileForm from "./MobileForm.svelte"
|
||||
import { writable } from "svelte/store"
|
||||
import { validateFields } from "../../../functions/validateFields"
|
||||
import { sendForm } from "../../../functions/sendForm"
|
||||
import { navigate } from "svelte-routing"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
export let col: Column
|
||||
export let siteId: string
|
||||
export let rowNr: number
|
||||
export let row: Row
|
||||
export let rows: Row[]
|
||||
|
||||
let formSend = false
|
||||
let formValues = writable<FormValues>({})
|
||||
|
||||
function submitForm() {
|
||||
const values: Array<ValueEntry> = Object.entries($formValues).map((entry: ObjectEntry) => {
|
||||
const key: string = entry[0]
|
||||
const value: CustomHTMLElement = entry[1]
|
||||
if (!key.includes("numberLabel")) {
|
||||
return [key, [value.checked || value.value, value.required]] as ValueEntry
|
||||
} else {
|
||||
return [key, [value.value, value, value.getAttribute("name"), value.required]] as ValueEntry
|
||||
}
|
||||
})
|
||||
|
||||
const fields: Array<ValueEntry> = values.filter((entry: ValueEntry) => !entry[0].includes("label"))
|
||||
const validation = validateFields(fields)
|
||||
if (validation.length) {
|
||||
validation.forEach((error) => {
|
||||
// @ts-ignore
|
||||
if (error[0].includes("block")) {
|
||||
// @ts-ignore
|
||||
error[1]()
|
||||
} else {
|
||||
// @ts-ignore
|
||||
$formValues[error[1]].classList.add("invalid")
|
||||
const label = $formValues[`${error[1]}_label`]
|
||||
const errorElement = document.createElement("div")
|
||||
errorElement.className = "error-message"
|
||||
errorElement.textContent = error[0] as string
|
||||
label?.appendChild(errorElement)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// @ts-ignore
|
||||
const formObj: FormObj = {}
|
||||
fields.forEach((entry) => {
|
||||
if (Array.isArray(entry[1]) && entry[1].length == 4) {
|
||||
// @ts-ignore
|
||||
if (entry[1][0]) formObj[entry[0]] = entry[1]
|
||||
} else {
|
||||
if (!entry[1][0] && !entry[1][1]) return
|
||||
// @ts-ignore
|
||||
formObj[entry[0]] = entry[1][0]
|
||||
}
|
||||
})
|
||||
let form: any
|
||||
row.column.forEach((col) => {
|
||||
if (col.contentType == "form") form = col
|
||||
})
|
||||
if (!form) return
|
||||
formObj["formRows"] = form.formRows.map((r: FormRow) => r.rowName)
|
||||
formObj["formTitle"] = form.formEmailTitle
|
||||
formSend = true
|
||||
const hny = document.getElementById("hny") as HTMLInputElement
|
||||
if (hny) formObj["honey"] = hny.checked
|
||||
sendForm(formObj)
|
||||
}
|
||||
}
|
||||
|
||||
let innerWidth = typeof window !== "undefined" ? window?.innerWidth || 0 : 0
|
||||
if (typeof window !== "undefined") {
|
||||
onMount(() => {
|
||||
const handleResize = () => {
|
||||
innerWidth = window.innerWidth
|
||||
}
|
||||
|
||||
window.addEventListener("resize", handleResize)
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if formSend}
|
||||
<div class="success-message">
|
||||
<h1>Formular erfolgreich gesendet!</h1>
|
||||
<p>Vielen Dank für Ihre Anfrage. Wir werden uns in Kürze bei Ihnen melden.</p>
|
||||
</div>
|
||||
{:else}
|
||||
<form
|
||||
class="form-rows"
|
||||
on:submit="{(e) => {
|
||||
e.preventDefault()
|
||||
submitForm()
|
||||
}}"
|
||||
>
|
||||
{#each col.formRows ?? [] as formRow, i}
|
||||
{#if innerWidth < 768}
|
||||
<MobileForm {formRow} {formValues} index="{i}" />
|
||||
{:else}
|
||||
<Form {formRow} {formValues} index="{i}" />
|
||||
{/if}
|
||||
{/each}
|
||||
<div class="row additional" style="padding: 0px;">
|
||||
<div class="data-protection">
|
||||
<label bind:this="{$formValues[`agreement_label`]}">
|
||||
<input
|
||||
required="{true}"
|
||||
class="checkit"
|
||||
type="checkbox"
|
||||
on:change="{(e) => {
|
||||
let element = e.currentTarget
|
||||
element.classList.remove('invalid')
|
||||
}}"
|
||||
bind:this="{$formValues['agreement']}"
|
||||
/>
|
||||
<span class="checkit-span"></span>
|
||||
</label>
|
||||
|
||||
<div class="datasec">
|
||||
<button on:click|preventDefault="{() => navigate('/datenschutz')}" class="link">
|
||||
Datenschutz
|
||||
</button>
|
||||
akzeptieren
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
name="contact_me_by_fax_only"
|
||||
id="hny"
|
||||
value="1"
|
||||
style="display:none !important"
|
||||
tabindex="-1"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button class="submit-request" type="submit">Anfrage absenden</button>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<style lang="less" global>
|
||||
@import "../../../assets/css/variables.less";
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea,
|
||||
.data-protection {
|
||||
margin: 5px 0px;
|
||||
box-shadow: 0 0 25px 10px var(--opposite-bg-color-5);
|
||||
}
|
||||
|
||||
.success-message {
|
||||
h1 {
|
||||
color: var(--heading-font-color);
|
||||
font-size: 36px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
.invalidBlocks {
|
||||
border: 2px solid rgb(255, 0, 0) !important;
|
||||
position: relative;
|
||||
&::after {
|
||||
font-size: 0.9rem !important;
|
||||
color: red !important;
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
content: "Bitte wähle entweder eine Kartenanzahl oder einen Wunschbetrag aus.";
|
||||
}
|
||||
}
|
||||
|
||||
.border-red {
|
||||
border-color: red !important;
|
||||
}
|
||||
|
||||
.no-margin {
|
||||
margin-top: 15px !important;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
border: 2px solid red !important;
|
||||
}
|
||||
.error-message {
|
||||
font-size: 0.9rem !important;
|
||||
color: red !important;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
.checkit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkit-span {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border: 1px solid grey;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[type="checkbox"]:checked + span:before {
|
||||
content: "\2714";
|
||||
position: absolute;
|
||||
transform-origin: bottom;
|
||||
}
|
||||
|
||||
.form-rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
width: 100%;
|
||||
h3 {
|
||||
font-weight: bold !important;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: 1.5rem;
|
||||
|
||||
.date {
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
label {
|
||||
width: 100% !important;
|
||||
font-size: inherit;
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
width: 100%;
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
.form-cols {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.form-column {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--hover-color);
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea,
|
||||
.data-protection {
|
||||
padding: 10px 20px;
|
||||
border: 0px solid var(--opposite-bg-color);
|
||||
border-bottom: 3px solid var(--heading-font-color);
|
||||
outline: 0px solid var(--opposite-bg-color);
|
||||
color: var(--opposite-bg-color);
|
||||
background-color: var(--background-color);
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.data-protection {
|
||||
display: flex;
|
||||
|
||||
flex-wrap: nowrap;
|
||||
gap: 5px !important;
|
||||
justify-content: start;
|
||||
flex-direction: row !important;
|
||||
}
|
||||
|
||||
input[type="date"] {
|
||||
font-family: inherit;
|
||||
width: 100% !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 10px 20px;
|
||||
border: 0;
|
||||
border-bottom: 3px solid var(--heading-font-color);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
border-bottom-color: var(--heading-font-color);
|
||||
}
|
||||
|
||||
#time-select {
|
||||
appearance: none;
|
||||
background-image: url("../../../../../media/clock.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 20px center;
|
||||
background-size: 18px;
|
||||
|
||||
option {
|
||||
padding: 10px 20px;
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
}
|
||||
@media @mobile {
|
||||
.date {
|
||||
width: 100vw !important;
|
||||
}
|
||||
}
|
||||
@media @tablet {
|
||||
.date {
|
||||
width: 100% !important;
|
||||
}
|
||||
.form-cols {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
}
|
||||
i .max-width {
|
||||
max-width: @body-maxwidth !important;
|
||||
}
|
||||
|
||||
.datasec {
|
||||
display: flex;
|
||||
align-items: center !important;
|
||||
.link {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end !important;
|
||||
text-decoration: underline;
|
||||
margin-right: 3px;
|
||||
color: rgb(14, 91, 146);
|
||||
}
|
||||
}
|
||||
|
||||
.additional {
|
||||
display: flex;
|
||||
@media @mobile {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
@media @tablet {
|
||||
flex-direction: row !important;
|
||||
}
|
||||
gap: 2.5rem;
|
||||
div {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
}
|
||||
.submit-request {
|
||||
flex: 1;
|
||||
box-shadow: 0 0 25px 10px var(--opposite-bg-color-5);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
background-color: var(--heading-font-color);
|
||||
color: var(--background-color);
|
||||
padding: 10px 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
351
frontend/src/lib/components/pagebuilder/form/MobileForm.svelte
Normal file
351
frontend/src/lib/components/pagebuilder/form/MobileForm.svelte
Normal file
@@ -0,0 +1,351 @@
|
||||
<script lang="ts">
|
||||
import FormLabelNumberBlock from "./FormLabelNumberBlock.svelte"
|
||||
import type { Writable } from "svelte/store"
|
||||
import CheckboxGroup from "./CheckboxGroup.svelte"
|
||||
import Datepicker from "./Datepicker.svelte"
|
||||
import Select from "./Select.svelte"
|
||||
export let formRow: FormRow
|
||||
export let formValues: Writable<FormValues>
|
||||
export let index: number
|
||||
function removeInvalid(e: Event) {
|
||||
let element = e.currentTarget as HTMLElement
|
||||
if (element) element.classList.remove("invalid")
|
||||
}
|
||||
|
||||
function getPosition(column: FormColumn, pos: number, i = 0) {
|
||||
let position = 0
|
||||
if (pos == 0) return
|
||||
if (column.showLabelNumber) position++
|
||||
if (pos == 1) return position
|
||||
if (column.showTimes) position++
|
||||
if (pos == 2) return position
|
||||
|
||||
if (column.showSelect) position++
|
||||
if (pos == 3) return position
|
||||
if (column.showDate) position++
|
||||
if (pos == 4) return position
|
||||
if (column.showNumber) position++
|
||||
return position + i
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="form-row">
|
||||
<h3 style="margin-bottom: -0.5rem;">
|
||||
{#if formRow.showRowName}{formRow.rowName || ""}{/if}
|
||||
</h3>
|
||||
<div class="form-cols mobile-fields">
|
||||
{#each formRow.columns as column, columnIndex}
|
||||
<h3>{column?.title ?? ""}</h3>
|
||||
{#if column?.annotation}
|
||||
<p>{column?.annotation}</p>
|
||||
{/if}
|
||||
{#if column.showLabelNumber}
|
||||
<div class="{`column-${columnIndex} position-${getPosition(column, 0)}`}">
|
||||
<FormLabelNumberBlock {column} {formValues} rowIndex="{index}" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if column?.showTimes}
|
||||
<div class="column-{columnIndex} position-{getPosition(column, 1)}">
|
||||
<label bind:this="{$formValues[`times_${column?.title ? column.title + '_' : ''}label`]}">
|
||||
<select
|
||||
id="time-select"
|
||||
bind:this="{$formValues[
|
||||
`times_${column?.title ?? 'Zeiten'}_${column.timesfieldOrder}_${index}_${
|
||||
column.emailNameTimes || columnIndex + 'invalidtimes'
|
||||
}`
|
||||
]}"
|
||||
required="{column?.timeNotRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
>
|
||||
<option value="" disabled selected>Bitte Uhrzeit wählen</option>
|
||||
{#each column?.times ?? [] as time}
|
||||
<option value="{time?.timeFrom}-{time?.timeTo}">
|
||||
{time?.timeFrom} - {time?.timeTo}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if column?.showSelect}
|
||||
<div class="column-{columnIndex} position-{getPosition(column, 2)}">
|
||||
<label bind:this="{$formValues[`select_${column?.title ? column.title + '_' : ''}label`]}">
|
||||
<select
|
||||
required="{column?.dateNotRequired !== true}"
|
||||
bind:this="{$formValues[
|
||||
`select_${column?.title ?? 'Auswahl'}_${column.datefieldOrder}_${index}_${
|
||||
column?.emailNameTime || columnIndex + 'invalidtime'
|
||||
}`
|
||||
]}"
|
||||
on:change="{removeInvalid}"
|
||||
>
|
||||
<option value="" disabled selected>{column.selectTitle}</option>
|
||||
{#each column?.selectEntries as entry}
|
||||
<option value="{entry?.leftSide}-{entry?.rightSide}">
|
||||
{entry?.leftSide}-{entry?.rightSide}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if column.showDate}
|
||||
<div class="column-{columnIndex} position-{getPosition(column, 3)}">
|
||||
<label bind:this="{$formValues[`date_${column?.title ? column.title + '_' : ''}label`]}">
|
||||
<input
|
||||
class="date"
|
||||
type="date"
|
||||
required="{column?.dateNotRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
bind:this="{$formValues[
|
||||
`date_${column?.title ?? 'Datum'}_${column.datefieldOrder}_${index}_${
|
||||
column?.emailNameDate || columnIndex + 'invaliddate'
|
||||
}`
|
||||
]}"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
{#if column.showNumber}
|
||||
<div class=" column-{columnIndex} position-{getPosition(column, 4)}">
|
||||
<label
|
||||
bind:this="{$formValues[
|
||||
`input_${column.title ? column.title + '_' : ''}${column?.numberPlaceholder}_label`
|
||||
]}"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
step="any"
|
||||
required="{column?.numberNotRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
placeholder="{column?.numberPlaceholder}"
|
||||
bind:this="{$formValues[
|
||||
`input_${column.title ? column.title + '_' : ''}${column?.numberPlaceholder}_${
|
||||
column.numberfieldOrder
|
||||
}_${index}_${column?.emailNameNumber || columnIndex + 'invalidnumber'}`
|
||||
]}"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
{#if column.showCheckboxGroup}
|
||||
<CheckboxGroup
|
||||
checkboxes="{column.checkboxes}"
|
||||
groupTitle="{column.groupTitle}"
|
||||
{formValues}
|
||||
rowNr="{index}"
|
||||
/>
|
||||
{/if}
|
||||
{#if column.showDatePicker}
|
||||
<Datepicker
|
||||
datePickerProps="{column.datePickerProps}"
|
||||
groupTitle="{column.groupTitle}"
|
||||
{formValues}
|
||||
rowNr="{index}"
|
||||
formCol="{column}"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if column.showMultiSelect}
|
||||
<Select
|
||||
groupTitle="{column.groupTitle}"
|
||||
{formValues}
|
||||
rowNr="{index}"
|
||||
formCol="{column}"
|
||||
options="{column.multiSelectOptions}"
|
||||
/>
|
||||
{/if}
|
||||
{#each column.text ?? [] as textField, textFieldIndex}
|
||||
<div class="column-{columnIndex} position-{getPosition(column, 5 + textFieldIndex, textFieldIndex)}">
|
||||
<h3 class="textTitle">{textField.textTitle || ""}</h3>
|
||||
<div
|
||||
class="column-{columnIndex} position-{getPosition(column, 5 + textFieldIndex, textFieldIndex)}"
|
||||
>
|
||||
{#if textField?.textArea}
|
||||
<label bind:this="{$formValues[`textarea_Nachricht_label`]}">
|
||||
<textarea
|
||||
placeholder="{textField?.textPlaceholder}"
|
||||
required="{textField?.notRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
bind:this="{$formValues[
|
||||
`textarea_Nachricht_${textField.textfieldOrder}_${index}_${
|
||||
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
|
||||
}`
|
||||
]}"
|
||||
></textarea>
|
||||
</label>
|
||||
{:else}
|
||||
<label
|
||||
bind:this="{$formValues[
|
||||
`input_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_label`
|
||||
]}"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="{textField?.textPlaceholder}"
|
||||
on:change="{removeInvalid}"
|
||||
required="{textField?.notRequired !== true}"
|
||||
bind:this="{$formValues[
|
||||
`${
|
||||
textField?.telValidation
|
||||
? 'Telefon'
|
||||
: textField?.emailValidation
|
||||
? 'Email'
|
||||
: 'input'
|
||||
}_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_${
|
||||
textField.textfieldOrder
|
||||
}_${index}_${
|
||||
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
|
||||
}`
|
||||
]}"
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
.mobile-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
/* Order for first column */
|
||||
.column-0.position-0 {
|
||||
order: 1;
|
||||
}
|
||||
.column-0.position-1 {
|
||||
order: 5;
|
||||
}
|
||||
.column-0.position-2 {
|
||||
order: 9;
|
||||
}
|
||||
.column-0.position-3 {
|
||||
order: 13;
|
||||
}
|
||||
.column-0.position-4 {
|
||||
order: 17;
|
||||
}
|
||||
.column-0.position-5 {
|
||||
order: 21;
|
||||
}
|
||||
.column-0.position-6 {
|
||||
order: 25;
|
||||
}
|
||||
.column-0.position-7 {
|
||||
order: 29;
|
||||
}
|
||||
.column-0.position-8 {
|
||||
order: 33;
|
||||
}
|
||||
.column-0.position-9 {
|
||||
order: 37;
|
||||
}
|
||||
|
||||
/* Order for second column */
|
||||
.column-1.position-0 {
|
||||
order: 2;
|
||||
}
|
||||
.column-1.position-1 {
|
||||
order: 6;
|
||||
}
|
||||
.column-1.position-2 {
|
||||
order: 10;
|
||||
}
|
||||
.column-1.position-3 {
|
||||
order: 14;
|
||||
}
|
||||
.column-1.position-4 {
|
||||
order: 18;
|
||||
}
|
||||
.column-1.position-5 {
|
||||
order: 22;
|
||||
}
|
||||
.column-1.position-6 {
|
||||
order: 26;
|
||||
}
|
||||
.column-1.position-7 {
|
||||
order: 30;
|
||||
}
|
||||
.column-1.position-8 {
|
||||
order: 34;
|
||||
}
|
||||
.column-1.position-9 {
|
||||
order: 38;
|
||||
}
|
||||
|
||||
/* Order for third column */
|
||||
.column-2.position-0 {
|
||||
order: 3;
|
||||
}
|
||||
.column-2.position-1 {
|
||||
order: 7;
|
||||
}
|
||||
.column-2.position-2 {
|
||||
order: 11;
|
||||
}
|
||||
.column-2.position-3 {
|
||||
order: 15;
|
||||
}
|
||||
.column-2.position-4 {
|
||||
order: 19;
|
||||
}
|
||||
.column-2.position-5 {
|
||||
order: 23;
|
||||
}
|
||||
.column-2.position-6 {
|
||||
order: 27;
|
||||
}
|
||||
.column-2.position-7 {
|
||||
order: 31;
|
||||
}
|
||||
.column-2.position-8 {
|
||||
order: 35;
|
||||
}
|
||||
.column-2.position-9 {
|
||||
order: 39;
|
||||
}
|
||||
|
||||
/* Order for fourth column */
|
||||
.column-3.position-0 {
|
||||
order: 4;
|
||||
}
|
||||
.column-3.position-1 {
|
||||
order: 8;
|
||||
}
|
||||
.column-3.position-2 {
|
||||
order: 12;
|
||||
}
|
||||
.column-3.position-3 {
|
||||
order: 16;
|
||||
}
|
||||
.column-3.position-4 {
|
||||
order: 20;
|
||||
}
|
||||
.column-3.position-5 {
|
||||
order: 24;
|
||||
}
|
||||
.column-3.position-6 {
|
||||
order: 28;
|
||||
}
|
||||
.column-3.position-7 {
|
||||
order: 32;
|
||||
}
|
||||
.column-3.position-8 {
|
||||
order: 36;
|
||||
}
|
||||
.column-3.position-9 {
|
||||
order: 40;
|
||||
}
|
||||
</style>
|
||||
84
frontend/src/lib/components/pagebuilder/form/Select.svelte
Normal file
84
frontend/src/lib/components/pagebuilder/form/Select.svelte
Normal file
@@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
import Select from "svelte-select/Select.svelte"
|
||||
import { onMount } from "svelte"
|
||||
import type { Writable } from "svelte/store"
|
||||
|
||||
export let options: MultiSelectOptions[] = []
|
||||
export let groupTitle: string
|
||||
|
||||
export let formValues: Writable<FormValues>
|
||||
export let rowNr: number
|
||||
export let formCol: FormColumn
|
||||
|
||||
let valueStorage: any[] = []
|
||||
let value: string[] = []
|
||||
let values = options.map((option) => ({ label: option.name, value: option.name }))
|
||||
let lastCustomInput: string | null = null
|
||||
|
||||
function setFormValues() {
|
||||
// @ts-ignore
|
||||
$formValues[`selectMultiple_${groupTitle}_${rowNr}_${formCol.multiSelectEmailTitle}`] = {
|
||||
value: value.toString(),
|
||||
required: !formCol.multiSelectNotRequired,
|
||||
}
|
||||
}
|
||||
|
||||
function handleInputChange(e: Event) {
|
||||
const newCustomInput = (e.target as HTMLInputElement).value
|
||||
|
||||
// Remove the last custom input if it exists
|
||||
if (lastCustomInput) {
|
||||
values = values.filter((item) => item.label !== lastCustomInput)
|
||||
}
|
||||
|
||||
// Add the new custom input
|
||||
if (newCustomInput && !values.some((item) => item.label === newCustomInput)) {
|
||||
values = [...values, { label: newCustomInput, value: newCustomInput }]
|
||||
}
|
||||
|
||||
// Update lastCustomInput
|
||||
lastCustomInput = newCustomInput
|
||||
}
|
||||
|
||||
$: {
|
||||
if (value.length) {
|
||||
setFormValues()
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
const inputEl = document.querySelector(".svelte-select input")
|
||||
if (inputEl) {
|
||||
inputEl.addEventListener("input", handleInputChange)
|
||||
}
|
||||
return () => {
|
||||
if (inputEl) {
|
||||
inputEl.removeEventListener("input", handleInputChange)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
$: if (!valueStorage) {
|
||||
valueStorage = []
|
||||
}
|
||||
$: value = valueStorage.map((item) => item.value)
|
||||
</script>
|
||||
|
||||
<Select
|
||||
bind:items="{values}"
|
||||
inputAttributes="{{ autocomplete: 'on' }}"
|
||||
placeholder=""
|
||||
showChevron="{true}"
|
||||
clearable="{true}"
|
||||
hideEmptyState="{true}"
|
||||
searchable="{true}"
|
||||
multiple="{true}"
|
||||
bind:value="{valueStorage}"
|
||||
on:clear="{() => (valueStorage = [])}"
|
||||
/>
|
||||
|
||||
<style lang="less" global>
|
||||
@import "../../../assets/css/variables.less";
|
||||
@import "../../../assets/css/svelte-select.less";
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { api } from "../../../../../api"
|
||||
|
||||
export async function sendForm(body: any) {
|
||||
let response: any
|
||||
response = await api("forms", { method: "POST" }, { formular: body })
|
||||
return response
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
export function validateFields(fieldsArray: ValueEntry[]): (string | (() => void))[][] {
|
||||
const errors = []
|
||||
let selectedGroup: number
|
||||
const numberRegex = /^[+]?([.]\d+|\d+([.]\d+)?)$/
|
||||
const emailRegex =
|
||||
/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/
|
||||
const dateRegex = /^\d{4}\-(0?[1-9]|1[012])\-(0?[1-9]|[12][0-9]|3[01])$/
|
||||
const timeRegex = /^\d{1,2}:\d{2}-\d{1,2}:\d{2}$/
|
||||
const phoneRegex = /^\s*(?:\+?(\d{1,3}))?[-. (]*(\d{3})[-. )]*(\d{3})[-. ]*(\d{4})(?: *x(\d+))?\s*$/
|
||||
|
||||
const wholeBlockInvalid = () => {
|
||||
const blockContainer = document.getElementsByClassName("blockContainer")
|
||||
|
||||
if (blockContainer.length == 0) return
|
||||
blockContainer[0].classList.add("invalidBlocks")
|
||||
}
|
||||
|
||||
const validateNumber = (value: string, field: any, element: HTMLElement) => {
|
||||
if (!numberRegex.test(`${value}`)) errors.push(["block", () => element.classList.add("border-red")])
|
||||
}
|
||||
|
||||
fieldsArray.forEach(([field, value]) => {
|
||||
if (field === "blockGroups" || (typeof field === "string" && field.includes("numberLabel"))) {
|
||||
if (!field.includes("numberLabel")) return
|
||||
// @ts-ignore
|
||||
let [elementValue, element, group, boolean] = value
|
||||
element = element as HTMLElement
|
||||
if (!elementValue) return
|
||||
if (selectedGroup !== undefined) {
|
||||
if (group !== String(selectedGroup)) errors.push(["block", wholeBlockInvalid])
|
||||
else validateNumber(elementValue, field, element)
|
||||
} else {
|
||||
//@ts-ignore
|
||||
selectedGroup = group
|
||||
validateNumber(elementValue, field, element)
|
||||
}
|
||||
return
|
||||
}
|
||||
value = value
|
||||
let required = value[1]
|
||||
value = value[0]
|
||||
if (!required && !value) return
|
||||
if (!value) {
|
||||
errors.push(["Eingabe ist erforderlich.", field])
|
||||
} else if (typeof field === "string") {
|
||||
if (field.includes("number_")) {
|
||||
if (!numberRegex.test(`${value}`)) errors.push(["Ungültiger numerischer Wert.", field])
|
||||
} else if (field.includes("agreement_") && typeof value == "boolean") {
|
||||
if (value !== true) errors.push(["Bitte das Kontrollkästchen anklicken.", field])
|
||||
} else if (field.includes("Email_") && typeof value == "string") {
|
||||
if (!emailRegex.test(value)) errors.push(["Ungültiges E-Mail-Format.", field])
|
||||
} else if (field.includes("date_") && typeof value == "string") {
|
||||
if (!dateRegex.test(value)) errors.push(["Ungültiges Datumsformat.", field])
|
||||
} else if (field.includes("times_") && typeof value == "string") {
|
||||
if (!timeRegex.test(value) && typeof value == "string") errors.push(["Ungültiges Zeitformat.", field])
|
||||
} else if (field.includes("Telefon_") && typeof value == "string") {
|
||||
if (!phoneRegex.test(value)) errors.push(["Ungültiges Telefonnummernformat.", field])
|
||||
}
|
||||
}
|
||||
})
|
||||
const blockContainer = document.getElementsByClassName("blockContainer")
|
||||
// @ts-ignore
|
||||
if (blockContainer.length && selectedGroup === undefined) {
|
||||
errors.push(["block", wholeBlockInvalid])
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
6
frontend/src/lib/functions/routes/loadContent.ts
Normal file
6
frontend/src/lib/functions/routes/loadContent.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { api } from "../../../api"
|
||||
|
||||
export async function loadContent(): Promise<Content[]> {
|
||||
let site = await api<Content[]>("content", {})
|
||||
return site.data
|
||||
}
|
||||
7
frontend/src/lib/functions/routes/loadNavigation.ts
Normal file
7
frontend/src/lib/functions/routes/loadNavigation.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { api } from "../../../api"
|
||||
|
||||
export async function loadNavigation(): Promise<Navigation[]> {
|
||||
let nav = await api<Navigation[]>("navigation", {})
|
||||
console.log(nav.data, "nav")
|
||||
return nav.data
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
import App from "./components/App.svelte"
|
||||
import App from "./App.svelte"
|
||||
|
||||
export default App
|
||||
|
||||
Reference in New Issue
Block a user