Merge branch 'ticket-app-master' into ticket/64-identifiants-person

This commit is contained in:
2025-09-30 16:01:33 +02:00
39 changed files with 854 additions and 220 deletions

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);