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