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;
|
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
|
* Must be a string in format Y-m-d\TH:i:sO
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
Job,
|
Job,
|
||||||
PrivateCommentEmbeddable,
|
PrivateCommentEmbeddable,
|
||||||
TranslatableString,
|
TranslatableString,
|
||||||
DateTimeCreate,
|
DateTimeWrite,
|
||||||
SetGender,
|
SetGender,
|
||||||
SetCenter,
|
SetCenter,
|
||||||
SetCivility,
|
SetCivility,
|
||||||
@@ -67,8 +67,8 @@ export interface PersonWrite {
|
|||||||
lastName: string;
|
lastName: string;
|
||||||
altNames: AltNameWrite[];
|
altNames: AltNameWrite[];
|
||||||
// address: number | null;
|
// address: number | null;
|
||||||
birthdate: DateTimeCreate | null;
|
birthdate: DateTimeWrite | null;
|
||||||
deathdate: DateTimeCreate | null;
|
deathdate: DateTimeWrite | null;
|
||||||
phonenumber: string;
|
phonenumber: string;
|
||||||
mobilenumber: string;
|
mobilenumber: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export interface WritePersonViolationMap
|
|||||||
civility: {
|
civility: {
|
||||||
"{{ value }}": string | null;
|
"{{ value }}": string | null;
|
||||||
};
|
};
|
||||||
|
birthdate: {};
|
||||||
}
|
}
|
||||||
export const createPerson = async (person: PersonWrite): Promise<Person> => {
|
export const createPerson = async (person: PersonWrite): Promise<Person> => {
|
||||||
return makeFetch<PersonWrite, Person, WritePersonViolationMap>(
|
return makeFetch<PersonWrite, Person, WritePersonViolationMap>(
|
||||||
|
|||||||
@@ -140,6 +140,7 @@
|
|||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<select
|
<select
|
||||||
class="form-select form-select-lg"
|
class="form-select form-select-lg"
|
||||||
|
:class="{ 'is-invalid': hasValidationError('center') }"
|
||||||
id="center"
|
id="center"
|
||||||
v-model="center"
|
v-model="center"
|
||||||
>
|
>
|
||||||
@@ -150,7 +151,7 @@
|
|||||||
{{ c.name }}
|
{{ c.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<label>{{ trans(PERSON_MESSAGES_PERSON_CENTER_TITLE) }}</label>
|
<label for="center">{{ trans(PERSON_MESSAGES_PERSON_CENTER_TITLE) }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="err in validationError('center')"
|
v-for="err in validationError('center')"
|
||||||
@@ -166,6 +167,7 @@
|
|||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<select
|
<select
|
||||||
class="form-select form-select-lg"
|
class="form-select form-select-lg"
|
||||||
|
:class="{ 'is-invalid': hasValidationError('civility') }"
|
||||||
id="civility"
|
id="civility"
|
||||||
v-model="civility"
|
v-model="civility"
|
||||||
>
|
>
|
||||||
@@ -176,7 +178,7 @@
|
|||||||
{{ localizeString(c.name) }}
|
{{ localizeString(c.name) }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<label>{{ trans(PERSON_MESSAGES_PERSON_CIVILITY_TITLE) }}</label>
|
<label for="civility">{{ trans(PERSON_MESSAGES_PERSON_CIVILITY_TITLE) }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="err in validationError('civility')"
|
v-for="err in validationError('civility')"
|
||||||
@@ -187,6 +189,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</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="mb-3">
|
||||||
<div class="input-group has-validation">
|
<div class="input-group has-validation">
|
||||||
<span class="input-group-text" id="phonenumber">
|
<span class="input-group-text" id="phonenumber">
|
||||||
@@ -195,11 +223,13 @@
|
|||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input
|
<input
|
||||||
class="form-control form-control-lg"
|
class="form-control form-control-lg"
|
||||||
|
:class="{ 'is-invalid': hasValidationError('phonenumber') }"
|
||||||
v-model="phonenumber"
|
v-model="phonenumber"
|
||||||
:placeholder="trans(PERSON_MESSAGES_PERSON_PHONENUMBER)"
|
:placeholder="trans(PERSON_MESSAGES_PERSON_PHONENUMBER)"
|
||||||
:aria-label="trans(PERSON_MESSAGES_PERSON_PHONENUMBER)"
|
:aria-label="trans(PERSON_MESSAGES_PERSON_PHONENUMBER)"
|
||||||
aria-describedby="phonenumber"
|
aria-describedby="phonenumber"
|
||||||
/>
|
/>
|
||||||
|
<label for="phonenumber">{{ trans(PERSON_MESSAGES_PERSON_PHONENUMBER) }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="err in validationError('phonenumber')"
|
v-for="err in validationError('phonenumber')"
|
||||||
@@ -218,11 +248,13 @@
|
|||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input
|
<input
|
||||||
class="form-control form-control-lg"
|
class="form-control form-control-lg"
|
||||||
|
:class="{ 'is-invalid': hasValidationError('mobilenumber') }"
|
||||||
v-model="mobilenumber"
|
v-model="mobilenumber"
|
||||||
:placeholder="trans(PERSON_MESSAGES_PERSON_MOBILENUMBER)"
|
:placeholder="trans(PERSON_MESSAGES_PERSON_MOBILENUMBER)"
|
||||||
:aria-label="trans(PERSON_MESSAGES_PERSON_MOBILENUMBER)"
|
:aria-label="trans(PERSON_MESSAGES_PERSON_MOBILENUMBER)"
|
||||||
aria-describedby="mobilenumber"
|
aria-describedby="mobilenumber"
|
||||||
/>
|
/>
|
||||||
|
<label for="mobilenumber">{{ trans(PERSON_MESSAGES_PERSON_MOBILENUMBER) }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="err in validationError('mobilenumber')"
|
v-for="err in validationError('mobilenumber')"
|
||||||
@@ -241,11 +273,13 @@
|
|||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input
|
<input
|
||||||
class="form-control form-control-lg"
|
class="form-control form-control-lg"
|
||||||
|
:class="{ 'is-invalid': hasValidationError('email') }"
|
||||||
v-model="email"
|
v-model="email"
|
||||||
:placeholder="trans(PERSON_MESSAGES_PERSON_EMAIL)"
|
:placeholder="trans(PERSON_MESSAGES_PERSON_EMAIL)"
|
||||||
:aria-label="trans(PERSON_MESSAGES_PERSON_EMAIL)"
|
:aria-label="trans(PERSON_MESSAGES_PERSON_EMAIL)"
|
||||||
aria-describedby="email"
|
aria-describedby="email"
|
||||||
/>
|
/>
|
||||||
|
<label for="email">{{ trans(PERSON_MESSAGES_PERSON_EMAIL) }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="err in validationError('email')"
|
v-for="err in validationError('email')"
|
||||||
@@ -305,6 +339,8 @@ import {
|
|||||||
} from "../../_api/OnTheFly";
|
} from "../../_api/OnTheFly";
|
||||||
import {
|
import {
|
||||||
trans,
|
trans,
|
||||||
|
BIRTHDATE,
|
||||||
|
PERSON_EDIT_ERROR_WHILE_SAVING,
|
||||||
PERSON_MESSAGES_PERSON_LASTNAME,
|
PERSON_MESSAGES_PERSON_LASTNAME,
|
||||||
PERSON_MESSAGES_PERSON_FIRSTNAME,
|
PERSON_MESSAGES_PERSON_FIRSTNAME,
|
||||||
PERSON_MESSAGES_PERSON_GENDER_PLACEHOLDER,
|
PERSON_MESSAGES_PERSON_GENDER_PLACEHOLDER,
|
||||||
@@ -323,7 +359,7 @@ import {
|
|||||||
Center,
|
Center,
|
||||||
Civility,
|
Civility,
|
||||||
Gender,
|
Gender,
|
||||||
DateTimeCreate,
|
DateTimeWrite,
|
||||||
} from "ChillMainAssets/types";
|
} from "ChillMainAssets/types";
|
||||||
import {
|
import {
|
||||||
AltName,
|
AltName,
|
||||||
@@ -337,6 +373,8 @@ import {
|
|||||||
isValidationException,
|
isValidationException,
|
||||||
ValidationExceptionInterface,
|
ValidationExceptionInterface,
|
||||||
} from "ChillMainAssets/lib/api/apiMethods";
|
} from "ChillMainAssets/lib/api/apiMethods";
|
||||||
|
import {useToast} from "vue-toast-notification";
|
||||||
|
import {getTimezoneOffsetString, ISOToDate} from "ChillMainAssets/chill/js/date";
|
||||||
|
|
||||||
interface PersonEditComponentConfig {
|
interface PersonEditComponentConfig {
|
||||||
id?: number | null;
|
id?: number | null;
|
||||||
@@ -355,6 +393,8 @@ const emit =
|
|||||||
|
|
||||||
defineExpose({ postPerson });
|
defineExpose({ postPerson });
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
const person = reactive<PersonWrite>({
|
const person = reactive<PersonWrite>({
|
||||||
type: "person",
|
type: "person",
|
||||||
firstName: "",
|
firstName: "",
|
||||||
@@ -436,12 +476,18 @@ const civility = computed({
|
|||||||
const birthDate = computed({
|
const birthDate = computed({
|
||||||
get: () => (person.birthdate ? person.birthdate.datetime.split("T")[0] : ""),
|
get: () => (person.birthdate ? person.birthdate.datetime.split("T")[0] : ""),
|
||||||
set: (value: string) => {
|
set: (value: string) => {
|
||||||
if (person.birthdate) {
|
const date = ISOToDate(value);
|
||||||
person.birthdate.datetime = value + "T00:00:00+0100";
|
if (null === date) {
|
||||||
} else {
|
person.birthdate = null;
|
||||||
person.birthdate = { datetime: value + "T00:00:00+0100" };
|
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({
|
const phonenumber = computed({
|
||||||
get: () => person.phonenumber,
|
get: () => person.phonenumber,
|
||||||
@@ -586,6 +632,8 @@ async function postPerson(): Promise<void> {
|
|||||||
if (isValidationException<WritePersonViolationMap>(e)) {
|
if (isValidationException<WritePersonViolationMap>(e)) {
|
||||||
console.log(e.byProperty);
|
console.log(e.byProperty);
|
||||||
validationErrors.value = e.byProperty;
|
validationErrors.value = e.byProperty;
|
||||||
|
} else {
|
||||||
|
toast.error(trans(PERSON_EDIT_ERROR_WHILE_SAVING));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ Administrative status: Situation administrative
|
|||||||
person:
|
person:
|
||||||
Identifiers: Identifiants
|
Identifiers: Identifiants
|
||||||
|
|
||||||
|
person_edit:
|
||||||
|
Error while saving: Erreur lors de l'enregistrement
|
||||||
|
|
||||||
# dédoublonnage
|
# dédoublonnage
|
||||||
Old person: Doublon
|
Old person: Doublon
|
||||||
@@ -1547,7 +1549,7 @@ person_messages:
|
|||||||
center_id: "Identifiant du centre"
|
center_id: "Identifiant du centre"
|
||||||
center_type: "Type de centre"
|
center_type: "Type de centre"
|
||||||
center_name: "Territoire"
|
center_name: "Territoire"
|
||||||
phonenumber: "Téléphone"
|
phonenumber: "Téléphone fixe"
|
||||||
mobilenumber: "Mobile"
|
mobilenumber: "Mobile"
|
||||||
altnames: "Autres noms"
|
altnames: "Autres noms"
|
||||||
email: "Courriel"
|
email: "Courriel"
|
||||||
|
|||||||
Reference in New Issue
Block a user