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"});
setLocale('fr');
setLocaleFallbacks({ en: "fr", nl: "fr", fr: "en" });
setLocale("fr");
export { trans };
export * from '../var/translations';
export { trans, getLocale };
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.
@@ -17,11 +18,10 @@ import { TranslatableString } from "ChillMainAssets/types";
* @returns The localized URL
*/
export function localizedUrl(url: string): string {
const lang =
document.documentElement.lang || navigator.language.split("-")[0] || "fr";
const locale = getLocale();
// Ensure url starts with a slash and does not already start with /{lang}/
const normalizedUrl = url.startsWith("/") ? url : `/${url}`;
const langPrefix = `/${lang}`;
const langPrefix = `/${locale}`;
if (normalizedUrl.startsWith(langPrefix + "/")) {
return normalizedUrl;
}
@@ -36,7 +36,7 @@ export function localizeString(
return "";
}
const currentLocale = locale || navigator.language.split("-")[0] || "fr";
const currentLocale = locale || getLocale();
if (translatableString[currentLocale]) {
return translatableString[currentLocale];
@@ -59,3 +59,47 @@ export function localizeString(
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(() => {
// Always show MyCustoms first if present
const tabs = [] as TabDefinition[];
for (const tabEnum of homepageConfig.value.displayTabs) {
const def = tabDefinitions.find(
@@ -137,10 +136,7 @@ const activeTab = ref(Number(HomepageTabs[homepageConfig.value.defaultTab]));
const loading = computed(() => store.state.loading);
function selectTab(tab: HomepageTabs) {
if (tab !== HomepageTabs.MyCustoms) {
store.dispatch("getByTab", { tab: tab });
}
async function selectTab(tab: HomepageTabs) {
activeTab.value = tab;
}

View File

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

View File

@@ -21,7 +21,7 @@
</template>
<template #tbody>
<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>
<span
v-for="(issue, index) in c.socialIssues"
@@ -82,6 +82,8 @@ import {
CONFIDENTIAL,
trans,
} from "translator";
import { localizeDateTimeFormat } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
const store = useStore();
const accompanyingCourses: ComputedRef<PaginationResponse<AccompanyingCourse>> =

View File

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

View File

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

View File

@@ -20,7 +20,7 @@
</template>
<template #tbody>
<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>
<span class="unread">
<i class="fa fa-envelope-o" />
@@ -65,6 +65,8 @@ import {
trans,
} from "translator";
import { PaginationResponse } from "ChillMainAssets/lib/api/apiMethods";
import { localizeDateTimeFormat } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
const store = useStore();
const notifications: ComputedRef<PaginationResponse<Notification>> = computed(

View File

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

View File

@@ -21,7 +21,7 @@
</template>
<template #tbody>
<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>
<span class="chill-entity entity-social-issue">
<span class="badge bg-chill-l-gray text-dark">
@@ -90,6 +90,7 @@ import {
trans,
} from "translator";
import { Workflow } from "ChillPersonAssets/types";
import { localizeDateTimeFormat } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
const store = useStore();

View File

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

View File

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

View File

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

View File

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

View File

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