frontend: real world movement

This commit is contained in:
Alex
2025-01-16 14:23:54 +01:00
parent 66ce257198
commit 11c55a17ea
46 changed files with 1277 additions and 563 deletions

View File

@@ -1,6 +1,7 @@
<script>
import InputField from "$lib/components/InputField.svelte";
import SmallLoader from "$lib/components/SmallLoader.svelte";
import { createEventDispatcher } from "svelte";
import { applyAction, enhance } from "$app/forms";
import { receive, send } from "$lib/utils/helpers";
import { t } from "svelte-i18n";
@@ -11,57 +12,85 @@
/** @type {App.Locals['subscriptions']}*/
export let subscriptions;
/** @type {App.Locals['user']} */
const blankUser = {
id: 0,
email: "",
first_name: "",
last_name: "",
phone: "",
address: "",
zip_code: "",
city: "",
company: "",
date_of_birth: "",
notes: "",
profile_picture: "",
payment_status: 0,
status: 1,
role_id: 0,
membership: {
id: 0,
start_date: "",
end_date: "",
status: 3,
parent_member_id: 0,
subscription_model: {
id: 0,
name: "",
},
},
licence: {
id: 0,
status: 1,
licence_number: "",
issued_date: "",
expiration_date: "",
country: "",
licence_categories: [],
},
bank_account: {
id: 0,
mandate_date_signed: "",
bank: "",
account_holder_name: "",
iban: "",
bic: "",
mandate_reference: "",
},
};
/** @type {App.Locals['user'] | null} */
export let user;
if (user == null) {
user = {
id: 0,
email: "",
first_name: "",
last_name: "",
phone: "",
address: "",
zip_code: "",
city: "",
company: "",
date_of_birth: "",
notes: "",
profile_picture: "",
payment_status: 0,
status: 1,
role_id: 0,
membership: {
id: 0,
start_date: "",
end_date: "",
status: 3,
parent_member_id: 0,
subscription_model: {
id: 0,
name: "",
},
},
licence: {
id: 0,
status: 1,
licence_number: "",
issued_date: "",
expiration_date: "",
country: "",
licence_categories: [],
},
bank_account: {
id: 0,
mandate_date_signed: "",
bank: "",
account_holder_name: "",
iban: "",
bic: "",
mandate_reference: "",
},
};
/** @type {App.Locals['user'] } */
let localUser;
$: {
if (user !== undefined && !localUser) {
localUser =
user === null
? { ...blankUser }
: {
...user,
licence: user.licence || blankUser.licence,
membership: user.membership || blankUser.membership,
bank_account: user.bank_account || blankUser.bank_account,
};
}
}
$: isNewUser = user === null;
$: isLoading = user === undefined;
$: {
console.log("incomingUser:", user);
}
// Add debug logging for user
$: {
console.log("processed user:", user);
}
/** @type {App.Locals['licence_categories']} */
export let licence_categories;
@@ -91,6 +120,7 @@
{ value: 5, label: $t("userStatus.5"), color: "#FF4646" }, // Red for "Deaktiviert"
];
const dispatch = createEventDispatcher();
const TABS = ["profile", "licence", "membership", "bankaccount"];
let activeTab = TABS[0];
@@ -106,7 +136,7 @@
}));
$: selectedSubscriptionModel =
subscriptions.find(
(sub) => sub?.id === user.membership?.subscription_model.id
(sub) => sub?.id === localUser.membership?.subscription_model.id
) || null;
/**
@@ -142,374 +172,386 @@
}
/** @type {import('../../routes/auth/about/[id]/$types').SubmitFunction} */
const handleUpdate = async ({ form, formData, action, cancel }) => {
const handleUpdate = async ({ formData, action, cancel }) => {
isUpdating = true;
return async ({ result }) => {
isUpdating = false;
if (result.type === "success" || result.type === "redirect") {
close();
} else {
document
.querySelector(".modal .container")
?.scrollTo({ top: 0, behavior: "smooth" });
}
await applyAction(result);
};
};
</script>
<form
class="content"
action="?/updateUser"
method="POST"
use:enhance={handleUpdate}
>
<input name="id" type="number" hidden bind:value={user.id} />
<h1 class="step-title" style="text-align: center;">{$t("user.edit")}</h1>
{#if form?.success}
<h4
class="step-subtitle warning"
in:receive={{ key: Math.floor(Math.random() * 100) }}
out:send={{ key: Math.floor(Math.random() * 100) }}
>
Um einen fehlerhaften upload Ihres Bildes zu vermeiden, clicke bitte auf
den "Update" Button unten.
</h4>
{/if}
{#if form?.errors}
{#each form?.errors as error (error.id)}
{#if isLoading}
<SmallLoader width={30} message={$t("loading.user_data")} />
{:else if localUser}
<form
class="content"
action="?/updateUser"
method="POST"
use:enhance={handleUpdate}
>
<input name="user[id]" type="hidden" bind:value={localUser.id} />
<h1 class="step-title" style="text-align: center;">{$t("user.edit")}</h1>
{#if form?.success}
<h4
class="step-subtitle warning"
in:receive={{ key: error.id }}
out:send={{ key: error.id }}
in:receive|global={{ key: Math.floor(Math.random() * 100) }}
out:send|global={{ key: Math.floor(Math.random() * 100) }}
>
{$t(error.field) + ": " + $t(error.key)}
Um einen fehlerhaften upload Ihres Bildes zu vermeiden, clicke bitte auf
den "Update" Button unten.
</h4>
{/each}
{/if}
{/if}
{#if form?.errors}
{#each form?.errors as error (error.id)}
<h4
class="step-subtitle warning"
in:receive|global={{ key: error.id }}
out:send|global={{ key: error.id }}
>
{$t(error.field) + ": " + $t(error.key)}
</h4>
{/each}
{/if}
<input
type="hidden"
hidden
name="profile_picture"
bind:value={user.profile_picture}
/>
<div class="button-container">
{#each TABS as tab}
<button
type="button"
class="button-dark"
class:active={activeTab === tab}
on:click={() => setActiveTab(tab)}
>
{$t(tab)}
</button>
{/each}
</div>
<div
class="tab-content"
style="display: {activeTab === 'profile' ? 'block' : 'none'}"
>
<InputField
name="status"
type="select"
label={$t("status")}
bind:value={user.status}
options={userStatusOptions}
<input
type="hidden"
hidden
name="user[profile_picture]"
bind:value={localUser.profile_picture}
/>
{#if user.role_id === 8}
<div class="button-container">
{#each TABS as tab}
<button
type="button"
class="button-dark"
class:active={activeTab === tab}
on:click={() => setActiveTab(tab)}
>
{$t(tab)}
</button>
{/each}
</div>
<div
class="tab-content"
style="display: {activeTab === 'profile' ? 'block' : 'none'}"
>
<InputField
name="role_id"
name="user[status]"
type="select"
label={$t("user.role")}
bind:value={user.role_id}
options={userRoleOptions}
label={$t("status")}
bind:value={localUser.status}
options={userStatusOptions}
/>
{/if}
<InputField
name="password"
type="password"
label={$t("password")}
placeholder={$t("placeholder.password")}
bind:value={password}
otherPasswordValue={password2}
/>
<InputField
name="password2"
type="password"
label={$t("password_repeat")}
placeholder={$t("placeholder.password")}
bind:value={password2}
otherPasswordValue={password}
/>
<InputField
name="first_name"
label={$t("first_name")}
bind:value={user.first_name}
placeholder={$t("placeholder.first_name")}
required={true}
/>
<InputField
name="last_name"
label={$t("last_name")}
bind:value={user.last_name}
placeholder={$t("placeholder.last_name")}
required={true}
/>
<InputField
name="company"
label={$t("company")}
bind:value={user.company}
placeholder={$t("placeholder.company")}
/>
<InputField
name="email"
type="email"
label={$t("email")}
bind:value={user.email}
placeholder={$t("placeholder.email")}
required={true}
/>
<InputField
name="phone"
type="tel"
label={$t("phone")}
bind:value={user.phone}
placeholder={$t("placeholder.phone")}
/>
<InputField
name="birth_date"
type="date"
label={$t("birth_date")}
bind:value={user.date_of_birth}
placeholder={$t("placeholder.birth_date")}
/>
<InputField
name="address"
label={$t("address")}
bind:value={user.address}
placeholder={$t("placeholder.address")}
/>
<InputField
name="zip_code"
label={$t("zip_code")}
bind:value={user.zip_code}
placeholder={$t("placeholder.zip_code")}
/>
<InputField
name="city"
label={$t("city")}
bind:value={user.city}
placeholder={$t("placeholder.city")}
/>
<InputField
name="notes"
type="textarea"
label={$t("notes")}
bind:value={user.notes}
placeholder={$t("placeholder.notes", {
values: { name: user.first_name || "" },
})}
rows={10}
/>
</div>
<div
class="tab-content"
style="display: {activeTab === 'licence' ? 'block' : 'none'}"
>
<InputField
name="licence_status"
type="select"
label={$t("status")}
bind:value={user.licence.status}
options={licenceStatusOptions}
/>
<InputField
name="licence_number"
type="text"
label={$t("licence_number")}
bind:value={user.licence.licence_number}
placeholder={$t("placeholder.licence_number")}
toUpperCase={true}
/>
<InputField
name="issued_date"
type="date"
label={$t("issued_date")}
bind:value={user.licence.issued_date}
placeholder={$t("placeholder.issued_date")}
/>
<InputField
name="expiration_date"
type="date"
label={$t("expiration_date")}
bind:value={user.licence.expiration_date}
placeholder={$t("placeholder.expiration_date")}
/>
<InputField
name="country"
label={$t("country")}
bind:value={user.licence.country}
placeholder={$t("placeholder.issuing_country")}
/>
<div class="licence-categories">
<h3>{$t("licence_categories")}</h3>
<div class="checkbox-grid">
{#each Object.entries(groupedCategories) as [group, categories], groupIndex}
{#if groupIndex > 0}
<div class="category-break" />
{/if}
{#each categories as category}
<div class="checkbox-item">
<div class="checkbox-label-container">
<InputField
type="checkbox"
name="licence_categories[]"
value={JSON.stringify(category)}
label={category.category}
checked={user.licence.licence_categories != null &&
user.licence.licence_categories.some(
(cat) => cat.category === category.category
)}
/>
{#if localUser.role_id === 8}
<InputField
name="user[role_id]"
type="select"
label={$t("user.role")}
bind:value={localUser.role_id}
options={userRoleOptions}
/>
{/if}
<InputField
name="user[password]"
type="password"
label={$t("password")}
placeholder={$t("placeholder.password")}
bind:value={password}
otherPasswordValue={password2}
/>
<InputField
name="password2"
type="password"
label={$t("password_repeat")}
placeholder={$t("placeholder.password")}
bind:value={password2}
otherPasswordValue={password}
/>
<InputField
name="user[first_name]"
label={$t("first_name")}
bind:value={localUser.first_name}
placeholder={$t("placeholder.first_name")}
required={true}
/>
<InputField
name="user[last_name]"
label={$t("last_name")}
bind:value={localUser.last_name}
placeholder={$t("placeholder.last_name")}
required={true}
/>
<InputField
name="user[company]"
label={$t("company")}
bind:value={localUser.company}
placeholder={$t("placeholder.company")}
/>
<InputField
name="user[email]"
type="email"
label={$t("email")}
bind:value={localUser.email}
placeholder={$t("placeholder.email")}
required={true}
/>
<InputField
name="user[phone]"
type="tel"
label={$t("phone")}
bind:value={localUser.phone}
placeholder={$t("placeholder.phone")}
/>
<InputField
name="user[date_of_birth]"
type="date"
label={$t("date_of_birth")}
bind:value={localUser.date_of_birth}
placeholder={$t("placeholder.date_of_birth")}
/>
<InputField
name="user[address]"
label={$t("address")}
bind:value={localUser.address}
placeholder={$t("placeholder.address")}
/>
<InputField
name="user[zip_code]"
label={$t("zip_code")}
bind:value={localUser.zip_code}
placeholder={$t("placeholder.zip_code")}
/>
<InputField
name="user[city]"
label={$t("city")}
bind:value={localUser.city}
placeholder={$t("placeholder.city")}
/>
<InputField
name="user[notes]"
type="textarea"
label={$t("notes")}
bind:value={localUser.notes}
placeholder={$t("placeholder.notes", {
values: { name: localUser.first_name || "" },
})}
rows={10}
/>
</div>
<div
class="tab-content"
style="display: {activeTab === 'licence' ? 'block' : 'none'}"
>
<InputField
name="user[licence][status]"
type="select"
label={$t("status")}
bind:value={localUser.licence.status}
options={licenceStatusOptions}
/>
<InputField
name="user[licence][number]"
type="text"
label={$t("licence_number")}
bind:value={localUser.licence.licence_number}
placeholder={$t("placeholder.licence_number")}
toUpperCase={true}
/>
<InputField
name="user[licence][issued_date]"
type="date"
label={$t("issued_date")}
bind:value={localUser.licence.issued_date}
placeholder={$t("placeholder.issued_date")}
/>
<InputField
name="user[licence][expiration_date]"
type="date"
label={$t("expiration_date")}
bind:value={localUser.licence.expiration_date}
placeholder={$t("placeholder.expiration_date")}
/>
<InputField
name="user[licence][country]"
label={$t("country")}
bind:value={localUser.licence.country}
placeholder={$t("placeholder.issuing_country")}
/>
<div class="licence-categories">
<h3>{$t("licence_categories")}</h3>
<div class="checkbox-grid">
{#each Object.entries(groupedCategories) as [group, categories], groupIndex}
{#if groupIndex > 0}
<div class="category-break" />
{/if}
{#each categories as category}
<div class="checkbox-item">
<div class="checkbox-label-container">
<InputField
type="checkbox"
name="user[licence][categories[]]"
value={JSON.stringify(category)}
label={category.category}
checked={localUser.licence.licence_categories != null &&
localUser.licence.licence_categories.some(
(cat) => cat.category === category.category
)}
/>
</div>
<span class="checkbox-description">
{$t(`licenceCategory.${category.category}`)}
</span>
</div>
<span class="checkbox-description">
{$t(`licenceCategory.${category.category}`)}
</span>
</div>
{/each}
{/each}
{/each}
</div>
</div>
</div>
</div>
<div
class="tab-content"
style="display: {activeTab === 'membership' ? 'block' : 'none'}"
>
<InputField
name="membership_status"
type="select"
label={$t("status")}
bind:value={user.membership.status}
options={membershipStatusOptions}
/>
<InputField
name="subscription_model_name"
type="select"
label={$t("subscription_model")}
bind:value={user.membership.subscription_model.name}
options={subscriptionModelOptions}
/>
<div class="subscription-info">
<div class="subscription-column">
<p>
<strong>{$t("monthly_fee")}:</strong>
{selectedSubscriptionModel?.monthly_fee || "-"}
</p>
<p>
<strong>{$t("hourly_rate")}:</strong>
{selectedSubscriptionModel?.hourly_rate || "-"}
</p>
{#if selectedSubscriptionModel?.included_hours_per_year}
<div
class="tab-content"
style="display: {activeTab === 'membership' ? 'block' : 'none'}"
>
<InputField
name="user[membership][status]"
type="select"
label={$t("status")}
bind:value={localUser.membership.status}
options={membershipStatusOptions}
/>
<InputField
name="user[membership][subscription_model][name]"
type="select"
label={$t("subscription_model")}
bind:value={localUser.membership.subscription_model.name}
options={subscriptionModelOptions}
/>
<div class="subscription-info">
<div class="subscription-column">
<p>
<strong>{$t("included_hours_per_year")}:</strong>
{selectedSubscriptionModel?.included_hours_per_year}
<strong>{$t("monthly_fee")}:</strong>
{selectedSubscriptionModel?.monthly_fee || "-"}
</p>
{/if}
{#if selectedSubscriptionModel?.included_hours_per_month}
<p>
<strong>{$t("included_hours_per_month")}:</strong>
{selectedSubscriptionModel?.included_hours_per_month}
<strong>{$t("hourly_rate")}:</strong>
{selectedSubscriptionModel?.hourly_rate || "-"}
</p>
{/if}
</div>
<div class="subscription-column">
<p>
<strong>{$t("details")}:</strong>
{selectedSubscriptionModel?.details || "-"}
</p>
{#if selectedSubscriptionModel?.conditions}
<p>
<strong>{$t("conditions")}:</strong>
{selectedSubscriptionModel?.conditions}
</p>
{/if}
{#if selectedSubscriptionModel?.included_hours_per_year}
<p>
<strong>{$t("included_hours_per_year")}:</strong>
{selectedSubscriptionModel?.included_hours_per_year}
</p>
{/if}
{#if selectedSubscriptionModel?.included_hours_per_month}
<p>
<strong>{$t("included_hours_per_month")}:</strong>
{selectedSubscriptionModel?.included_hours_per_month}
</p>
{/if}
</div>
<div class="subscription-column">
<p>
<strong>{$t("details")}:</strong>
{selectedSubscriptionModel?.details || "-"}
</p>
{#if selectedSubscriptionModel?.conditions}
<p>
<strong>{$t("conditions")}:</strong>
{selectedSubscriptionModel?.conditions}
</p>
{/if}
</div>
</div>
<InputField
name="user[membership][start_date]"
type="date"
label={$t("start")}
bind:value={localUser.membership.start_date}
placeholder={$t("placeholder.start_date")}
/>
<InputField
name="user[membership][end_date]"
type="date"
label={$t("end")}
bind:value={localUser.membership.end_date}
placeholder={$t("placeholder.end_date")}
/>
<InputField
name="user[membership][parent_member_id]"
type="number"
label={$t("parent_member_id")}
bind:value={localUser.membership.parent_member_id}
placeholder={$t("placeholder.parent_member_id")}
/>
</div>
<InputField
name="membership_start_date"
type="date"
label={$t("start")}
bind:value={user.membership.start_date}
placeholder={$t("placeholder.start_date")}
/>
<InputField
name="membership_end_date"
type="date"
label={$t("end")}
bind:value={user.membership.end_date}
placeholder={$t("placeholder.end_date")}
/>
<InputField
name="parent_member_id"
type="number"
label={$t("parent_member_id")}
bind:value={user.membership.parent_member_id}
placeholder={$t("placeholder.parent_member_id")}
/>
</div>
<div
class="tab-content"
style="display: {activeTab === 'bankaccount' ? 'block' : 'none'}"
>
<InputField
name="account_holder_name"
label={$t("bank_account_holder")}
bind:value={user.bank_account.account_holder_name}
placeholder={$t("placeholder.bank_account_holder")}
/>
<InputField
name="bank"
label={$t("bank_name")}
bind:value={user.bank_account.bank}
placeholder={$t("placeholder.bank_name")}
/>
<InputField
name="iban"
label={$t("iban")}
bind:value={user.bank_account.iban}
placeholder={$t("placeholder.iban")}
toUpperCase={true}
/>
<InputField
name="bic"
label={$t("bic")}
bind:value={user.bank_account.bic}
placeholder={$t("placeholder.bic")}
toUpperCase={true}
/>
<InputField
name="mandate_reference"
label={$t("mandate_reference")}
bind:value={user.bank_account.mandate_reference}
placeholder={$t("placeholder.mandate_reference")}
/>
<InputField
name="mandate_date_signed"
label={$t("mandate_date_signed")}
type="date"
bind:value={user.bank_account.mandate_date_signed}
readonly={true}
/>
</div>
<div class="button-container">
{#if isUpdating}
<SmallLoader width={30} message={"Aktualisiere..."} />
{:else}
<button type="button" class="button-dark" on:click={close}>
{$t("cancel")}</button
>
<button type="submit" class="button-dark">{$t("confirm")}</button>
{/if}
</div>
</form>
<div
class="tab-content"
style="display: {activeTab === 'bankaccount' ? 'block' : 'none'}"
>
<InputField
name="user[bank_account][account_holder_name]"
label={$t("bank_account_holder")}
bind:value={localUser.bank_account.account_holder_name}
placeholder={$t("placeholder.bank_account_holder")}
/>
<InputField
name="user[bank_account][bank_name]"
label={$t("bank_name")}
bind:value={localUser.bank_account.bank}
placeholder={$t("placeholder.bank_name")}
/>
<InputField
name="user[bank_account][iban]"
label={$t("iban")}
bind:value={localUser.bank_account.iban}
placeholder={$t("placeholder.iban")}
toUpperCase={true}
/>
<InputField
name="user[bank_account][bic]"
label={$t("bic")}
bind:value={localUser.bank_account.bic}
placeholder={$t("placeholder.bic")}
toUpperCase={true}
/>
<InputField
name="user[bank_account][mandate_reference]"
label={$t("mandate_reference")}
bind:value={localUser.bank_account.mandate_reference}
placeholder={$t("placeholder.mandate_reference")}
/>
<InputField
name="user[bank_account][mandate_date_signed]"
label={$t("mandate_date_signed")}
type="date"
bind:value={localUser.bank_account.mandate_date_signed}
readonly={true}
/>
</div>
<div class="button-container">
{#if isUpdating}
<SmallLoader width={30} message={"Aktualisiere..."} />
{:else}
<button
type="button"
class="button-dark"
on:click={() => dispatch("cancel")}
>
{$t("cancel")}</button
>
<button type="submit" class="button-dark">{$t("confirm")}</button>
{/if}
</div>
</form>
{/if}
<style>
.category-break {