mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-11-13 15:47:37 +00:00
Enhance PersonEdit form: Add birthdate input with validation, improve field error handling using hasValidationError, refactor birthDate to respect timezone offsets, and update translations for better user feedback. Replace DateTimeCreate with DateTimeWrite across types and components.
This commit is contained in:
@@ -158,3 +158,18 @@ export const intervalISOToDays = (str: string | null): number | null => {
|
||||
|
||||
return days;
|
||||
};
|
||||
|
||||
export function getTimezoneOffsetString(date: Date, timeZone: string): string {
|
||||
const utcDate = new Date(date.toLocaleString("en-US", { timeZone: "UTC" }));
|
||||
const tzDate = new Date(date.toLocaleString("en-US", { timeZone }));
|
||||
const offsetMinutes = (utcDate.getTime() - tzDate.getTime()) / (60 * 1000);
|
||||
|
||||
// Inverser le signe pour avoir la convention ±HH:MM
|
||||
const sign = offsetMinutes <= 0 ? "+" : "-";
|
||||
const absMinutes = Math.abs(offsetMinutes);
|
||||
const hours = String(Math.floor(absMinutes / 60)).padStart(2, "0");
|
||||
const minutes = String(absMinutes % 60).padStart(2, "0");
|
||||
|
||||
return `${sign}${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ export interface DateTime {
|
||||
}
|
||||
|
||||
/**
|
||||
* A date representation to use when we create an instance
|
||||
* A date representation to use when we create or update a date
|
||||
*/
|
||||
export interface DateTimeCreate {
|
||||
export interface DateTimeWrite {
|
||||
/**
|
||||
* Must be a string in format Y-m-d\TH:i:sO
|
||||
*/
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
Job,
|
||||
PrivateCommentEmbeddable,
|
||||
TranslatableString,
|
||||
DateTimeCreate,
|
||||
DateTimeWrite,
|
||||
SetGender,
|
||||
SetCenter,
|
||||
SetCivility,
|
||||
@@ -67,8 +67,8 @@ export interface PersonWrite {
|
||||
lastName: string;
|
||||
altNames: AltNameWrite[];
|
||||
// address: number | null;
|
||||
birthdate: DateTimeCreate | null;
|
||||
deathdate: DateTimeCreate | null;
|
||||
birthdate: DateTimeWrite | null;
|
||||
deathdate: DateTimeWrite | null;
|
||||
phonenumber: string;
|
||||
mobilenumber: string;
|
||||
email: string;
|
||||
|
||||
@@ -72,6 +72,7 @@ export interface WritePersonViolationMap
|
||||
civility: {
|
||||
"{{ value }}": string | null;
|
||||
};
|
||||
birthdate: {};
|
||||
}
|
||||
export const createPerson = async (person: PersonWrite): Promise<Person> => {
|
||||
return makeFetch<PersonWrite, Person, WritePersonViolationMap>(
|
||||
|
||||
@@ -140,6 +140,7 @@
|
||||
<div class="form-floating">
|
||||
<select
|
||||
class="form-select form-select-lg"
|
||||
:class="{ 'is-invalid': hasValidationError('center') }"
|
||||
id="center"
|
||||
v-model="center"
|
||||
>
|
||||
@@ -150,7 +151,7 @@
|
||||
{{ c.name }}
|
||||
</option>
|
||||
</select>
|
||||
<label>{{ trans(PERSON_MESSAGES_PERSON_CENTER_TITLE) }}</label>
|
||||
<label for="center">{{ trans(PERSON_MESSAGES_PERSON_CENTER_TITLE) }}</label>
|
||||
</div>
|
||||
<div
|
||||
v-for="err in validationError('center')"
|
||||
@@ -166,6 +167,7 @@
|
||||
<div class="form-floating">
|
||||
<select
|
||||
class="form-select form-select-lg"
|
||||
:class="{ 'is-invalid': hasValidationError('civility') }"
|
||||
id="civility"
|
||||
v-model="civility"
|
||||
>
|
||||
@@ -176,7 +178,7 @@
|
||||
{{ localizeString(c.name) }}
|
||||
</option>
|
||||
</select>
|
||||
<label>{{ trans(PERSON_MESSAGES_PERSON_CIVILITY_TITLE) }}</label>
|
||||
<label for="civility">{{ trans(PERSON_MESSAGES_PERSON_CIVILITY_TITLE) }}</label>
|
||||
</div>
|
||||
<div
|
||||
v-for="err in validationError('civility')"
|
||||
@@ -187,6 +189,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="input-group has-validation">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-cake2-fill"></i>
|
||||
</span>
|
||||
<div class="form-floating">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
:class="{ 'is-invalid': hasValidationError('birthdate') }"
|
||||
name="birthdate"
|
||||
type="date"
|
||||
v-model="birthDate"
|
||||
:placeholder="trans(BIRTHDATE)"
|
||||
:aria-label="trans(BIRTHDATE)"
|
||||
/>
|
||||
<label for="birthdate">{{ trans(BIRTHDATE) }}</label>
|
||||
</div>
|
||||
<div
|
||||
v-for="err in validationError('birthdate')"
|
||||
class="invalid-feedback was-validated-force"
|
||||
>
|
||||
{{ err }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="input-group has-validation">
|
||||
<span class="input-group-text" id="phonenumber">
|
||||
@@ -195,11 +223,13 @@
|
||||
<div class="form-floating">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
:class="{ 'is-invalid': hasValidationError('phonenumber') }"
|
||||
v-model="phonenumber"
|
||||
:placeholder="trans(PERSON_MESSAGES_PERSON_PHONENUMBER)"
|
||||
:aria-label="trans(PERSON_MESSAGES_PERSON_PHONENUMBER)"
|
||||
aria-describedby="phonenumber"
|
||||
/>
|
||||
<label for="phonenumber">{{ trans(PERSON_MESSAGES_PERSON_PHONENUMBER) }}</label>
|
||||
</div>
|
||||
<div
|
||||
v-for="err in validationError('phonenumber')"
|
||||
@@ -218,11 +248,13 @@
|
||||
<div class="form-floating">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
:class="{ 'is-invalid': hasValidationError('mobilenumber') }"
|
||||
v-model="mobilenumber"
|
||||
:placeholder="trans(PERSON_MESSAGES_PERSON_MOBILENUMBER)"
|
||||
:aria-label="trans(PERSON_MESSAGES_PERSON_MOBILENUMBER)"
|
||||
aria-describedby="mobilenumber"
|
||||
/>
|
||||
<label for="mobilenumber">{{ trans(PERSON_MESSAGES_PERSON_MOBILENUMBER) }}</label>
|
||||
</div>
|
||||
<div
|
||||
v-for="err in validationError('mobilenumber')"
|
||||
@@ -241,11 +273,13 @@
|
||||
<div class="form-floating">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
:class="{ 'is-invalid': hasValidationError('email') }"
|
||||
v-model="email"
|
||||
:placeholder="trans(PERSON_MESSAGES_PERSON_EMAIL)"
|
||||
:aria-label="trans(PERSON_MESSAGES_PERSON_EMAIL)"
|
||||
aria-describedby="email"
|
||||
/>
|
||||
<label for="email">{{ trans(PERSON_MESSAGES_PERSON_EMAIL) }}</label>
|
||||
</div>
|
||||
<div
|
||||
v-for="err in validationError('email')"
|
||||
@@ -305,6 +339,8 @@ import {
|
||||
} from "../../_api/OnTheFly";
|
||||
import {
|
||||
trans,
|
||||
BIRTHDATE,
|
||||
PERSON_EDIT_ERROR_WHILE_SAVING,
|
||||
PERSON_MESSAGES_PERSON_LASTNAME,
|
||||
PERSON_MESSAGES_PERSON_FIRSTNAME,
|
||||
PERSON_MESSAGES_PERSON_GENDER_PLACEHOLDER,
|
||||
@@ -323,7 +359,7 @@ import {
|
||||
Center,
|
||||
Civility,
|
||||
Gender,
|
||||
DateTimeCreate,
|
||||
DateTimeWrite,
|
||||
} from "ChillMainAssets/types";
|
||||
import {
|
||||
AltName,
|
||||
@@ -337,6 +373,8 @@ import {
|
||||
isValidationException,
|
||||
ValidationExceptionInterface,
|
||||
} from "ChillMainAssets/lib/api/apiMethods";
|
||||
import {useToast} from "vue-toast-notification";
|
||||
import {getTimezoneOffsetString, ISOToDate} from "ChillMainAssets/chill/js/date";
|
||||
|
||||
interface PersonEditComponentConfig {
|
||||
id?: number | null;
|
||||
@@ -355,6 +393,8 @@ const emit =
|
||||
|
||||
defineExpose({ postPerson });
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const person = reactive<PersonWrite>({
|
||||
type: "person",
|
||||
firstName: "",
|
||||
@@ -436,12 +476,18 @@ const civility = computed({
|
||||
const birthDate = computed({
|
||||
get: () => (person.birthdate ? person.birthdate.datetime.split("T")[0] : ""),
|
||||
set: (value: string) => {
|
||||
if (person.birthdate) {
|
||||
person.birthdate.datetime = value + "T00:00:00+0100";
|
||||
} else {
|
||||
person.birthdate = { datetime: value + "T00:00:00+0100" };
|
||||
const date = ISOToDate(value);
|
||||
if (null === date) {
|
||||
person.birthdate = null;
|
||||
return;
|
||||
}
|
||||
const offset = getTimezoneOffsetString(date, Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||
if (person.birthdate) {
|
||||
person.birthdate.datetime = value + "T00:00:00" + offset;
|
||||
} else {
|
||||
person.birthdate = { datetime: value + "T00:00:00" + offset };
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
const phonenumber = computed({
|
||||
get: () => person.phonenumber,
|
||||
@@ -586,6 +632,8 @@ async function postPerson(): Promise<void> {
|
||||
if (isValidationException<WritePersonViolationMap>(e)) {
|
||||
console.log(e.byProperty);
|
||||
validationErrors.value = e.byProperty;
|
||||
} else {
|
||||
toast.error(trans(PERSON_EDIT_ERROR_WHILE_SAVING));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,8 @@ Administrative status: Situation administrative
|
||||
person:
|
||||
Identifiers: Identifiants
|
||||
|
||||
person_edit:
|
||||
Error while saving: Erreur lors de l'enregistrement
|
||||
|
||||
# dédoublonnage
|
||||
Old person: Doublon
|
||||
@@ -1547,7 +1549,7 @@ person_messages:
|
||||
center_id: "Identifiant du centre"
|
||||
center_type: "Type de centre"
|
||||
center_name: "Territoire"
|
||||
phonenumber: "Téléphone"
|
||||
phonenumber: "Téléphone fixe"
|
||||
mobilenumber: "Mobile"
|
||||
altnames: "Autres noms"
|
||||
email: "Courriel"
|
||||
|
||||
Reference in New Issue
Block a user