mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-25 16:14:59 +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