612 lines
17 KiB
Svelte
612 lines
17 KiB
Svelte
<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 { hasPrivilige, receive, send } from '$lib/utils/helpers';
|
|
import { t } from 'svelte-i18n';
|
|
import { PERMISSIONS } from '$lib/utils/constants';
|
|
import { defaultLicence } from '$lib/utils/defaults';
|
|
|
|
/** @type {import('../../routes/auth/about/[id]/$types').ActionData} */
|
|
export let form;
|
|
|
|
/** @type {App.Locals['subscriptions']}*/
|
|
export let subscriptions;
|
|
|
|
/** @type {App.Locals['user']} */
|
|
export let user;
|
|
|
|
// Ensure licence is initialized before passing to child
|
|
$: if (user && !user.licence) {
|
|
user.licence = defaultLicence();
|
|
}
|
|
|
|
/** @type {App.Locals['user']} */
|
|
export let editor;
|
|
|
|
let readonlyUser = !hasPrivilige(editor, PERMISSIONS.Update);
|
|
|
|
// $: isNewUser = user === null;
|
|
$: isLoading = user === undefined;
|
|
|
|
/** @type {App.Locals['licence_categories']} */
|
|
export let licence_categories;
|
|
|
|
const userStatusOptions = [
|
|
{ value: 1, label: $t('userStatus.1'), color: '--subtext1' }, // Grey for "Nicht verifiziert"
|
|
{ value: 2, label: $t('userStatus.2'), color: '--light-green' }, // Light green for "Verifiziert"
|
|
{ value: 3, label: $t('userStatus.3'), color: '--green' }, // Green for "Aktiv"
|
|
{ value: 4, label: $t('userStatus.4'), color: '--pink' }, // Pink for "Passiv"
|
|
{ value: 5, label: $t('userStatus.5'), color: '--red' } // Red for "Deaktiviert"
|
|
];
|
|
const userRoleOptions = [
|
|
{ value: 0, label: $t('userRole.0'), color: '--subtext1' }, // Grey for "Nicht verifiziert"
|
|
{ value: 1, label: $t('userRole.1'), color: '--light-green' }, // Light green for "Verifiziert"
|
|
{ value: 2, label: $t('userRole.2'), color: '--green' }, // Light green for "Verifiziert"
|
|
{ value: 4, label: $t('userRole.4'), color: '--pink' }, // Green for "Aktiv"
|
|
{ value: 8, label: $t('userRole.8'), color: '--red' } // Pink for "Passiv"
|
|
];
|
|
const membershipStatusOptions = [
|
|
{ value: 3, label: $t('userStatus.3'), color: '--green' }, // Green for "Aktiv"
|
|
{ value: 4, label: $t('userStatus.4'), color: '--pink' }, // Pink for "Passiv"
|
|
{ value: 5, label: $t('userStatus.5'), color: '--red' } // Red for "Deaktiviert"
|
|
];
|
|
const licenceStatusOptions = [
|
|
{ value: 1, label: $t('userStatus.1'), color: '--subtext1' }, // Grey for "Nicht verifiziert"
|
|
{ value: 3, label: $t('userStatus.3'), color: '--green' }, // Green for "Aktiv"
|
|
{ value: 4, label: $t('userStatus.4'), color: '--pink' }, // Pink for "Passiv"
|
|
{ value: 5, label: $t('userStatus.5'), color: '--red' } // Red for "Deaktiviert"
|
|
];
|
|
|
|
const dispatch = createEventDispatcher();
|
|
const TABS = hasPrivilige(user, PERMISSIONS.Member)
|
|
? ['profile', 'licence', 'membership', 'bankaccount']
|
|
: ['profile', 'bankaccount', 'membership'];
|
|
let activeTab = TABS[0];
|
|
|
|
let isUpdating = false,
|
|
password = '',
|
|
confirm_password = '';
|
|
|
|
/** @type {Object.<string, App.Locals['licence_categories']>} */
|
|
$: groupedCategories = groupCategories(licence_categories);
|
|
$: subscriptionModelOptions = subscriptions.map((sub) => ({
|
|
value: sub?.name ?? '',
|
|
label: sub?.name ?? ''
|
|
}));
|
|
$: selectedSubscriptionModel =
|
|
subscriptions.find((sub) => sub?.name === user.membership?.subscription_model.name) || null;
|
|
|
|
/**
|
|
* creates groups of categories depending on the first letter
|
|
* @param {App.Locals['licence_categories']} categories - the categories to sort and group
|
|
* @returns {Object.<string, App.Locals['licence_categories']>} Grouped categories
|
|
*/
|
|
function groupCategories(categories) {
|
|
return Object.entries(categories)
|
|
.sort((a, b) => a[1].category.localeCompare(b[1].category))
|
|
.reduce(
|
|
(/** @type {Object.<string, App.Locals['licence_categories']>} */ acc, [, category]) => {
|
|
const firstLetter = category.category[0];
|
|
if (!acc[firstLetter]) {
|
|
acc[firstLetter] = [];
|
|
}
|
|
acc[firstLetter].push(category);
|
|
return acc;
|
|
},
|
|
{}
|
|
);
|
|
}
|
|
|
|
/** @type {import('../../routes/auth/about/[id]/$types').SubmitFunction} */
|
|
const handleUpdate = async () => {
|
|
isUpdating = true;
|
|
return async ({ result }) => {
|
|
isUpdating = false;
|
|
if (result.type === 'success' || result.type === 'redirect') {
|
|
dispatch('close');
|
|
} else {
|
|
document.querySelector('.modal .container')?.scrollTo({ top: 0, behavior: 'smooth' });
|
|
}
|
|
await applyAction(result);
|
|
};
|
|
};
|
|
</script>
|
|
|
|
{#if isLoading}
|
|
<SmallLoader width={30} message={$t('loading.user_data')} />
|
|
{:else if user}
|
|
<form class="content" action="?/updateUser" method="POST" use:enhance={handleUpdate}>
|
|
<input name="user[id]" type="hidden" bind:value={user.id} />
|
|
<h1 class="step-title" style="text-align: center;">
|
|
{user.id ? $t('user.edit') : $t('user.create')}
|
|
</h1>
|
|
{#if form?.success}
|
|
<h4
|
|
class="step-subtitle warning"
|
|
in:receive|global={{ key: Math.floor(Math.random() * 100) }}
|
|
out:send|global={{ 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)}
|
|
<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}
|
|
|
|
<div class="button-container">
|
|
{#each TABS as tab}
|
|
<button
|
|
type="button"
|
|
class="button-dark"
|
|
class:active={activeTab === tab}
|
|
on:click={() => (activeTab = tab)}
|
|
>
|
|
{$t(tab)}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
<div class="tab-content" style="display: {activeTab === 'profile' ? 'block' : 'none'}">
|
|
<InputField
|
|
name="user[status]"
|
|
type="select"
|
|
label={$t('status')}
|
|
bind:value={user.status}
|
|
options={userStatusOptions}
|
|
readonly={readonlyUser}
|
|
/>
|
|
{#if hasPrivilige(editor, PERMISSIONS.Super)}
|
|
<InputField
|
|
name="user[role_id]"
|
|
type="select"
|
|
label={$t('user.role')}
|
|
bind:value={user.role_id}
|
|
options={userRoleOptions}
|
|
/>
|
|
{/if}
|
|
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
|
<InputField
|
|
name="user[password]"
|
|
type="password"
|
|
label={$t('password')}
|
|
placeholder={$t('placeholder.password')}
|
|
bind:value={password}
|
|
otherPasswordValue={confirm_password}
|
|
/>
|
|
<InputField
|
|
name="confirm_password"
|
|
type="password"
|
|
label={$t('confirm_password')}
|
|
placeholder={$t('placeholder.password')}
|
|
bind:value={confirm_password}
|
|
otherPasswordValue={password}
|
|
/>
|
|
{/if}
|
|
<InputField
|
|
name="user[first_name]"
|
|
label={$t('user.first_name')}
|
|
bind:value={user.first_name}
|
|
placeholder={$t('placeholder.first_name')}
|
|
required={true}
|
|
readonly={readonlyUser}
|
|
/>
|
|
<InputField
|
|
name="user[last_name]"
|
|
label={$t('user.last_name')}
|
|
bind:value={user.last_name}
|
|
placeholder={$t('placeholder.last_name')}
|
|
required={true}
|
|
readonly={readonlyUser}
|
|
/>
|
|
<InputField
|
|
name="user[company]"
|
|
label={$t('company')}
|
|
bind:value={user.company}
|
|
placeholder={$t('placeholder.company')}
|
|
/>
|
|
<InputField
|
|
name="user[email]"
|
|
type="email"
|
|
label={$t('user.email')}
|
|
bind:value={user.email}
|
|
placeholder={$t('placeholder.email')}
|
|
required={true}
|
|
/>
|
|
<InputField
|
|
name="user[phone]"
|
|
type="tel"
|
|
label={$t('user.phone')}
|
|
bind:value={user.phone}
|
|
placeholder={$t('placeholder.phone')}
|
|
/>
|
|
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
|
<InputField
|
|
name="user[dateofbirth]"
|
|
type="date"
|
|
label={$t('user.dateofbirth')}
|
|
bind:value={user.dateofbirth}
|
|
placeholder={$t('placeholder.dateofbirth')}
|
|
readonly={readonlyUser}
|
|
/>
|
|
{/if}
|
|
<InputField
|
|
name="user[address]"
|
|
label={$t('address')}
|
|
bind:value={user.address}
|
|
placeholder={$t('placeholder.address')}
|
|
/>
|
|
<InputField
|
|
name="user[zip_code]"
|
|
label={$t('zip_code')}
|
|
bind:value={user.zip_code}
|
|
placeholder={$t('placeholder.zip_code')}
|
|
/>
|
|
<InputField
|
|
name="user[city]"
|
|
label={$t('city')}
|
|
bind:value={user.city}
|
|
placeholder={$t('placeholder.city')}
|
|
/>
|
|
{#if !readonlyUser}
|
|
<InputField
|
|
name="user[notes]"
|
|
type="textarea"
|
|
label={$t('notes')}
|
|
bind:value={user.notes}
|
|
placeholder={$t('placeholder.notes', {
|
|
values: { name: user.first_name || '' }
|
|
})}
|
|
rows={10}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
|
|
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
|
<div class="tab-content" style="display: {activeTab === 'licence' ? 'block' : 'none'}">
|
|
<InputField
|
|
name="user[licence][status]"
|
|
type="select"
|
|
label={$t('status')}
|
|
bind:value={user.licence.status}
|
|
options={licenceStatusOptions}
|
|
readonly={readonlyUser}
|
|
/>
|
|
<InputField
|
|
name="user[licence][number]"
|
|
type="text"
|
|
label={$t('licence_number')}
|
|
bind:value={user.licence.number}
|
|
placeholder={$t('placeholder.licence_number')}
|
|
toUpperCase={true}
|
|
readonly={readonlyUser}
|
|
/>
|
|
<InputField
|
|
name="user[licence][issued_date]"
|
|
type="date"
|
|
label={$t('issued_date')}
|
|
bind:value={user.licence.issued_date}
|
|
placeholder={$t('placeholder.issued_date')}
|
|
readonly={readonlyUser}
|
|
/>
|
|
<InputField
|
|
name="user[licence][expiration_date]"
|
|
type="date"
|
|
label={$t('expiration_date')}
|
|
bind:value={user.licence.expiration_date}
|
|
placeholder={$t('placeholder.expiration_date')}
|
|
readonly={readonlyUser}
|
|
/>
|
|
<InputField
|
|
name="user[licence][country]"
|
|
label={$t('country')}
|
|
bind:value={user.licence.country}
|
|
placeholder={$t('placeholder.issuing_country')}
|
|
readonly={readonlyUser}
|
|
/>
|
|
<div class="licence-categories">
|
|
<h3>{$t('licence_categories')}</h3>
|
|
<div class="checkbox-grid">
|
|
{#each Object.entries(groupedCategories) as [, categories], groupIndex}
|
|
{#if groupIndex > 0}
|
|
<div class="category-break"></div>
|
|
{/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={user.licence.categories != null &&
|
|
user.licence.categories.some((cat) => cat.category === category.category)}
|
|
/>
|
|
</div>
|
|
<span class="checkbox-description">
|
|
{$t(`licenceCategory.${category.category}`)}
|
|
</span>
|
|
</div>
|
|
{/each}
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
<div class="tab-content" style="display: {activeTab === 'membership' ? 'block' : 'none'}">
|
|
<InputField
|
|
name="user[membership][status]"
|
|
type="select"
|
|
label={$t('status')}
|
|
bind:value={user.membership.status}
|
|
options={membershipStatusOptions}
|
|
readonly={readonlyUser}
|
|
/>
|
|
<InputField
|
|
name="user[membership][subscription_model][name]"
|
|
type="select"
|
|
label={$t('subscription.subscription')}
|
|
bind:value={user.membership.subscription_model.name}
|
|
options={subscriptionModelOptions}
|
|
readonly={readonlyUser || !hasPrivilige(user, PERMISSIONS.Member)}
|
|
/>
|
|
<div class="subscription-info">
|
|
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
|
<div class="subscription-column">
|
|
<p>
|
|
<strong>{$t('subscription.monthly_fee')}:</strong>
|
|
{selectedSubscriptionModel?.monthly_fee || '-'} €
|
|
</p>
|
|
<p>
|
|
<strong>{$t('subscription.hourly_rate')}:</strong>
|
|
{selectedSubscriptionModel?.hourly_rate || '-'} €
|
|
</p>
|
|
{#if selectedSubscriptionModel?.included_hours_per_year}
|
|
<p>
|
|
<strong>{$t('subscription.included_hours_per_year')}:</strong>
|
|
{selectedSubscriptionModel?.included_hours_per_year}
|
|
</p>
|
|
{/if}
|
|
{#if selectedSubscriptionModel?.included_hours_per_month}
|
|
<p>
|
|
<strong>{$t('subscription.included_hours_per_month')}:</strong>
|
|
{selectedSubscriptionModel?.included_hours_per_month}
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
<div class="subscription-column">
|
|
<p>
|
|
<strong>{$t('details')}:</strong>
|
|
{selectedSubscriptionModel?.details || '-'}
|
|
</p>
|
|
{#if selectedSubscriptionModel?.conditions}
|
|
<p>
|
|
<strong>{$t('subscription.conditions')}:</strong>
|
|
{selectedSubscriptionModel?.conditions}
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
<InputField
|
|
name="user[membership][start_date]"
|
|
type="date"
|
|
label={$t('start')}
|
|
bind:value={user.membership.start_date}
|
|
placeholder={$t('placeholder.start_date')}
|
|
readonly={readonlyUser}
|
|
/>
|
|
<InputField
|
|
name="user[membership][end_date]"
|
|
type="date"
|
|
label={$t('end')}
|
|
bind:value={user.membership.end_date}
|
|
placeholder={$t('placeholder.end_date')}
|
|
readonly={readonlyUser}
|
|
/>
|
|
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
|
<InputField
|
|
name="user[membership][parent_member_id]"
|
|
type="number"
|
|
label={$t('parent_member_id')}
|
|
bind:value={user.membership.parent_member_id}
|
|
placeholder={$t('placeholder.parent_member_id')}
|
|
readonly={readonlyUser}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
<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={user.bank_account.account_holder_name}
|
|
placeholder={$t('placeholder.bank_account_holder')}
|
|
/>
|
|
<InputField
|
|
name="user[bank_account][bank_name]"
|
|
label={$t('bank_name')}
|
|
bind:value={user.bank_account.bank}
|
|
placeholder={$t('placeholder.bank_name')}
|
|
/>
|
|
<InputField
|
|
name="user[bank_account][iban]"
|
|
label={$t('iban')}
|
|
bind:value={user.bank_account.iban}
|
|
placeholder={$t('placeholder.iban')}
|
|
toUpperCase={true}
|
|
/>
|
|
<InputField
|
|
name="user[bank_account][bic]"
|
|
label={$t('bic')}
|
|
bind:value={user.bank_account.bic}
|
|
placeholder={$t('placeholder.bic')}
|
|
toUpperCase={true}
|
|
/>
|
|
<InputField
|
|
name="user[bank_account][mandate_reference]"
|
|
label={$t('mandate_reference')}
|
|
bind:value={user.bank_account.mandate_reference}
|
|
placeholder={$t('placeholder.mandate_reference')}
|
|
readonly={readonlyUser}
|
|
/>
|
|
<InputField
|
|
name="user[bank_account][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={$t('loading.updating')} />
|
|
{: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 {
|
|
grid-column: 1 / -1;
|
|
height: 1px;
|
|
background-color: var(--overlay0);
|
|
margin-top: 10px;
|
|
margin-left: 20%;
|
|
width: 60%;
|
|
opacity: 0.4;
|
|
}
|
|
|
|
.licence-categories {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.checkbox-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: 0px;
|
|
}
|
|
|
|
.checkbox-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.checkbox-label-container {
|
|
flex: 0 0 auto;
|
|
width: 4em;
|
|
margin-right: 5px;
|
|
}
|
|
|
|
.checkbox-description {
|
|
flex: 1;
|
|
font-size: 15px;
|
|
color: var(--subtext0);
|
|
margin-left: 10px;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.checkbox-grid {
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 0px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.checkbox-item {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.checkbox-description {
|
|
margin-left: 24px;
|
|
margin-top: 5px;
|
|
text-align: left;
|
|
}
|
|
}
|
|
.subscription-info {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
margin-top: 1rem;
|
|
font-size: 0.9rem;
|
|
background-color: var(--surface0);
|
|
padding: 1rem;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--surface1);
|
|
}
|
|
|
|
.subscription-column {
|
|
flex: 1;
|
|
min-width: 200px;
|
|
color: var(--text);
|
|
}
|
|
|
|
.subscription-column p {
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.subscription-column strong {
|
|
display: inline-block;
|
|
min-width: 100px;
|
|
color: var(--lavender);
|
|
}
|
|
.tab-content {
|
|
padding: 1rem;
|
|
border-radius: 0 0 3px 3px;
|
|
background-color: var(--surface0);
|
|
border: 1px solid var(--surface1);
|
|
margin-top: 1rem;
|
|
}
|
|
.button-container {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
margin-top: 1rem;
|
|
width: 100%;
|
|
}
|
|
|
|
.button-container button {
|
|
flex: 1 1 0;
|
|
min-width: 120px;
|
|
max-width: calc(50% - 5px);
|
|
background-color: var(--surface1);
|
|
color: var(--text);
|
|
border: 1px solid var(--overlay0);
|
|
transition: all 0.2s ease-in-out;
|
|
}
|
|
|
|
.button-container button:hover {
|
|
background-color: var(--surface2);
|
|
border-color: var(--lavender);
|
|
}
|
|
|
|
.button-container button.active {
|
|
background-color: var(--mauve);
|
|
border-color: var(--mauve);
|
|
color: var(--base);
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.button-container button {
|
|
flex-basis: 100%;
|
|
max-width: none;
|
|
}
|
|
}
|
|
</style>
|