Merge branch '1682-1683-1684-fix-bug-mr-884' into 'ticket-app-master'

FIX des bugs du merge request 884

See merge request Chill-Projet/chill-bundles!885
This commit is contained in:
2025-09-30 13:49:04 +00:00
15 changed files with 127 additions and 81 deletions

View File

@@ -1,7 +1,12 @@
import { trans, setLocale, setLocaleFallbacks } from "./ux-translator"; import {
trans,
setLocale,
getLocale,
setLocaleFallbacks,
} from "./ux-translator";
setLocaleFallbacks({"en": "fr", "nl": "fr", "fr": "en"}); setLocaleFallbacks({ en: "fr", nl: "fr", fr: "en" });
setLocale('fr'); setLocale("fr");
export { trans }; export { trans, getLocale };
export * from '../var/translations'; export * from "../var/translations";

View File

@@ -1,4 +1,5 @@
import { TranslatableString } from "ChillMainAssets/types"; import { DateTime, TranslatableString } from "ChillMainAssets/types";
import { getLocale } from "translator";
/** /**
* Localizes a translatable string object based on the current locale. * Localizes a translatable string object based on the current locale.
@@ -17,11 +18,10 @@ import { TranslatableString } from "ChillMainAssets/types";
* @returns The localized URL * @returns The localized URL
*/ */
export function localizedUrl(url: string): string { export function localizedUrl(url: string): string {
const lang = const locale = getLocale();
document.documentElement.lang || navigator.language.split("-")[0] || "fr";
// Ensure url starts with a slash and does not already start with /{lang}/ // Ensure url starts with a slash and does not already start with /{lang}/
const normalizedUrl = url.startsWith("/") ? url : `/${url}`; const normalizedUrl = url.startsWith("/") ? url : `/${url}`;
const langPrefix = `/${lang}`; const langPrefix = `/${locale}`;
if (normalizedUrl.startsWith(langPrefix + "/")) { if (normalizedUrl.startsWith(langPrefix + "/")) {
return normalizedUrl; return normalizedUrl;
} }
@@ -36,7 +36,7 @@ export function localizeString(
return ""; return "";
} }
const currentLocale = locale || navigator.language.split("-")[0] || "fr"; const currentLocale = locale || getLocale();
if (translatableString[currentLocale]) { if (translatableString[currentLocale]) {
return translatableString[currentLocale]; return translatableString[currentLocale];
@@ -59,3 +59,47 @@ export function localizeString(
return ""; return "";
} }
const datetimeFormats: Record<
string,
Record<string, Intl.DateTimeFormatOptions>
> = {
fr: {
short: {
year: "numeric",
month: "numeric",
day: "numeric",
},
text: {
year: "numeric",
month: "long",
day: "numeric",
},
long: {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
hour12: false,
},
hoursOnly: {
hour: "numeric",
minute: "numeric",
hour12: false,
},
},
};
export function localizeDateTimeFormat(
dateTime: DateTime,
format: keyof typeof datetimeFormats.fr = "short",
): string {
const locale = getLocale();
const options =
datetimeFormats[locale]?.[format] || datetimeFormats.fr[format];
return new Intl.DateTimeFormat(locale, options).format(
new Date(dateTime.datetime),
);
}
export default datetimeFormats;

View File

@@ -122,7 +122,6 @@ const tabDefinitions: TabDefinition[] = [
]; ];
const displayedTabs = computed(() => { const displayedTabs = computed(() => {
// Always show MyCustoms first if present
const tabs = [] as TabDefinition[]; const tabs = [] as TabDefinition[];
for (const tabEnum of homepageConfig.value.displayTabs) { for (const tabEnum of homepageConfig.value.displayTabs) {
const def = tabDefinitions.find( const def = tabDefinitions.find(
@@ -137,10 +136,7 @@ const activeTab = ref(Number(HomepageTabs[homepageConfig.value.defaultTab]));
const loading = computed(() => store.state.loading); const loading = computed(() => store.state.loading);
function selectTab(tab: HomepageTabs) { async function selectTab(tab: HomepageTabs) {
if (tab !== HomepageTabs.MyCustoms) {
store.dispatch("getByTab", { tab: tab });
}
activeTab.value = tab; activeTab.value = tab;
} }

View File

@@ -2,7 +2,9 @@
<li> <li>
<h2>{{ props.item.title }}</h2> <h2>{{ props.item.title }}</h2>
<time class="createdBy" datetime="{{item.startDate.datetime}}">{{ <time class="createdBy" datetime="{{item.startDate.datetime}}">{{
$d(newsItemStartDate(), "text") props.item?.startDate
? localizeDateTimeFormat(props.item?.startDate, "text")
: ""
}}</time> }}</time>
<div class="content" v-if="shouldTruncate(item.content)"> <div class="content" v-if="shouldTruncate(item.content)">
<div v-html="prepareContent(item.content)"></div> <div v-html="prepareContent(item.content)"></div>
@@ -26,7 +28,9 @@
<template #body> <template #body>
<p class="news-date"> <p class="news-date">
<time class="createdBy" datetime="{{item.startDate.datetime}}">{{ <time class="createdBy" datetime="{{item.startDate.datetime}}">{{
$d(newsItemStartDate(), "text") props.item?.startDate
? localizeDateTimeFormat(props.item?.startDate, "text")
: ""
}}</time> }}</time>
</p> </p>
<div v-html="convertMarkdownToHtml(item.content)"></div> <div v-html="convertMarkdownToHtml(item.content)"></div>
@@ -42,7 +46,7 @@ import DOMPurify from "dompurify";
import { NewsItemType } from "../../../types"; import { NewsItemType } from "../../../types";
import type { PropType } from "vue"; import type { PropType } from "vue";
import { ref } from "vue"; import { ref } from "vue";
import { ISOToDatetime } from "../../../chill/js/date"; import { localizeDateTimeFormat } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
const props = defineProps({ const props = defineProps({
item: { item: {
@@ -133,7 +137,7 @@ const preprocess = (markdown: string): string => {
}; };
const postprocess = (html: string): string => { const postprocess = (html: string): string => {
DOMPurify.addHook("afterSanitizeAttributes", (node: any) => { DOMPurify.addHook("afterSanitizeAttributes", (node: Element) => {
if ("target" in node) { if ("target" in node) {
node.setAttribute("target", "_blank"); node.setAttribute("target", "_blank");
node.setAttribute("rel", "noopener noreferrer"); node.setAttribute("rel", "noopener noreferrer");
@@ -159,10 +163,6 @@ const prepareContent = (content: string): string => {
const htmlContent = convertMarkdownToHtml(content); const htmlContent = convertMarkdownToHtml(content);
return truncateContent(htmlContent); return truncateContent(htmlContent);
}; };
const newsItemStartDate = (): null | Date => {
return ISOToDatetime(props.item?.startDate.datetime);
};
</script> </script>
<style scoped> <style scoped>

View File

@@ -21,7 +21,7 @@
</template> </template>
<template #tbody> <template #tbody>
<tr v-for="(c, i) in accompanyingCourses.results" :key="`course-${i}`"> <tr v-for="(c, i) in accompanyingCourses.results" :key="`course-${i}`">
<td>{{ $d(new Date(c.openingDate.datetime), "short") }}</td> <td>{{ localizeDateTimeFormat(c.openingDate, "short") }}</td>
<td> <td>
<span <span
v-for="(issue, index) in c.socialIssues" v-for="(issue, index) in c.socialIssues"
@@ -82,6 +82,8 @@ import {
CONFIDENTIAL, CONFIDENTIAL,
trans, trans,
} from "translator"; } from "translator";
import { localizeDateTimeFormat } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
const store = useStore(); const store = useStore();
const accompanyingCourses: ComputedRef<PaginationResponse<AccompanyingCourse>> = const accompanyingCourses: ComputedRef<PaginationResponse<AccompanyingCourse>> =

View File

@@ -1,62 +1,59 @@
<template> <template>
<span v-if="noResults" class="chill-no-data-statement"> <div id="dashboards" class="container g-3">
{{ trans(NO_DASHBOARD) }}
</span>
<div v-else id="dashboards" class="container g-3">
<div class="row"> <div class="row">
<div class="mbloc col-xs-12 col-sm-4"> <div class="mbloc col-xs-12 col-sm-4">
<div class="custom1"> <div class="custom1">
<ul class="list-unstyled"> <ul class="list-unstyled">
<li v-if="(counter.value?.notifications || 0) > 0"> <li v-if="counter.notifications > 0">
<span :class="counterClass"> <span :class="counterClass">
{{ {{
trans(COUNTER_UNREAD_NOTIFICATIONS, { trans(COUNTER_UNREAD_NOTIFICATIONS, {
n: counter.value?.notifications || 0, n: counter.notifications,
}) })
}} }}
</span> </span>
</li> </li>
<li v-if="(counter.value?.accompanyingCourses || 0) > 0"> <li v-if="counter.accompanyingCourses > 0">
<span :class="counterClass"> <span :class="counterClass">
{{ {{
trans(COUNTER_ASSIGNATED_COURSES, { trans(COUNTER_ASSIGNATED_COURSES, {
n: counter.value?.accompanyingCourses || 0, n: counter.accompanyingCourses,
}) })
}} }}
</span> </span>
</li> </li>
<li v-if="(counter.value?.works || 0) > 0"> <li v-if="counter.works > 0">
<span :class="counterClass"> <span :class="counterClass">
{{ {{
trans(COUNTER_ASSIGNATED_ACTIONS, { trans(COUNTER_ASSIGNATED_ACTIONS, {
n: counter.value?.works || 0, n: counter.works,
}) })
}} }}
</span> </span>
</li> </li>
<li v-if="(counter.value?.evaluations || 0) > 0"> <li v-if="counter.evaluations > 0">
<span :class="counterClass"> <span :class="counterClass">
{{ {{
trans(COUNTER_ASSIGNATED_EVALUATIONS, { trans(COUNTER_ASSIGNATED_EVALUATIONS, {
n: counter.value?.evaluations || 0, n: counter.evaluations,
}) })
}} }}
</span> </span>
</li> </li>
<li v-if="(counter.value?.tasksAlert || 0) > 0"> <li v-if="counter.tasksAlert > 0">
<span :class="counterClass"> <span :class="counterClass">
{{ {{
trans(COUNTER_ALERT_TASKS, { trans(COUNTER_ALERT_TASKS, {
n: counter.value?.tasksAlert || 0, n: counter.tasksAlert,
}) })
}} }}
</span> </span>
</li> </li>
<li v-if="(counter.value?.tasksWarning || 0) > 0"> <li v-if="counter.tasksWarning > 0">
<span :class="counterClass"> <span :class="counterClass">
{{ {{
trans(COUNTER_WARNING_TASKS, { trans(COUNTER_WARNING_TASKS, {
n: counter.value?.tasksWarning || 0, n: counter.tasksWarning,
}) })
}} }}
</span> </span>
@@ -85,7 +82,6 @@ import { useStore } from "vuex";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import News from "./DashboardWidgets/News.vue"; import News from "./DashboardWidgets/News.vue";
import { import {
NO_DASHBOARD,
COUNTER_UNREAD_NOTIFICATIONS, COUNTER_UNREAD_NOTIFICATIONS,
COUNTER_ASSIGNATED_COURSES, COUNTER_ASSIGNATED_COURSES,
COUNTER_ASSIGNATED_ACTIONS, COUNTER_ASSIGNATED_ACTIONS,
@@ -105,14 +101,19 @@ interface MyCustom {
const store = useStore(); const store = useStore();
const counter = computed(() => store.getters.counter); const counter = computed(() => ({
notifications: store.state.homepage.notifications?.count ?? 0,
accompanyingCourses: store.state.homepage.accompanyingCourses?.count ?? 0,
works: store.state.homepage.works?.count ?? 0,
evaluations: store.state.homepage.evaluations?.count ?? 0,
tasksAlert: store.state.homepage.tasksAlert?.count ?? 0,
tasksWarning: store.state.homepage.tasksWarning?.count ?? 0,
}));
const counterClass = { counter: true }; const counterClass = { counter: true };
const dashboardItems = ref<MyCustom[]>([]); const dashboardItems = ref<MyCustom[]>([]);
const noResults = computed(() => false);
const hasDashboardItems = computed(() => dashboardItems.value.length > 0); const hasDashboardItems = computed(() => dashboardItems.value.length > 0);
onMounted(async () => { onMounted(async () => {

View File

@@ -22,11 +22,7 @@
<template #tbody> <template #tbody>
<tr v-for="(e, i) in evaluations.results" :key="`evaluation-${i}`"> <tr v-for="(e, i) in evaluations.results" :key="`evaluation-${i}`">
<td> <td>
{{ {{ e.maxDate ? localizeDateTimeFormat(e.maxDate, "short") : "" }}
e.maxDate?.datetime
? $d(new Date(e.maxDate.datetime), "short")
: ""
}}
</td> </td>
<td> <td>
{{ localizeString(e.evaluation?.title ?? null) }} {{ localizeString(e.evaluation?.title ?? null) }}
@@ -115,6 +111,8 @@ import {
NO_DATA, NO_DATA,
trans, trans,
} from "translator"; } from "translator";
import { localizeDateTimeFormat } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
const evaluations: ComputedRef< const evaluations: ComputedRef<
PaginationResponse<AccompanyingPeriodWorkEvaluation> PaginationResponse<AccompanyingPeriodWorkEvaluation>
> = computed(() => store.state.homepage.evaluations); > = computed(() => store.state.homepage.evaluations);

View File

@@ -20,7 +20,7 @@
</template> </template>
<template #tbody> <template #tbody>
<tr v-for="(n, i) in notifications.results" :key="`notify-${i}`"> <tr v-for="(n, i) in notifications.results" :key="`notify-${i}`">
<td>{{ $d(new Date(n.date.datetime), "long") }}</td> <td>{{ localizeDateTimeFormat(n.date, "long") }}</td>
<td> <td>
<span class="unread"> <span class="unread">
<i class="fa fa-envelope-o" /> <i class="fa fa-envelope-o" />
@@ -65,6 +65,8 @@ import {
trans, trans,
} from "translator"; } from "translator";
import { PaginationResponse } from "ChillMainAssets/lib/api/apiMethods"; import { PaginationResponse } from "ChillMainAssets/lib/api/apiMethods";
import { localizeDateTimeFormat } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
const store = useStore(); const store = useStore();
const notifications: ComputedRef<PaginationResponse<Notification>> = computed( const notifications: ComputedRef<PaginationResponse<Notification>> = computed(

View File

@@ -21,12 +21,12 @@
<template #tbody> <template #tbody>
<tr v-for="(t, i) in tasks.alert.results" :key="`task-alert-${i}`"> <tr v-for="(t, i) in tasks.alert.results" :key="`task-alert-${i}`">
<td v-if="t.warningDate !== null"> <td v-if="t.warningDate !== null">
{{ $d(new Date(t.warningDate.datetime), "short") }} {{ localizeDateTimeFormat(t.warningDate, "short") }}
</td> </td>
<td v-else /> <td v-else />
<td> <td>
<span class="outdated">{{ <span class="outdated">{{
$d(new Date(t.endDate.datetime), "short") localizeDateTimeFormat(t.endDate, "short")
}}</span> }}</span>
</td> </td>
<td>{{ t.title }}</td> <td>{{ t.title }}</td>
@@ -62,10 +62,10 @@
<tr v-for="(t, i) in tasks.warning.results" :key="`task-warning-${i}`"> <tr v-for="(t, i) in tasks.warning.results" :key="`task-warning-${i}`">
<td> <td>
<span class="outdated">{{ <span class="outdated">{{
$d(new Date(t.warningDate.datetime), "short") localizeDateTimeFormat(t.warningDate, "short")
}}</span> }}</span>
</td> </td>
<td>{{ $d(new Date(t.endDate.datetime), "short") }}</td> <td>{{ localizeDateTimeFormat(t.endDate, "short") }}</td>
<td>{{ t.title }}</td> <td>{{ t.title }}</td>
<td> <td>
<a class="btn btn-sm btn-show" :href="getUrl(t)"> <a class="btn btn-sm btn-show" :href="getUrl(t)">
@@ -94,6 +94,7 @@ import {
} from "translator"; } from "translator";
import { TasksState } from "./store/modules/homepage"; import { TasksState } from "./store/modules/homepage";
import { Alert, Warning } from "ChillPersonAssets/types"; import { Alert, Warning } from "ChillPersonAssets/types";
import { localizeDateTimeFormat } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
const store = useStore(); const store = useStore();

View File

@@ -21,7 +21,7 @@
</template> </template>
<template #tbody> <template #tbody>
<tr v-for="(w, i) in works.value.results" :key="`works-${i}`"> <tr v-for="(w, i) in works.value.results" :key="`works-${i}`">
<td>{{ $d(w.startDate.datetime, "short") }}</td> <td>{{ localizeDateTimeFormat(w.startDate.datetime, "short") }}</td>
<td> <td>
<span class="chill-entity entity-social-issue"> <span class="chill-entity entity-social-issue">
<span class="badge bg-chill-l-gray text-dark"> <span class="badge bg-chill-l-gray text-dark">
@@ -90,6 +90,7 @@ import {
trans, trans,
} from "translator"; } from "translator";
import { Workflow } from "ChillPersonAssets/types"; import { Workflow } from "ChillPersonAssets/types";
import { localizeDateTimeFormat } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
const store = useStore(); const store = useStore();

View File

@@ -11,6 +11,7 @@ import {
Warning, Warning,
Workflow, Workflow,
WorflowCc, WorflowCc,
Notification,
} from "ChillPersonAssets/types"; } from "ChillPersonAssets/types";
import { RootState } from ".."; import { RootState } from "..";
import { HomepageTabs } from "ChillMainAssets/types"; import { HomepageTabs } from "ChillMainAssets/types";
@@ -191,6 +192,7 @@ export const moduleHomepage: Module<State, RootState> = {
if (!getters.isNotificationsLoaded) { if (!getters.isNotificationsLoaded) {
commit("setLoading", true); commit("setLoading", true);
const url = `/api/1.0/main/notification/my/unread${"?" + param}`; const url = `/api/1.0/main/notification/my/unread${"?" + param}`;
makeFetch("GET", url) makeFetch("GET", url)
.then((response) => { .then((response) => {
commit("addNotifications", response); commit("addNotifications", response);

View File

@@ -291,6 +291,7 @@ export interface Notification {
relatedEntityClass: string; relatedEntityClass: string;
relatedEntityId: number; relatedEntityId: number;
} }
export interface Participation { export interface Participation {
person: Person; person: Person;
} }

View File

@@ -2,6 +2,13 @@
<div class="card mb-4"> <div class="card mb-4">
<div class="card-body"> <div class="card-body">
<form @submit.prevent="submitForm"> <form @submit.prevent="submitForm">
<div class="mb-3">
<label class="form-label pe-2" for="emergency">
{{ trans(CHILL_TICKET_LIST_FILTER_EMERGENCY) }}
</label>
<emergency-toggle-component v-model="isEmergency" class="float-end" />
</div>
<!-- Sélection du motif --> <!-- Sélection du motif -->
<div class="mb-3"> <div class="mb-3">
<label class="form-label">{{ <label class="form-label">{{
@@ -12,16 +19,7 @@
:motives="motives" :motives="motives"
/> />
</div> </div>
<!-- Attribution des tickets -->
<div class="mb-3">
<label class="form-label">
{{ trans(CHILL_TICKET_TICKET_ADD_COMMENT_TITLE) }}
</label>
<addressee-selector-component
v-model="ticketForm.addressees"
:suggested="userGroups"
/>
</div>
<!-- Sélection des personnes --> <!-- Sélection des personnes -->
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-6"> <div class="col-md-6">
@@ -60,23 +58,17 @@
:motive="ticketForm.motive ? ticketForm.motive : null" :motive="ticketForm.motive ? ticketForm.motive : null"
/> />
</div> </div>
<!-- Attribution des tickets -->
<div class="mb-3"> <div class="mb-3">
<label class="form-label pe-2" for="emergency"> <label class="form-label">
{{ trans(CHILL_TICKET_LIST_FILTER_EMERGENCY) }} {{ trans(CHILL_TICKET_TICKET_ADD_ADDRESSEE_TITLE) }}
</label> </label>
<toggle-component <addressee-selector-component
v-model="isEmergency" v-model="ticketForm.addressees"
:on-label="trans(CHILL_TICKET_LIST_FILTER_EMERGENCY)" :suggested="userGroups"
:off-label="trans(CHILL_TICKET_LIST_FILTER_EMERGENCY)"
:classColor="{
on: 'bg-warning',
off: 'bg-secondary',
}"
id="emergency"
class="float-end"
/> />
</div> </div>
<!-- Boutons d'action --> <!-- Boutons d'action -->
<div class="d-flex justify-content-end gap-2 mt-4"> <div class="d-flex justify-content-end gap-2 mt-4">
<button type="button" class="btn btn-secondary" @click="resetForm"> <button type="button" class="btn btn-secondary" @click="resetForm">
@@ -100,8 +92,7 @@ import MotiveSelectorComponent from "./Motive/MotiveSelectorComponent.vue";
import CommentEditorComponent from "./Comment/CommentEditorComponent.vue"; import CommentEditorComponent from "./Comment/CommentEditorComponent.vue";
import PersonsSelectorComponent from "./Person/PersonsSelectorComponent.vue"; import PersonsSelectorComponent from "./Person/PersonsSelectorComponent.vue";
import AddresseeSelectorComponent from "./Addressee/AddresseeSelectorComponent.vue"; import AddresseeSelectorComponent from "./Addressee/AddresseeSelectorComponent.vue";
import ToggleComponent from "../../TicketList/components/ToggleComponent.vue"; import EmergencyToggleComponent from "./Emergency/EmergencyToggleComponent.vue";
// Types // Types
import { import {
Motive, Motive,
@@ -124,6 +115,7 @@ import {
CHILL_TICKET_TICKET_SET_PERSONS_CALLER_LABEL, CHILL_TICKET_TICKET_SET_PERSONS_CALLER_LABEL,
CHILL_TICKET_TICKET_SET_PERSONS_USER_LABEL, CHILL_TICKET_TICKET_SET_PERSONS_USER_LABEL,
CHILL_TICKET_LIST_FILTER_EMERGENCY, CHILL_TICKET_LIST_FILTER_EMERGENCY,
CHILL_TICKET_TICKET_ADD_ADDRESSEE_TITLE,
} from "translator"; } from "translator";
import { UserGroup, UserGroupOrUser } from "ChillMainAssets/types"; import { UserGroup, UserGroupOrUser } from "ChillMainAssets/types";

View File

@@ -19,6 +19,7 @@
"sourceMap": true "sourceMap": true
}, },
"includes": [ "includes": [
"./assets/**/*.ts",
"./src/**/*.ts", "./src/**/*.ts",
"./src/**/*.vue" "./src/**/*.vue"
], ],

View File

@@ -104,7 +104,7 @@ module.exports = (async () => {
await populateConfig(Encore, chillEntries); await populateConfig(Encore, chillEntries);
Encore.addAliases({ Encore.addAliases({
translator: resolve(__dirname, './assets/translator'), translator: resolve(__dirname, 'assets/translator.ts'),
"@symfony/ux-translator": resolve(__dirname, './vendor/symfony/ux-translator/assets'), "@symfony/ux-translator": resolve(__dirname, './vendor/symfony/ux-translator/assets'),
}); });