restructure profile edit

This commit is contained in:
2025-04-28 23:28:09 +02:00
parent cda8d63a3f
commit 1df2af9179
9 changed files with 654 additions and 210 deletions

View File

@@ -1,22 +1,84 @@
<script lang="ts">
import { life_events, unknown, until } from '$lib/paraglide/messages';
import {
add_life_event,
description,
life_events,
unknown,
until
} from '$lib/paraglide/messages';
import type { components } from '$lib/api/api.gen';
export let draftPerson: any;
export let person_life_events: components['schemas']['PersonProperties']['life_events'];
export let editorMode = false;
export let onChange: (field: keyof components['schemas']['PersonProperties'], value: any) => void;
function updateEvent(index: number, key: 'from' | 'to' | 'description', value: string) {
if (!person_life_events) return;
person_life_events = person_life_events.map((event, i) =>
i === index ? { ...event, [key]: value } : event
);
onChange('life_events', person_life_events);
}
function addEvent() {
const newEvent = { from: '', to: '', description: '' };
person_life_events = [...(person_life_events ?? []), newEvent];
onChange('life_events', person_life_events);
}
</script>
{#if draftPerson.life_events?.length}
{#if person_life_events?.length}
<div class="divider">{life_events()}</div>
<ul class="timeline timeline-snap-start timeline-vertical">
{#each draftPerson.life_events as event}
{#each person_life_events as event, index}
<li>
<div class="timeline-start">{event.from ?? unknown()}</div>
<div class="timeline-start">
{#if editorMode}
<input
type="text"
class="input input-xs input-bordered"
value={event.from ?? ''}
on:input={(e) => updateEvent(index, 'from', e.currentTarget.value)}
placeholder={unknown().toLowerCase()}
/>
{:else}
{event.from ?? unknown().toLowerCase()}
{/if}
</div>
<div class="timeline-middle">
<div class="badge badge-primary"></div>
</div>
<div class="timeline-end">
<p>{event.description}</p>
{#if event.to}
<p class="text-sm opacity-50">{until()} {event.to}</p>
<div class="timeline-end space-y-1">
{#if editorMode}
<textarea
class="textarea textarea-xs textarea-bordered w-full"
value={event.description ?? ''}
on:input={(e) => updateEvent(index, 'description', e.currentTarget.value)}
placeholder={description()}
></textarea>
{:else}
<p>{event.description}</p>
{/if}
{#if event.to || editorMode}
<p class="text-sm opacity-50">
{until()}
{#if editorMode}
<input
type="text"
class="input input-xs input-bordered ml-1"
value={event.to ?? ''}
on:input={(e) => updateEvent(index, 'to', e.currentTarget.value)}
placeholder={unknown().toLowerCase()}
/>
{:else}
{event.to ?? unknown().toLowerCase()}
{/if}
</p>
{/if}
</div>
<hr />
@@ -24,3 +86,11 @@
{/each}
</ul>
{/if}
{#if editorMode}
<div class="mt-4 flex justify-center">
<button class="btn btn-primary btn-sm" on:click={addEvent}>
{add_life_event()}
</button>
</div>
{/if}

View File

@@ -2,27 +2,27 @@
import type { components } from '$lib/api/api.gen';
import { video, photos, upload } from '$lib/paraglide/messages';
export let draftPerson: components['schemas']['PersonProperties'];
export let editorMode = false;
export let person: components['schemas']['PersonProperties'];
export let editorMode = false;
</script>
{#if editorMode}
<button class="btn bg-neutral text-neutral-content btn-xs" on:click={() => {}}>
{upload()}
</button>
<button class="btn bg-neutral text-neutral-content btn-xs" on:click={() => {}}>
{upload()}
</button>
{/if}
{#if draftPerson.photos?.length || draftPerson.videos?.length}
{#if person.photos?.length || person.videos?.length}
<div class="divider">{photos()} & {video()}</div>
<div class="grid grid-cols-2 gap-4 md:grid-cols-4">
{#each draftPerson.photos ?? [] as picture}
{#each person.photos ?? [] as picture}
<img
src={picture.url}
alt={picture.description ?? photos()}
class="h-32 w-full rounded-lg object-cover shadow-md"
/>
{/each}
{#each draftPerson.videos ?? [] as video}
{#each person.videos ?? [] as video}
<video src={video.url} controls class="h-32 w-full rounded-lg shadow-md">
<track kind="captions" src={video.description} srcLang="en" default />
<track kind="descriptions" src={video.description} srcLang="en" default />

View File

@@ -7,17 +7,24 @@
import LifeEventsTimeline from './LifeEventsTimeline.svelte';
import OtherDetails from './OtherDetails.svelte';
import type { components } from '$lib/api/api.gen.js';
import { life_events } from '$lib/paraglide/messages';
let {
closeModal = () => {},
person = {}
}: { closeModal: () => void; person: components['schemas']['PersonProperties'] } = $props();
let editorMode = $state(false);
let draftPerson = $state({});
let draftPerson = $state({} as components['schemas']['PersonProperties']);
draftPerson = person;
editorMode = false;
function handleDraftPersonChange(
field: keyof components['schemas']['PersonProperties'],
value: any
) {
draftPerson[field] = value;
}
function close() {
closeModal();
editorMode = false;
@@ -32,7 +39,6 @@
// Save logic here
editorMode = false;
}
</script>
<div class="modal modal-open" transition:fade>
@@ -41,10 +47,13 @@
<ModalButtons {editorMode} onClose={close} onSave={save} onToggleEdit={toggleEdit} />
<div class="divider"></div>
</div>
<ProfileHeader {draftPerson} {editorMode} />
<MediaGallery {draftPerson} />
<LifeEventsTimeline {draftPerson} />
<OtherDetails {draftPerson} {editorMode} />
<ProfileHeader {person} {editorMode} onChange={handleDraftPersonChange} />
<MediaGallery {person} />
<LifeEventsTimeline
person_life_events={person.life_events}
{editorMode}
onChange={handleDraftPersonChange}
/>
<OtherDetails {person} {editorMode} onChange={handleDraftPersonChange} />
</div>
</div>

View File

@@ -1,14 +1,18 @@
<script lang="ts">
import { callMessageFunction } from '$lib/i18n';
import type { MessageKeys } from '$lib/i18n';
export let draftPerson: any;
export let editorMode = false;
import type { components } from '$lib/api/api.gen';
export let person: components['schemas']['PersonProperties'];
export let editorMode = false;
export let onChange: (field: keyof components['schemas']['PersonProperties'], value: any) => void;
const skipFields = [
'id',
'first_name',
'last_name',
'born',
'died',
'middle_name',
'biological_sex',
'email',
'limit',
@@ -17,25 +21,74 @@
'profile_picture',
'photos',
'videos',
'life_events'
'life_events',
'residence',
'medications',
'medical_conditions',
'languages',
'notes',
'audios',
'google_id'
];
</script>
<div class="mt-6 grid grid-cols-1 gap-4 md:grid-cols-2">
{#each Object.entries(draftPerson) as [key, value]}
{#if !skipFields.includes(key) && value !== undefined && value !== null}
{#each Object.entries(person) as [key, value]}
{#if !skipFields.includes(key) && ((value !== undefined && value !== null) || editorMode)}
<div>
<p>
<strong>{callMessageFunction(key as MessageKeys) || key}:</strong>
<label class="label font-semibold"
>{callMessageFunction(key as MessageKeys) || key}:
{#if editorMode}
<textarea
bind:value={draftPerson[key]}
class="textarea textarea-bordered textarea-sm w-full"
></textarea>
{#if typeof value === 'string'}
{#if value.length > 100}
<textarea
bind:value={person[key as keyof components['schemas']['PersonProperties']]}
class="textarea textarea-bordered textarea-sm w-full"
oninput={(e) =>
onChange(
key as keyof components['schemas']['PersonProperties'],
String(person[key as keyof components['schemas']['PersonProperties']])
)}
></textarea>
{:else}
<input
type="text"
class="input input-bordered input-sm w-full"
bind:value={person[key as keyof components['schemas']['PersonProperties']]}
oninput={() =>
onChange(
key as keyof components['schemas']['PersonProperties'],
String(person[key as keyof components['schemas']['PersonProperties']])
)}
/>
{/if}
{:else if typeof value === 'boolean'}
<input
type="checkbox"
class="checkbox checkbox-primary"
bind:value={person[key as keyof components['schemas']['PersonProperties']]}
onchange={(e) =>
onChange(
key as keyof components['schemas']['PersonProperties'],
Boolean(person[key as keyof components['schemas']['PersonProperties']])
)}
/>
{:else if typeof value === 'number'}
<input
type="number"
class="input input-bordered input-sm w-full"
bind:value={person[key as keyof components['schemas']['PersonProperties']]}
oninput={(e) =>
onChange(
key as keyof components['schemas']['PersonProperties'],
Number(person[key as keyof components['schemas']['PersonProperties']])
)}
/>
{/if}
{:else}
{JSON.stringify(value) ?? '-'}
<p>{value ?? '-'}</p>
{/if}
</p>
</label>
</div>
{/if}
{/each}

View File

@@ -1,18 +1,36 @@
<script lang="ts">
import { death } from './../paraglide/messages/en.js';
import { onMount } from 'svelte';
import type { components } from '$lib/api/api.gen';
import { male,female,intersex,other,change_profile_picture, biological_sex, born,died, email, first_name, id, last_name, middle_name, mothers_first_name, mothers_last_name, profile_picture } from '$lib/paraglide/messages';
import { callMessageFunction } from '$lib/i18n';
import type { MessageKeys } from '$lib/i18n';
import { onMount } from 'svelte';
import type { components } from '$lib/api/api.gen';
import {
male,
female,
intersex,
other,
change_profile_picture,
biological_sex,
born,
died,
email,
first_name,
id,
last_name,
middle_name,
mothers_first_name,
mothers_last_name,
profile_picture
} from '$lib/paraglide/messages';
import { callMessageFunction } from '$lib/i18n';
import type { MessageKeys } from '$lib/i18n';
export let draftPerson: components['schemas']['PersonProperties'] & {
id?: string,
};
export let editorMode = false;
let birth_date: HTMLInputElement;
let death_date: HTMLInputElement;
onMount(() => {
export let person: components['schemas']['PersonProperties'] & {
id?: string;
};
export let editorMode = false;
export let onChange: (field: keyof components['schemas']['PersonProperties'], value: any) => void;
let birth_date: HTMLInputElement;
let death_date: HTMLInputElement;
onMount(() => {
if (birth_date) {
import('pikaday').then(({ default: Pikaday }) => {
const picker = new Pikaday({
@@ -20,21 +38,23 @@
minDate: new Date(1900, 0, 1),
field: birth_date,
onSelect: function (date) {
birth_date.value = date.toISOString();
birth_date.value = date.toISOString().split('T')[0];
onChange('born', date.toISOString().split('T')[0]);
}
});
// Clean up when component unmounts
return () => picker.destroy();
});
}
if (death_date) {
if (death_date) {
import('pikaday').then(({ default: Pikaday }) => {
const picker = new Pikaday({
format: 'YYYY-MM-DD',
minDate: new Date(1900, 0, 1),
field: death_date,
onSelect: function (date) {
death_date.value = date.toISOString();
death_date.value = date.toISOString().split('T')[0];
onChange('died', date.toISOString().split('T')[0]);
}
});
// Clean up when component unmounts
@@ -43,62 +63,107 @@
}
});
</script>
<div class="flex flex-col md:flex-row gap-6">
<div class="flex-shrink-0 flex flex-col items-center gap-2">
<img src={draftPerson.profile_picture||'https://cdn-icons-png.flaticon.com/512/10628/10628885.png'} alt={profile_picture()} class="rounded-lg shadow-md w-48 h-48 object-cover" />
{#if editorMode}
<button class="btn bg-neutral text-neutral-content btn-xs" on:click={() => {}}>
{change_profile_picture()}
</button>
{/if}
</div>
<div class="flex-1 grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<p><strong>{first_name()}:</strong> {#if editorMode}<input bind:value={draftPerson.first_name} class="input input-sm input-bordered w-full" />{:else}{draftPerson.first_name ?? '-'}{/if}</p>
<p><strong>{last_name()}:</strong> {#if editorMode}<input bind:value={draftPerson.last_name} class="input input-sm input-bordered w-full" />{:else}{draftPerson.last_name ?? '-'}{/if}</p>
<p><strong>{middle_name()}:</strong> {#if editorMode}<input bind:value={draftPerson.middle_name} class="input input-sm input-bordered w-full" />{:else}{draftPerson.middle_name ?? '-'}{/if}</p>
<p><strong>{born()}:</strong>
<input
type="text"
class="w-full pika-single"
id="birth_date"
bind:this={birth_date}
bind:value={draftPerson.born}/>
</p>
<p><strong>{died()}:</strong>
<input
type="text"
class="w-full pika-single"
id="death_date"
placeholder={died()}
bind:this={death_date}
bind:value={draftPerson.died}
/>
</p>
<p><strong>{biological_sex()}:</strong>
{#if editorMode}
<select
name="biological_sex"
class="select select-bordered w-full select-sm"
id="biological_sex"
bind:value={draftPerson.biological_sex}
placeholder={biological_sex()}
>
<option value="male">{male()} </option>
<option value="female">{female()} </option>
<option value="intersex">{intersex()} </option>
<option value="other">{other()} </option>
</select>
{:else}{callMessageFunction(draftPerson.biological_sex as MessageKeys) ?? '-'}{/if}</p>
</div>
<div>
<p><strong>{email()}:</strong> {#if editorMode}<input bind:value={draftPerson.email} class="input input-sm input-bordered w-full" />{:else}{draftPerson.email ?? '-'}{/if}</p>
<p><strong>{mothers_first_name()}:</strong> {#if editorMode}<input bind:value={draftPerson.mothers_first_name} class="input input-sm input-bordered w-full" />{:else}{draftPerson.mothers_first_name ?? '-'}{/if}</p>
<p><strong>{mothers_last_name()}:</strong> {#if editorMode}<input bind:value={draftPerson.mothers_last_name} class="input input-sm input-bordered w-full" />{:else}{draftPerson.mothers_last_name ?? '-'}{/if}</p>
<p><strong> {id()}:</strong>{draftPerson.id ?? '-'}</p>
<p><strong> Limit:</strong>{draftPerson.limit ?? '-'}</p>
</div>
</div>
<div class="flex flex-col gap-6 md:flex-row">
<div class="flex flex-shrink-0 flex-col items-center gap-2">
<img
src={person.profile_picture || 'https://cdn-icons-png.flaticon.com/512/10628/10628885.png'}
alt={profile_picture()}
class="h-48 w-48 rounded-lg object-cover shadow-md"
/>
{#if editorMode}
<button class="btn bg-neutral text-neutral-content btn-xs" on:click={() => {}}>
{change_profile_picture()}
</button>
{/if}
</div>
<div class="grid flex-1 grid-cols-1 gap-4 md:grid-cols-2">
<div>
<p>
<strong>{first_name()}:</strong>
{#if editorMode}<input
bind:value={person.first_name}
class="input input-sm input-bordered w-full"
/>{:else}{person.first_name ?? '-'}{/if}
</p>
<p>
<strong>{last_name()}:</strong>
{#if editorMode}<input
bind:value={person.last_name}
class="input input-sm input-bordered w-full"
/>{:else}{person.last_name ?? '-'}{/if}
</p>
<p>
<strong>{middle_name()}:</strong>
{#if editorMode}<input
bind:value={person.middle_name}
class="input input-sm input-bordered w-full"
/>{:else}{person.middle_name ?? '-'}{/if}
</p>
<p>
<strong>{born()}:</strong>
{#if editorMode}<input
type="text"
class="pika-single w-full"
id="birth_date"
bind:this={birth_date}
bind:value={person.born}
/>
{:else}{person.born ?? '-'}{/if}
</p>
<p>
<strong>{died()}:</strong>
{#if editorMode}<input
type="text"
class="pika-single w-full"
id="death_date"
placeholder={died()}
bind:this={death_date}
bind:value={person.died}
/>{:else}{person.died ?? '-'}{/if}
</p>
<p>
<strong>{biological_sex()}:</strong>
{#if editorMode}
<select
name="biological_sex"
class="select select-bordered select-sm w-full"
id="biological_sex"
bind:value={person.biological_sex}
placeholder={biological_sex()}
>
<option value="male">{male()} </option>
<option value="female">{female()} </option>
<option value="intersex">{intersex()} </option>
<option value="other">{other()} </option>
</select>
{:else}{callMessageFunction(person.biological_sex as MessageKeys) ?? '-'}{/if}
</p>
</div>
<div>
<p>
<strong>{email()}:</strong>
{#if editorMode}<input
bind:value={person.email}
class="input input-sm input-bordered w-full"
/>{:else}{person.email ?? '-'}{/if}
</p>
<p>
<strong>{mothers_first_name()}:</strong>
{#if editorMode}<input
bind:value={person.mothers_first_name}
class="input input-sm input-bordered w-full"
/>{:else}{person.mothers_first_name ?? '-'}{/if}
</p>
<p>
<strong>{mothers_last_name()}:</strong>
{#if editorMode}<input
bind:value={person.mothers_last_name}
class="input input-sm input-bordered w-full"
/>{:else}{person.mothers_last_name ?? '-'}{/if}
</p>
<p><strong> {id()}:</strong>{person.id ?? '-'}</p>
<p><strong> Limit:</strong>{person.limit ?? '-'}</p>
</div>
</div>
</div>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import {
register,
create,
close,
born,
mothers_first_name,
@@ -13,42 +13,134 @@
male,
female,
other,
intersex
intersex,
create_relationship_and_person,
child,
sibling,
parent,
spouse,
relation,
relation_type,
notes,
until,
optional_field,
from_time
} from '$lib/paraglide/messages';
import { onMount } from 'svelte';
import type { components } from '$lib/api/api.gen.js';
import { validatePersonRegistration, validateFamilyRelationship } from './validate_fields';
import type { Node, Edge } from '@xyflow/svelte';
let {
closeModal = () => {},
relationship = null,
}: { closeModal : ()=>void,relationship: number | null } = $props();
onCreation = (nodes: Array<Node> | null, edges: Array<Edge> | null) => {},
onOnlyPersonCreation = (person: components['schemas']['Person']) => {},
relationshipStartID = null
}: {
closeModal: () => void;
onCreation: (newNodes: Array<Node> | null, newEdges: Array<Edge> | null) => void;
onOnlyPersonCreation: (person: components['schemas']['Person']) => void | undefined;
relationshipStartID: number | null;
} = $props();
let birth_date: HTMLInputElement;
let draftRelationship: components['schemas']['FamilyRelationship'] & {type: string} | null = {} as components['schemas']['FamilyRelationship'] & {type: string} | null;
let draftPerson :components['schemas']['PersonRegistration'] = {} as components['schemas']['PersonRegistration'];
let relationship_from_time: HTMLInputElement;
let relationship_until: HTMLInputElement;
let draftRelationship: (components['schemas']['FamilyRelationship'] & { type: string }) | null =
$state({} as components['schemas']['FamilyRelationship'] & { type: string });
let draftPerson: components['schemas']['PersonRegistration'] = $state(
{} as components['schemas']['PersonRegistration']
);
let error: string | undefined | null = $state();
function onClose() {
closeModal();
}
async function create(event: SubmitEvent) {
async function onCreate(event: SubmitEvent) {
event.preventDefault();
error = validatePersonRegistration(draftPerson);
if (error) {
return;
}
if (relationship !== null && draftRelationship !== null) {
error = validateFamilyRelationship(draftRelationship);
if (error) {
if (relationshipStartID !== null) {
if (draftRelationship !== null) {
error = validateFamilyRelationship(draftRelationship);
if (error) {
return;
}
}
let requestBody = {
relationship: draftRelationship,
type: draftRelationship!.type,
person: draftPerson
} as {
person: components['schemas']['PersonRegistration'];
type?: 'child' | 'parent' | 'spouse' | 'sibling';
relationship: components['schemas']['FamilyRelationship'];
};
let response = await fetch(`/api/person_and_relationship/${relationshipStartID}`, {
method: 'POST',
body: JSON.stringify(requestBody),
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
error = 'Error creating person and relationship';
return;
}
let data = (await response.json()) as {
person?: components['schemas']['Person'];
relationships?: components['schemas']['Relationship'][];
};
if (onCreation !== undefined) {
let edges: Array<Edge> = [];
data.relationships?.map((relationship) =>
edges.push({
id: String(relationship.id),
source: String(relationship.start),
target: String(relationship.end),
data: {
...relationship.properties
}
})
);
let newNode = {
id: String(data.person?.Id),
data: {
...data.person?.Props
},
position: { x: 0, y: 0 },
type: 'personNode'
} as Node;
onCreation([newNode], edges);
closeModal();
}
} else {
let requestBody = draftPerson as components['schemas']['PersonRegistration'];
let response = await fetch(`/api/person`, {
method: 'POST',
body: JSON.stringify(requestBody),
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
error = 'Error creating person';
return;
}
if (onOnlyPersonCreation !== undefined) {
onOnlyPersonCreation(await response.json());
}
}
const url = `/api/person`;
const response = await fetch(url);
result = await response.text();
closeModal();
}
@@ -63,7 +155,44 @@
birth_date.placeholder = '';
},
onSelect: function (date) {
birth_date.value = date.toISOString();
birth_date.value = date.toISOString().split('T')[0];
draftPerson.born = date.toISOString().split('T')[0];
}
});
// Clean up when component unmounts
return () => picker.destroy();
});
}
if (relationship_from_time) {
import('pikaday').then(({ default: Pikaday }) => {
const picker = new Pikaday({
format: 'YYYY-MM-DD',
minDate: new Date(1900, 0, 1),
field: relationship_from_time,
onOpen: function () {
relationship_from_time.placeholder = '';
},
onSelect: function (date) {
relationship_from_time.value = date.toISOString().split('T')[0];
draftRelationship.from = date.toISOString().split('T')[0];
}
});
// Clean up when component unmounts
return () => picker.destroy();
});
}
if (relationship_until) {
import('pikaday').then(({ default: Pikaday }) => {
const picker = new Pikaday({
format: 'YYYY-MM-DD',
minDate: new Date(1900, 0, 1),
field: relationship_until,
onOpen: function () {
relationship_until.placeholder = '';
},
onSelect: function (date) {
relationship_until.value = date.toISOString().split('T')[0];
draftRelationship.to = date.toISOString().split('T')[0];
}
});
// Clean up when component unmounts
@@ -74,17 +203,25 @@
</script>
<div class="modal modal-open" transition:fade>
<div class="modal-box max-h-screen w-full max-w-5xl overflow-y-auto">
<div class="bg-base-100 sticky top-0 z-10">
<button class="btn btn-error btn-sm" onclick={onClose}>
{close()}
</button>
<div class="divider"></div>
<div
class="modal-box flex max-h-screen w-full max-w-5xl flex-col items-center justify-center overflow-y-auto"
>
<div class="flex w-full max-w-5xl items-center justify-between p-2">
<h3 class="text-left text-lg font-bold">{create_relationship_and_person()}</h3>
<div>
<button class="btn btn-error btn-sm" onclick={onClose}>
{close()}
</button>
</div>
</div>
<form onsubmit={create}>
<fieldset class="fieldset">
<div class="divider"></div>
<form onsubmit={onCreate} class="w-full">
<fieldset
class="fieldset grid w-full grid-cols-1 items-center gap-y-4 md:grid-cols-2 md:gap-x-6"
>
{#if error}
<div role="alert" class="alert alert-error">
<div role="alert" class="alert alert-error col-span-full">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 shrink-0 stroke-current"
@@ -101,74 +238,144 @@
<span>{error}</span>
</div>
{/if}
{#if relationship !== undefined}
<input type="hidden" name="relationship" value={relationship} />
{#if relationshipStartID !== undefined}
<input type="hidden" name="relationshipStartID" value={relationshipStartID} />
<div class="flex flex-col">
<label class="label" for="relationship_type">{relation_type()}</label>
<select
name="relationship_type"
class="select select-bordered"
id="relationship_type"
bind:value={draftRelationship.type}
>
<option value="child">{child()}</option>
<option value="parent">{parent()}</option>
<option value="sibling">{sibling()}</option>
<option value="spouse">{spouse()}</option>
</select>
</div>
<div class="flex flex-col">
<label class="label" for="relationship_notes"
>{relation() + ' ' + notes().toLowerCase()}:</label
>
<textarea
name="relationship_notes"
class="textarea"
bind:value={draftRelationship.notes}
placeholder={notes().toLowerCase() + ' ' + optional_field().toLowerCase()}
></textarea>
</div>
<div class="flex flex-col">
<label class="label" for="from_time">{from_time()}</label>
<input
type="text"
name="from_time"
id="from_time"
class="input input-bordered validator pika-single"
placeholder={optional_field()}
bind:this={relationship_from_time}
/>
</div>
<div class="flex flex-col">
<label class="label" for="until">{until()}</label>
<input
type="text"
name="until"
id="until"
class="input input-bordered validator pika-single"
placeholder={optional_field()}
bind:this={relationship_until}
/>
</div>
<div class="divider margin-t-2 col-span-full"></div>
{/if}
<label class="fieldset-label" for="email">{email()}</label>
<input
type="email"
name="email"
class="input"
placeholder={email()}
bind:value={draftPerson.email}
/>
<label class="fieldset-label" for="first_name">{first_name()}</label>
<input
type="text"
class="input"
name="first_name"
id="first_name"
placeholder={first_name()}
/>
<label class="fieldset-label" for="last_name">{last_name()}</label>
<input
type="text"
class="input"
name="last_name"
id="last_name"
placeholder={last_name()}
/>
<label class="fieldset-label" for="birth_date">{born()}</label>
<input
type="text"
class="input pika-single"
id="birth_date"
placeholder={born()}
bind:value={draftPerson.born}
bind:this={birth_date}
/>
<label class="fieldset-label" for="biological_sex">{biological_sex()}</label>
<select
name="biological_sex"
class="select select-bordered w-full max-w-xs"
id="biological_sex"
placeholder={biological_sex()}
bind:value={draftPerson.biological_sex}
>
<option value="male">{male()} </option>
<option value="female">{female()} </option>
<option value="intersex">{intersex()} </option>
<option value="other">{other()} </option>
</select>
<label class="fieldset-label" for="mothers_last_name">{mothers_last_name()}</label>
<input
type="text"
class="input"
name="mothers_last_name"
id="mothers_last_name"
placeholder={mothers_last_name()}
bind:value={draftPerson.mothers_last_name}
/>
<label class="fieldset-label" for="mothers_first_name">{mothers_first_name()}</label>
<input
type="text"
class="input"
name="mothers_first_name"
id="mothers_first_name"
placeholder={mothers_first_name()}
bind:value={draftPerson.mothers_first_name}
/>
<button class="btn btn-neutral mt-4">{register()}</button>
<!-- Inputs -->
<div class="flex flex-col">
<label class="label" for="first_name">{first_name()}</label>
<input
type="text"
name="first_name"
class="input input-bordered"
placeholder={first_name()}
bind:value={draftPerson.first_name}
/>
</div>
<div class="flex flex-col">
<label class="label" for="last_name">{last_name()}</label>
<input
type="text"
name="last_name"
class="input input-bordered"
placeholder={last_name()}
bind:value={draftPerson.last_name}
/>
</div>
<div class="flex flex-col">
<label class="label" for="email">{email()}</label>
<input
type="email"
name="email"
class="input input-bordered validator"
placeholder={email() + ' ' + optional_field().toLowerCase()}
bind:value={draftPerson.email}
/>
</div>
<div class="flex flex-col">
<label class="label" for="birth_date">{born()}</label>
<input
type="text"
name="birth_date"
class="input input-bordered validator pika-single"
placeholder={born()}
bind:this={birth_date}
/>
</div>
<div class="flex flex-col">
<label class="label" for="biological_sex">{biological_sex()}</label>
<select
name="biological_sex"
class="select select-bordered"
id="biological_sex"
bind:value={draftPerson.biological_sex}
>
<option value="male">{male()}</option>
<option value="female">{female()}</option>
<option value="intersex">{intersex()}</option>
<option value="other">{other()}</option>
</select>
</div>
<div class="flex flex-col">
<label class="label" for="mothers_last_name">{mothers_last_name()}</label>
<input
type="text"
name="mothers_last_name"
class="input input-bordered"
placeholder={mothers_last_name()}
bind:value={draftPerson.mothers_last_name}
/>
</div>
<div class="flex flex-col">
<label class="label" for="mothers_first_name">{mothers_first_name()}</label>
<input
type="text"
name="mothers_first_name"
class="input input-bordered"
placeholder={mothers_first_name()}
bind:value={draftPerson.mothers_first_name}
/>
</div>
<!-- Submit button spans full width -->
<div class="col-span-full mt-4 flex justify-center">
<button type="submit" class="btn btn-neutral mt-4">{create()}</button>
</div>
</fieldset>
</form>
</div>

View File

@@ -6,14 +6,14 @@ export function validatePersonRegistration(
): string | null {
if (!data.first_name || data.first_name.trim() === '') {
return missing_field({
field: first_name(),
});
field: first_name()
});
}
if (!data.last_name || data.last_name.trim() === '') {
return missing_field({
field: last_name(),
});
field: last_name()
});
}
if (
@@ -37,14 +37,14 @@ export function validatePersonRegistration(
if (!data.mothers_first_name || data.mothers_first_name.trim() === '') {
return missing_field({
field: mothers_first_name(),
});
field: mothers_first_name()
});
}
if (!data.mothers_last_name || data.mothers_last_name.trim() === '') {
return missing_field({
field: 'Mother\'s last name',
});
field: "Mother's last name"
});
}
return null; // No errors

View File

@@ -0,0 +1,40 @@
<script lang="ts">
import type { components } from '$lib/api/api.gen';
export let key: keyof components['schemas']['PersonProperties'];
export let value: any;
export let editorMode = false;
export let onChange: (field: keyof components['schemas']['PersonProperties'], value: any) => void;
let numberField: HTMLInputElement;
let textField: HTMLTextAreaElement;
let checkboxField: HTMLInputElement;
</script>
{#if editorMode}
{#if typeof value === 'boolean'}
<input
type="checkbox"
class="toggle toggle-primary"
checked={value}
bind:this={checkboxField}
oninput={() => onChange(key, checkboxField.value === 'true')}
/>
{:else if typeof value === 'number'}
<input
type="number"
class="input input-bordered input-sm w-full"
{value}
bind:this={numberField}
oninput={() => onChange(key, Number(numberField.value))}
/>
{:else}
<textarea
class="textarea textarea-bordered textarea-sm w-full"
{value}
oninput={() => onChange(key, textField.value)}
bind:this={textField}
></textarea>
{/if}
{:else}
<p class="text-sm text-gray-700">{value ?? '-'}</p>
{/if}