From 8c926c3cfd9022a6e7c676c0a89178a4928ae600 Mon Sep 17 00:00:00 2001 From: Abdelsalem Date: Mon, 14 Apr 2025 11:21:22 +0200 Subject: [PATCH 01/23] add announcement page Signed-off-by: Abdelsalem --- package-lock.json | 92 +++++++++ package.json | 2 + src/components/App/app-top-bar.tsx | 16 +- src/pages/banners/add-announcement-form.tsx | 151 +++++++++++++++ src/pages/banners/announcements-page.tsx | 177 ++++++++++++++++++ .../banners/cancel-button-cell-renderer.tsx | 27 +++ src/pages/banners/date-cell-renderer.tsx | 40 ++++ src/pages/banners/index.ts | 8 + src/pages/index.ts | 1 + src/routes/utils.tsx | 9 + src/services/user-admin.ts | 54 ++++++ src/translations/en.json | 17 +- src/translations/fr.json | 17 +- src/utils/error.ts | 27 +++ 14 files changed, 635 insertions(+), 3 deletions(-) create mode 100644 src/pages/banners/add-announcement-form.tsx create mode 100644 src/pages/banners/announcements-page.tsx create mode 100644 src/pages/banners/cancel-button-cell-renderer.tsx create mode 100644 src/pages/banners/date-cell-renderer.tsx create mode 100644 src/pages/banners/index.ts diff --git a/package-lock.json b/package-lock.json index 4ca02df..d694add 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,11 +16,13 @@ "@mui/icons-material": "^5.16.14", "@mui/lab": "5.0.0-alpha.175", "@mui/material": "^5.16.14", + "@mui/x-date-pickers": "^7.28.3", "@mui/x-tree-view": "^6.17.0", "@reduxjs/toolkit": "^2.5.1", "ag-grid-community": "^33.1.0", "ag-grid-react": "^33.1.0", "core-js": "^3.40.0", + "dayjs": "^1.11.13", "notistack": "^3.0.2", "oidc-client": "^1.11.5", "react": "^18.3.1", @@ -4063,6 +4065,90 @@ } } }, + "node_modules/@mui/x-date-pickers": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.28.3.tgz", + "integrity": "sha512-5umKB/DIMfDN+FAlzcrocix9PpoJDJ+5hMdlby8spTPObP4wCSN+wkEhk0vFC7qE9FAWXr4wjemaKvsNf41cCw==", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0 || ^7.0.0 || ^7.0.0-beta", + "@mui/x-internals": "7.28.0", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0 || ^7.0.0-beta", + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0 || ^7.0.0-beta", + "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2 || ^3.0.0", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "node_modules/@mui/x-internals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.28.0.tgz", + "integrity": "sha512-p4GEp/09bLDumktdIMiw+OF4p+pJOOjTG0VUvzNxjbHB9GxbBKoMcHrmyrURqoBnQpWIeFnN/QAoLMFSpfwQbw==", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0 || ^7.0.0 || ^7.0.0-beta" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@mui/x-tree-view": { "version": "6.17.0", "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-6.17.0.tgz", @@ -6977,6 +7063,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", diff --git a/package.json b/package.json index f4b6d4f..83963b1 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,13 @@ "@mui/icons-material": "^5.16.14", "@mui/lab": "5.0.0-alpha.175", "@mui/material": "^5.16.14", + "@mui/x-date-pickers": "^7.28.3", "@mui/x-tree-view": "^6.17.0", "@reduxjs/toolkit": "^2.5.1", "ag-grid-community": "^33.1.0", "ag-grid-react": "^33.1.0", "core-js": "^3.40.0", + "dayjs": "^1.11.13", "notistack": "^3.0.2", "oidc-client": "^1.11.5", "react": "^18.3.1", diff --git a/src/components/App/app-top-bar.tsx b/src/components/App/app-top-bar.tsx index e2ccef0..2d03c55 100644 --- a/src/components/App/app-top-bar.tsx +++ b/src/components/App/app-top-bar.tsx @@ -15,7 +15,7 @@ import { useState, } from 'react'; import { capitalize, Tab, Tabs, useTheme } from '@mui/material'; -import { Groups, ManageAccounts, PeopleAlt } from '@mui/icons-material'; +import { Groups, ManageAccounts, PeopleAlt, NotificationImportant } from '@mui/icons-material'; import { fetchAppsMetadata, logout, Metadata, TopBar } from '@gridsuite/commons-ui'; import { useParameterState } from '../parameters'; import { APP_NAME, PARAM_LANGUAGE, PARAM_THEME } from '../../utils/config-params'; @@ -73,6 +73,20 @@ const tabs = new Map([ ))} />, ], + [ + MainPaths.banners, + } + label={} + href={`/${MainPaths.banners}`} + value={MainPaths.banners} + key={`tab-${MainPaths.banners}`} + iconPosition="start" + LinkComponent={forwardRef>((props, ref) => ( + + ))} + />, + ], ]); const AppTopBar: FunctionComponent = () => { diff --git a/src/pages/banners/add-announcement-form.tsx b/src/pages/banners/add-announcement-form.tsx new file mode 100644 index 0000000..21880df --- /dev/null +++ b/src/pages/banners/add-announcement-form.tsx @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { CustomFormProvider, SubmitButton, useSnackMessage } from '@gridsuite/commons-ui'; +import Grid from '@mui/material/Grid'; +import { FormattedMessage, useIntl } from 'react-intl'; +import React, { FunctionComponent, useCallback } from 'react'; +import { FormControl, InputLabel, MenuItem, Select, TextField } from '@mui/material'; +import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers'; +import { UserAdminSrv, Announcement } from '../../services'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import yup from '../../utils/yup-config'; +import 'dayjs/locale/fr'; +import 'dayjs/locale/en'; +import { useParameterState } from '../../components/parameters'; +import { PARAM_LANGUAGE } from '../../utils/config-params'; +import { getErrorMessage, handleAnnouncementCreationErrors } from '../../utils/error'; + +export const MESSAGE = 'message'; +export const START_DATE = 'startDate'; +export const END_DATE = 'endDate'; +export const SEVERITY = 'severity'; + +interface AddAnnouncementProps { + onAnnouncementCreated: () => void; +} + +const AddAnnouncementForm: FunctionComponent = ({ onAnnouncementCreated }) => { + const intl = useIntl(); + const [languageLocal] = useParameterState(PARAM_LANGUAGE); + const { snackError } = useSnackMessage(); + + const formSchema = yup + .object() + .shape({ + [MESSAGE]: yup.string().trim().required(), + [START_DATE]: yup.string().required(), + [END_DATE]: yup.string().required(), + [SEVERITY]: yup.string().required(), + }) + .required(); + + const formMethods = useForm({ + resolver: yupResolver(formSchema), + }); + + const { register, setValue, handleSubmit, formState } = formMethods; + + const onSubmit = useCallback( + (params: any) => { + let startDate = new Date(params.startDate).toISOString(); + let endDate = new Date(params.endDate).toISOString(); + const newAnnouncement = { + id: crypto.randomUUID(), + message: params.message, + startDate: startDate, + endDate: endDate, + severity: params.severity, + } as Announcement; + UserAdminSrv.addAnnouncement(newAnnouncement) + .then(() => onAnnouncementCreated()) + .catch((error) => { + let errorMessage = getErrorMessage(error) ?? ''; + if (!handleAnnouncementCreationErrors(errorMessage, snackError)) { + snackError({ + headerId: 'errCreateAnnouncement', + messageTxt: errorMessage, + }); + } + }); + }, + [onAnnouncementCreated, snackError] + ); + + return ( + + + + + + + + setValue('startDate', newValue)} + /> + + + + + setValue('endDate', newValue)} + /> + + + + + + + + + + + + + + + + + + ); +}; + +export default AddAnnouncementForm; diff --git a/src/pages/banners/announcements-page.tsx b/src/pages/banners/announcements-page.tsx new file mode 100644 index 0000000..57c9795 --- /dev/null +++ b/src/pages/banners/announcements-page.tsx @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { FunctionComponent, useCallback, useMemo, useRef, useState } from 'react'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { Grid } from '@mui/material'; +import { GridTableRef } from '../../components/Grid'; +import { Announcement, UserAdminSrv } from '../../services'; +import { ColDef, GetRowIdParams, ValueGetterParams } from 'ag-grid-community'; +import AddAnnouncementForm from './add-announcement-form'; +import { DateCellRenderer } from './date-cell-renderer'; +import AgGrid from '../../components/Grid/AgGrid'; +import { useSnackMessage } from '@gridsuite/commons-ui'; +import { CancelButtonCellRenderer } from './cancel-button-cell-renderer'; +import { UUID } from 'crypto'; + +const stylesLayout = { + columnContainer: { + maxHeight: '60px', + paddingLeft: '15px', + }, +}; + +const defaultColDef: ColDef = { + editable: false, + resizable: true, + minWidth: 50, + cellRenderer: 'agAnimateSlideCellRenderer', + rowDrag: false, + sortable: true, +}; + +function getRowId(params: GetRowIdParams): string { + return params.data.id; +} + +const AnnouncementsPage: FunctionComponent = () => { + const intl = useIntl(); + const gridRef = useRef>(null); + + const { snackError } = useSnackMessage(); + + const [data, setData] = useState(null); + + const loadDataAndSave = useCallback( + function loadDataAndSave(): Promise { + return UserAdminSrv.fetchAnnouncementList().then(setData, (error) => { + snackError({ + messageTxt: error.message, + headerId: 'table.error.retrieve', + }); + }); + }, + [snackError] + ); + + const convertSeverity = useCallback( + (severity: string) => { + if (severity === UserAdminSrv.AnnouncementSeverity.INFO) { + return intl.formatMessage({ id: 'banners.table.info' }); + } else if (severity === UserAdminSrv.AnnouncementSeverity.WARN) { + return intl.formatMessage({ id: 'banners.table.warn' }); + } else { + return ''; + } + }, + [intl] + ); + + const refreshGrid = useCallback(() => { + gridRef.current.context?.refresh(); + }, []); + + const handleDeleteAnnouncement = useCallback( + (announcementId: UUID) => { + UserAdminSrv.deleteAnnouncement(announcementId).then(() => { + refreshGrid(); + }); + }, + [refreshGrid] + ); + + const columns = useMemo( + (): ColDef[] => [ + { + field: 'message', + cellDataType: 'text', + flex: 3, + lockVisible: true, + headerName: intl.formatMessage({ id: 'banners.table.message' }), + }, + { + field: 'startDate', + cellRenderer: DateCellRenderer, + flex: 3, + lockVisible: true, + headerName: intl.formatMessage({ id: 'banners.table.startDate' }), + }, + { + field: 'endDate', + cellRenderer: DateCellRenderer, + flex: 3, + lockVisible: true, + headerName: intl.formatMessage({ id: 'banners.table.endDate' }), + }, + { + field: 'severity', + cellDataType: 'text', + flex: 2, + lockVisible: true, + headerName: intl.formatMessage({ id: 'banners.table.severity' }), + valueGetter: (value: ValueGetterParams) => convertSeverity(value.data.severity), + }, + { + field: 'id', + cellRenderer: CancelButtonCellRenderer, + cellRendererParams: { + onClickHandler: handleDeleteAnnouncement, + }, + flex: 2, + lockVisible: true, + headerName: intl.formatMessage({ id: 'banners.table.cancel' }), + }, + ], + [intl, convertSeverity, handleDeleteAnnouncement] + ); + + return ( + <> + + + +

+ +

+
+ + + +
+ + + +

+ +

+
+ + + + ref={gridRef} + rowData={data} + alwaysShowVerticalScroll={true} + onGridReady={loadDataAndSave} + columnDefs={columns} + defaultColDef={defaultColDef} + gridId="table-banners" + getRowId={getRowId} + context={useMemo( + () => ({ + refresh: loadDataAndSave, + }), + [loadDataAndSave] + )} + /> + + +
+
+ + ); +}; +export default AnnouncementsPage; diff --git a/src/pages/banners/cancel-button-cell-renderer.tsx b/src/pages/banners/cancel-button-cell-renderer.tsx new file mode 100644 index 0000000..bb7773f --- /dev/null +++ b/src/pages/banners/cancel-button-cell-renderer.tsx @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import { Box, IconButton } from '@mui/material'; +import { DisabledByDefault } from '@mui/icons-material'; +import { UUID } from 'crypto'; + +export type CancelButtonCellRendererProps = { value: UUID; onClickHandler: Function }; + +export function CancelButtonCellRenderer({ value, onClickHandler }: Readonly) { + return ( + + { + onClickHandler(value); + }} + > + + + + ); +} diff --git a/src/pages/banners/date-cell-renderer.tsx b/src/pages/banners/date-cell-renderer.tsx new file mode 100644 index 0000000..80d7021 --- /dev/null +++ b/src/pages/banners/date-cell-renderer.tsx @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import { useIntl } from 'react-intl'; +import { Box, Tooltip } from '@mui/material'; + +export type DateCellRendererProps = { value: string }; + +export function DateCellRenderer({ value }: Readonly) { + const intl = useIntl(); + + const dateValue = new Date(value); + if (!Number.isNaN(dateValue.getDate())) { + const time = new Intl.DateTimeFormat(intl.locale, { + timeStyle: 'medium', + hour12: false, + }).format(dateValue); + const displayedDate = + intl.locale === 'en' ? dateValue.toISOString().substring(0, 10) : dateValue.toLocaleDateString(intl.locale); + + const cellText = displayedDate + ' ' + time; + + const fullDate = new Intl.DateTimeFormat(intl.locale, { + dateStyle: 'long', + timeStyle: 'long', + hour12: false, + }).format(dateValue); + + return ( + + + {cellText} + + + ); + } +} diff --git a/src/pages/banners/index.ts b/src/pages/banners/index.ts new file mode 100644 index 0000000..79d51d8 --- /dev/null +++ b/src/pages/banners/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +export { default as Banners } from './announcements-page'; diff --git a/src/pages/index.ts b/src/pages/index.ts index bdfb9db..fd8439e 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -8,3 +8,4 @@ export * from './users'; export * from './profiles'; export * from './groups'; +export * from './banners'; diff --git a/src/routes/utils.tsx b/src/routes/utils.tsx index c5d4a22..4963e23 100644 --- a/src/routes/utils.tsx +++ b/src/routes/utils.tsx @@ -11,11 +11,13 @@ import ErrorPage from './ErrorPage'; import HomePage from './HomePage'; import { getPreLoginPath } from '@gridsuite/commons-ui'; import { FormattedMessage } from 'react-intl'; +import { Banners } from '../pages/banners'; export enum MainPaths { users = 'users', profiles = 'profiles', groups = 'groups', + banners = 'banners', } export function appRoutes(): RouteObject[] { @@ -49,6 +51,13 @@ export function appRoutes(): RouteObject[] { appBar_tab: MainPaths.groups, }, }, + { + path: `/${MainPaths.banners}`, + element: , + handle: { + appBar_tab: MainPaths.banners, + }, + }, ], }, { diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index fcd2636..ff80a82 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -284,3 +284,57 @@ export function addGroup(group: string): Promise { throw reason; }); } + +export enum AnnouncementSeverity { + INFO = 'INFO', + WARN = 'WARN', +} + +export function sanitizeString(val: string | null | undefined) { + const trimedValue = val?.trim(); + return trimedValue === '' ? null : trimedValue; +} + +export type Announcement = { + id: UUID; + startDate: string; + endDate: string; + message: string; + severity: string; +}; + +export function addAnnouncement(announcement: Announcement): Promise { + console.debug(`Creating announcement ...`); + return backendFetch( + `${USER_ADMIN_URL}/announcements/${announcement.id}?startDate=${announcement.startDate}&endDate=${announcement.endDate}&severity=${announcement.severity}`, + { + method: 'post', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: sanitizeString(announcement.message), + } + ) + .then(() => undefined) + .catch((reason) => { + console.error(`Error while creating announcement : ${reason}`); + throw reason; + }); +} + +export function fetchAnnouncementList(): Promise { + console.debug(`Fetching announcement ...`); + return backendFetchJson(`${USER_ADMIN_URL}/announcements`, { method: 'get' }).catch((reason) => { + console.error(`Error while fetching announcement : ${reason}`); + throw reason; + }); +} + +export function deleteAnnouncement(announcementId: UUID): Promise { + console.debug(`Deleting announcement ...`); + return backendFetch(`${USER_ADMIN_URL}/announcements/${announcementId}`, { method: 'delete' }).catch((reason) => { + console.error(`Error while deleting announcement : ${reason}`); + throw reason; + }); +} diff --git a/src/translations/en.json b/src/translations/en.json index 0e95a5b..8598e11 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -16,6 +16,7 @@ "appBar.tabs.profiles": "Profiles", "appBar.tabs.groups": "Groups", "appBar.tabs.connections": "Connections", + "appBar.tabs.warningBanner": "Warning banner", "table.noRows": "No data", "table.error.retrieve": "Error while retrieving data", @@ -104,5 +105,19 @@ "groups.form.field.group.label": "Group ID", "linked.path.display.noLink": "no configuration selected.", - "linked.path.display.invalidLink": "invalid configurations link." + "linked.path.display.invalidLink": "invalid configurations link.", + + "banners.programNewMessage": "Program new message", + "banners.programmedMessage": "Programmed messages", + "banners.table.message": "Message", + "banners.table.startDate": "Start date", + "banners.table.endDate": "End date", + "banners.table.severity": "Severity", + "banners.table.cancel": "Cancel", + "banners.table.info": "Info", + "banners.table.warn": "Warning", + "errCreateAnnouncement": "Error while creating announcement: ", + "noOverlapAllowedErr": "The date overlaps with another announcement date", + "noSameDateErr": "The announcement start and end date must be different", + "startDateAfterEndDateErr": "The start date cannot be after the end date" } diff --git a/src/translations/fr.json b/src/translations/fr.json index 8852960..dd58195 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -16,6 +16,7 @@ "appBar.tabs.profiles": "Profils", "appBar.tabs.groups": "Groupes", "appBar.tabs.connections": "Connexions", + "appBar.tabs.warningBanner": "Bandeau d'avertissement", "table.noRows": "No data", "table.id": "ID", @@ -105,5 +106,19 @@ "groups.form.field.group.label": "ID groupe", "linked.path.display.noLink": "pas de configuration selectionnée.", - "linked.path.display.invalidLink": "lien vers configurations invalide." + "linked.path.display.invalidLink": "lien vers configurations invalide.", + + "banners.programNewMessage": "Programmer un nouveau message", + "banners.programmedMessage": "Messages programmés", + "banners.table.message": "Message", + "banners.table.startDate": "Date de début", + "banners.table.endDate": "Date de fin", + "banners.table.severity": "Sévérité", + "banners.table.cancel": "Annulation", + "banners.table.info": "Info", + "banners.table.warn": "Attention", + "errCreateAnnouncement": "Erreur lors de la création de l'annonce : ", + "noOverlapAllowedErr": "La date d'annonce chevauche une autre date d'annonce", + "noSameDateErr": "La date de début et de fin d'annonce doivent être différentes", + "startDateAfterEndDateErr": "La date de début d'annonce ne peut pas être après la date de fin" } diff --git a/src/utils/error.ts b/src/utils/error.ts index af9d81c..d9e0537 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -24,3 +24,30 @@ export function getErrorMessage(error: unknown): string | null { return JSON.stringify(error ?? undefined) ?? null; } } + +const OVERLAPPING_ANNOUNCEMENTS = 'OVERLAPPING_ANNOUNCEMENTS'; +const SAME_START_END_DATE = 'SAME_START_END_DATE'; +const START_DATE_AFTER_END_DATE = 'START_DATE_AFTER_END_DATE'; + +export function handleAnnouncementCreationErrors(error: string, snackError: Function): boolean { + if (error.includes(OVERLAPPING_ANNOUNCEMENTS)) { + snackError({ + headerId: 'errCreateAnnouncement', + messageId: 'noOverlapAllowedErr', + }); + return true; + } else if (error.includes(SAME_START_END_DATE)) { + snackError({ + headerId: 'errCreateAnnouncement', + messageId: 'noSameDateErr', + }); + return true; + } else if (error.includes(START_DATE_AFTER_END_DATE)) { + snackError({ + headerId: 'errCreateAnnouncement', + messageId: 'startDateAfterEndDateErr', + }); + return true; + } + return false; +} From 80c4b97d0a24d507bda7b0c6fcebebb21710f6b6 Mon Sep 17 00:00:00 2001 From: Abdelsalem Date: Mon, 14 Apr 2025 12:26:33 +0200 Subject: [PATCH 02/23] fix ts Signed-off-by: Abdelsalem --- src/pages/banners/add-announcement-form.tsx | 4 ++-- src/pages/banners/announcements-page.tsx | 2 +- src/services/user-admin.ts | 9 +++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/pages/banners/add-announcement-form.tsx b/src/pages/banners/add-announcement-form.tsx index 21880df..6088344 100644 --- a/src/pages/banners/add-announcement-form.tsx +++ b/src/pages/banners/add-announcement-form.tsx @@ -98,7 +98,7 @@ const AddAnnouncementForm: FunctionComponent = ({ onAnnoun {...register('startDate')} name={START_DATE} label={intl.formatMessage({ id: 'banners.table.startDate' })} - onChange={(newValue) => setValue('startDate', newValue)} + onChange={(newValue) => setValue('startDate', newValue?.toString ?? '')} /> @@ -108,7 +108,7 @@ const AddAnnouncementForm: FunctionComponent = ({ onAnnoun {...register('endDate')} name={END_DATE} label={intl.formatMessage({ id: 'banners.table.endDate' })} - onChange={(newValue) => setValue('endDate', newValue)} + onChange={(newValue) => setValue('endDate', newValue?.toString ?? '')} /> diff --git a/src/pages/banners/announcements-page.tsx b/src/pages/banners/announcements-page.tsx index 57c9795..9508b3f 100644 --- a/src/pages/banners/announcements-page.tsx +++ b/src/pages/banners/announcements-page.tsx @@ -72,7 +72,7 @@ const AnnouncementsPage: FunctionComponent = () => { ); const refreshGrid = useCallback(() => { - gridRef.current.context?.refresh(); + gridRef.current?.context?.refresh(); }, []); const handleDeleteAnnouncement = useCallback( diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index ff80a82..3cafd23 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -328,7 +328,7 @@ export function fetchAnnouncementList(): Promise { return backendFetchJson(`${USER_ADMIN_URL}/announcements`, { method: 'get' }).catch((reason) => { console.error(`Error while fetching announcement : ${reason}`); throw reason; - }); + }) as Promise; } export function deleteAnnouncement(announcementId: UUID): Promise { @@ -336,5 +336,10 @@ export function deleteAnnouncement(announcementId: UUID): Promise { return backendFetch(`${USER_ADMIN_URL}/announcements/${announcementId}`, { method: 'delete' }).catch((reason) => { console.error(`Error while deleting announcement : ${reason}`); throw reason; - }); + }) + .then(() => undefined) + .catch((reason) => { + console.error(`Error while creating announcement : ${reason}`); + throw reason; + }); } From 2f7cc0fc7fd2104443575073d3ac1bc7cca22d75 Mon Sep 17 00:00:00 2001 From: Abdelsalem Date: Tue, 15 Apr 2025 10:26:19 +0200 Subject: [PATCH 03/23] remove useless code Signed-off-by: Abdelsalem --- src/pages/banners/add-announcement-form.tsx | 4 ++-- src/services/user-admin.ts | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/pages/banners/add-announcement-form.tsx b/src/pages/banners/add-announcement-form.tsx index 6088344..21880df 100644 --- a/src/pages/banners/add-announcement-form.tsx +++ b/src/pages/banners/add-announcement-form.tsx @@ -98,7 +98,7 @@ const AddAnnouncementForm: FunctionComponent = ({ onAnnoun {...register('startDate')} name={START_DATE} label={intl.formatMessage({ id: 'banners.table.startDate' })} - onChange={(newValue) => setValue('startDate', newValue?.toString ?? '')} + onChange={(newValue) => setValue('startDate', newValue)} /> @@ -108,7 +108,7 @@ const AddAnnouncementForm: FunctionComponent = ({ onAnnoun {...register('endDate')} name={END_DATE} label={intl.formatMessage({ id: 'banners.table.endDate' })} - onChange={(newValue) => setValue('endDate', newValue?.toString ?? '')} + onChange={(newValue) => setValue('endDate', newValue)} /> diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index 3cafd23..03acb3e 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -333,13 +333,10 @@ export function fetchAnnouncementList(): Promise { export function deleteAnnouncement(announcementId: UUID): Promise { console.debug(`Deleting announcement ...`); - return backendFetch(`${USER_ADMIN_URL}/announcements/${announcementId}`, { method: 'delete' }).catch((reason) => { - console.error(`Error while deleting announcement : ${reason}`); - throw reason; - }) + return backendFetch(`${USER_ADMIN_URL}/announcements/${announcementId}`, { method: 'delete' }) .then(() => undefined) .catch((reason) => { - console.error(`Error while creating announcement : ${reason}`); + console.error(`Error while deleting announcement : ${reason}`); throw reason; }); } From 6ae929abb4c75d9e5fd4f9b89abaa3ecfb009963 Mon Sep 17 00:00:00 2001 From: Abdelsalem Date: Tue, 15 Apr 2025 11:33:40 +0200 Subject: [PATCH 04/23] listen for announcement Signed-off-by: Abdelsalem --- src/components/App/app-top-bar.tsx | 31 ++++++++++++++++++++++++++++- src/utils/notifications-provider.ts | 4 ++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/components/App/app-top-bar.tsx b/src/components/App/app-top-bar.tsx index 311fcc1..403bedd 100644 --- a/src/components/App/app-top-bar.tsx +++ b/src/components/App/app-top-bar.tsx @@ -16,7 +16,7 @@ import { } from 'react'; import { capitalize, Tab, Tabs, useTheme } from '@mui/material'; import { Groups, ManageAccounts, PeopleAlt, NotificationImportant } from '@mui/icons-material'; -import { fetchAppsMetadata, logout, Metadata, TopBar } from '@gridsuite/commons-ui'; +import { fetchAppsMetadata, logout, Metadata, TopBar, useNotificationsListener } from '@gridsuite/commons-ui'; import { useParameterState } from '../parameters'; import { APP_NAME, PARAM_LANGUAGE, PARAM_THEME } from '../../utils/config-params'; import { NavLink, type To, useMatches, useNavigate } from 'react-router'; @@ -29,6 +29,7 @@ import AppPackage from '../../../package.json'; import { AppState } from '../../redux/reducer'; import { AppDispatch } from '../../redux/store'; import { MainPaths } from '../../routes/utils'; +import { NOTIFICATIONS_URL_KEYS } from '../../utils/notifications-provider'; const tabs = new Map([ [ @@ -110,6 +111,33 @@ const AppTopBar: FunctionComponent = () => { const [languageLocal, handleChangeLanguage] = useParameterState(PARAM_LANGUAGE); const [appsAndUrls, setAppsAndUrls] = useState([]); + + const [announcementInfos, setAnnouncementInfos] = useState(null); + + useNotificationsListener(NOTIFICATIONS_URL_KEYS.GLOBAL_CONFIG, { + listenerCallbackMessage: (event) => { + const eventData = JSON.parse(event.data); + if (eventData.headers.messageType === 'announcement') { + if ( + announcementInfos != null && + announcementInfos.announcementId === eventData.headers.announcementId + ) { + // If we receive a notification for an announcement that we already received we ignore it + return; + } + const announcement = { + announcementId: eventData.headers.announcementId, + message: eventData.payload, + severity: eventData.headers.severity, + duration: eventData.headers.duration, + } as AnnouncementProps; + setAnnouncementInfos(announcement); + } else if (eventData.headers.messageType === 'cancelAnnouncement') { + setAnnouncementInfos(null); + } + }, + }); + useEffect(() => { if (user !== null) { fetchAppsMetadata().then((res) => { @@ -136,6 +164,7 @@ const AppTopBar: FunctionComponent = () => { onLanguageClick={handleChangeLanguage} language={languageLocal} developerMode={false} // TODO: set as optional in commons-ui + announcementInfos={announcementInfos} > , [wsBase, tokenId] ); From 88cb9b18a1adaf9b1af97e53271fb0539f7bb5c4 Mon Sep 17 00:00:00 2001 From: Abdelsalem Date: Tue, 15 Apr 2025 12:33:06 +0200 Subject: [PATCH 05/23] minor refacto Signed-off-by: Abdelsalem --- src/pages/banners/add-announcement-form.tsx | 2 +- src/translations/en.json | 5 +++-- src/translations/fr.json | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/banners/add-announcement-form.tsx b/src/pages/banners/add-announcement-form.tsx index 21880df..c7a9a87 100644 --- a/src/pages/banners/add-announcement-form.tsx +++ b/src/pages/banners/add-announcement-form.tsx @@ -85,7 +85,7 @@ const AddAnnouncementForm: FunctionComponent = ({ onAnnoun Date: Fri, 18 Apr 2025 16:01:12 +0200 Subject: [PATCH 06/23] refactor: use useGlobalAnnouncement --- src/components/App/app-top-bar.tsx | 31 ++------------------- src/pages/banners/add-announcement-form.tsx | 4 +-- src/services/user-admin.ts | 2 +- src/utils/notifications-provider.ts | 14 +++------- 4 files changed, 10 insertions(+), 41 deletions(-) diff --git a/src/components/App/app-top-bar.tsx b/src/components/App/app-top-bar.tsx index 403bedd..f16cd65 100644 --- a/src/components/App/app-top-bar.tsx +++ b/src/components/App/app-top-bar.tsx @@ -15,8 +15,8 @@ import { useState, } from 'react'; import { capitalize, Tab, Tabs, useTheme } from '@mui/material'; -import { Groups, ManageAccounts, PeopleAlt, NotificationImportant } from '@mui/icons-material'; -import { fetchAppsMetadata, logout, Metadata, TopBar, useNotificationsListener } from '@gridsuite/commons-ui'; +import { Groups, ManageAccounts, NotificationImportant, PeopleAlt } from '@mui/icons-material'; +import { fetchAppsMetadata, logout, Metadata, TopBar, useGlobalAnnouncement } from '@gridsuite/commons-ui'; import { useParameterState } from '../parameters'; import { APP_NAME, PARAM_LANGUAGE, PARAM_THEME } from '../../utils/config-params'; import { NavLink, type To, useMatches, useNavigate } from 'react-router'; @@ -29,7 +29,6 @@ import AppPackage from '../../../package.json'; import { AppState } from '../../redux/reducer'; import { AppDispatch } from '../../redux/store'; import { MainPaths } from '../../routes/utils'; -import { NOTIFICATIONS_URL_KEYS } from '../../utils/notifications-provider'; const tabs = new Map([ [ @@ -112,31 +111,7 @@ const AppTopBar: FunctionComponent = () => { const [appsAndUrls, setAppsAndUrls] = useState([]); - const [announcementInfos, setAnnouncementInfos] = useState(null); - - useNotificationsListener(NOTIFICATIONS_URL_KEYS.GLOBAL_CONFIG, { - listenerCallbackMessage: (event) => { - const eventData = JSON.parse(event.data); - if (eventData.headers.messageType === 'announcement') { - if ( - announcementInfos != null && - announcementInfos.announcementId === eventData.headers.announcementId - ) { - // If we receive a notification for an announcement that we already received we ignore it - return; - } - const announcement = { - announcementId: eventData.headers.announcementId, - message: eventData.payload, - severity: eventData.headers.severity, - duration: eventData.headers.duration, - } as AnnouncementProps; - setAnnouncementInfos(announcement); - } else if (eventData.headers.messageType === 'cancelAnnouncement') { - setAnnouncementInfos(null); - } - }, - }); + const announcementInfos = useGlobalAnnouncement(user); useEffect(() => { if (user !== null) { diff --git a/src/pages/banners/add-announcement-form.tsx b/src/pages/banners/add-announcement-form.tsx index c7a9a87..ba6a339 100644 --- a/src/pages/banners/add-announcement-form.tsx +++ b/src/pages/banners/add-announcement-form.tsx @@ -98,7 +98,7 @@ const AddAnnouncementForm: FunctionComponent = ({ onAnnoun {...register('startDate')} name={START_DATE} label={intl.formatMessage({ id: 'banners.table.startDate' })} - onChange={(newValue) => setValue('startDate', newValue)} + onChange={(newValue) => setValue('startDate', newValue?.toISOString() ?? '')} /> @@ -108,7 +108,7 @@ const AddAnnouncementForm: FunctionComponent = ({ onAnnoun {...register('endDate')} name={END_DATE} label={intl.formatMessage({ id: 'banners.table.endDate' })} - onChange={(newValue) => setValue('endDate', newValue)} + onChange={(newValue) => setValue('endDate', newValue?.toISOString() ?? '')} /> diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index 03acb3e..c325065 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -306,7 +306,7 @@ export type Announcement = { export function addAnnouncement(announcement: Announcement): Promise { console.debug(`Creating announcement ...`); return backendFetch( - `${USER_ADMIN_URL}/announcements/${announcement.id}?startDate=${announcement.startDate}&endDate=${announcement.endDate}&severity=${announcement.severity}`, + `${USER_ADMIN_URL}/announcements?startDate=${announcement.startDate}&endDate=${announcement.endDate}&severity=${announcement.severity}`, { method: 'post', headers: { diff --git a/src/utils/notifications-provider.ts b/src/utils/notifications-provider.ts index ea3556d..a958a20 100644 --- a/src/utils/notifications-provider.ts +++ b/src/utils/notifications-provider.ts @@ -10,13 +10,7 @@ import { useSelector } from 'react-redux'; import type { AppState } from '../redux/reducer'; import { getUrlWithToken, getWsBase } from './api-ws'; import { APP_NAME } from './config-params'; - -export enum NOTIFICATIONS_URL_KEYS { - CONFIG = 'CONFIG', - GLOBAL_CONFIG = 'GLOBAL_CONFIG', -} - -export const PREFIX_CONFIG_NOTIFICATION_WS = '/config-notification'; +import { NotificationsUrlKeys, PREFIX_CONFIG_NOTIFICATION_WS } from '@gridsuite/commons-ui'; export function useNotificationsUrlGenerator() { // The websocket API doesn't allow relative urls @@ -28,17 +22,17 @@ export function useNotificationsUrlGenerator() { return useMemo( () => ({ - [NOTIFICATIONS_URL_KEYS.CONFIG]: tokenId + [NotificationsUrlKeys.CONFIG]: tokenId ? getUrlWithToken( `${wsBase}${PREFIX_CONFIG_NOTIFICATION_WS}/notify?${new URLSearchParams({ appName: APP_NAME, })}` ) : undefined, - [NOTIFICATIONS_URL_KEYS.GLOBAL_CONFIG]: tokenId + [NotificationsUrlKeys.GLOBAL_CONFIG]: tokenId ? getUrlWithToken(`${wsBase}${PREFIX_CONFIG_NOTIFICATION_WS}/global`) : undefined, - }) satisfies Record, + }) satisfies { [Key in NotificationsUrlKeys]?: string | undefined }, [wsBase, tokenId] ); } From d30fcf2f178184a8d2872c8cc8f87fe23cf0c282 Mon Sep 17 00:00:00 2001 From: Joris Mancini Date: Fri, 18 Apr 2025 17:12:31 +0200 Subject: [PATCH 07/23] feat: use useGlobalAnnouncement --- src/components/App/app.tsx | 10 +++++++--- src/utils/notifications-provider.ts | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/App/app.tsx b/src/components/App/app.tsx index e08d06e..8cb99a0 100644 --- a/src/components/App/app.tsx +++ b/src/components/App/app.tsx @@ -8,7 +8,12 @@ import { FunctionComponent, PropsWithChildren, useCallback, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Grid } from '@mui/material'; -import { CardErrorBoundary, useNotificationsListener, useSnackMessage } from '@gridsuite/commons-ui'; +import { + CardErrorBoundary, + NotificationsUrlKeys, + useNotificationsListener, + useSnackMessage, +} from '@gridsuite/commons-ui'; import { selectComputedLanguage, selectLanguage, selectTheme } from '../../redux/actions'; import { AppState } from '../../redux/reducer'; import { ConfigParameters, ConfigSrv } from '../../services'; @@ -17,7 +22,6 @@ import { getComputedLanguage } from '../../utils/language'; import AppTopBar from './app-top-bar'; import { useDebugRender } from '../../utils/hooks'; import { AppDispatch } from '../../redux/store'; -import { NOTIFICATIONS_URL_KEYS } from '../../utils/notifications-provider'; const App: FunctionComponent> = (props, context) => { useDebugRender('app'); @@ -59,7 +63,7 @@ const App: FunctionComponent> = (props, context) => { [updateParams, snackError] ); - useNotificationsListener(NOTIFICATIONS_URL_KEYS.CONFIG, { listenerCallbackMessage: updateConfig }); + useNotificationsListener(NotificationsUrlKeys.CONFIG, { listenerCallbackMessage: updateConfig }); useEffect(() => { if (user !== null) { diff --git a/src/utils/notifications-provider.ts b/src/utils/notifications-provider.ts index a958a20..8abcfd0 100644 --- a/src/utils/notifications-provider.ts +++ b/src/utils/notifications-provider.ts @@ -32,7 +32,7 @@ export function useNotificationsUrlGenerator() { [NotificationsUrlKeys.GLOBAL_CONFIG]: tokenId ? getUrlWithToken(`${wsBase}${PREFIX_CONFIG_NOTIFICATION_WS}/global`) : undefined, - }) satisfies { [Key in NotificationsUrlKeys]?: string | undefined }, + }) satisfies Partial>, [wsBase, tokenId] ); } From 4a36ae2bc2833c6f2dbc06d5de03db4b4284f046 Mon Sep 17 00:00:00 2001 From: Joris Mancini Date: Fri, 18 Apr 2025 17:36:00 +0200 Subject: [PATCH 08/23] fix: websocket url replacement method --- src/utils/api-ws.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/utils/api-ws.ts b/src/utils/api-ws.ts index 84b37a9..1689a76 100644 --- a/src/utils/api-ws.ts +++ b/src/utils/api-ws.ts @@ -9,10 +9,7 @@ import { getToken } from './api'; export type * from './api'; -export function getWsBase(): string { - // We use the `baseURI` (from `` in index.html) to build the URL, which is corrected by httpd/nginx - return document.baseURI.replace(/^http(s?):\/\//, 'ws$1://').replace(/\/+$/, '') + import.meta.env.VITE_WS_GATEWAY; -} +export const getWsBase = () => document.baseURI.replace(/^http:\/\//, 'ws://').replace(/^https:\/\//, 'wss://'); export function getUrlWithToken(baseUrl: string): string { const querySymbol = baseUrl.includes('?') ? '&' : '?'; From 04a66a6ede9b3c2def58f95c4aa3cd7aeee5c141 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Fri, 25 Apr 2025 09:27:06 +0200 Subject: [PATCH 09/23] Update commons-ui --- src/components/App/app-top-bar.tsx | 5 +---- src/components/App/app.tsx | 4 ++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/App/app-top-bar.tsx b/src/components/App/app-top-bar.tsx index f16cd65..4a20a7d 100644 --- a/src/components/App/app-top-bar.tsx +++ b/src/components/App/app-top-bar.tsx @@ -16,7 +16,7 @@ import { } from 'react'; import { capitalize, Tab, Tabs, useTheme } from '@mui/material'; import { Groups, ManageAccounts, NotificationImportant, PeopleAlt } from '@mui/icons-material'; -import { fetchAppsMetadata, logout, Metadata, TopBar, useGlobalAnnouncement } from '@gridsuite/commons-ui'; +import { fetchAppsMetadata, logout, Metadata, TopBar } from '@gridsuite/commons-ui'; import { useParameterState } from '../parameters'; import { APP_NAME, PARAM_LANGUAGE, PARAM_THEME } from '../../utils/config-params'; import { NavLink, type To, useMatches, useNavigate } from 'react-router'; @@ -111,8 +111,6 @@ const AppTopBar: FunctionComponent = () => { const [appsAndUrls, setAppsAndUrls] = useState([]); - const announcementInfos = useGlobalAnnouncement(user); - useEffect(() => { if (user !== null) { fetchAppsMetadata().then((res) => { @@ -139,7 +137,6 @@ const AppTopBar: FunctionComponent = () => { onLanguageClick={handleChangeLanguage} language={languageLocal} developerMode={false} // TODO: set as optional in commons-ui - announcementInfos={announcementInfos} > > = (props, context) => { sx={{ height: '100vh', width: '100vw' }} > + + + {/*Router outlet ->*/ props.children} From 99134f3f5491f96449533623f9326b364bb7dea7 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Fri, 25 Apr 2025 14:35:59 +0200 Subject: [PATCH 10/23] rename banner to announcement --- package-lock.json | 6 +- package.json | 2 +- src/components/App/app-top-bar.tsx | 8 +- src/components/Grid/AgGrid.tsx | 2 +- .../add-announcement-form.tsx | 36 +++--- .../announcements-page.tsx | 109 ++++++++---------- .../cancel-button-cell-renderer.tsx | 0 .../date-cell-renderer.tsx | 0 src/pages/{banners => announcements}/index.ts | 2 +- src/pages/index.ts | 2 +- src/routes/utils.tsx | 10 +- src/translations/en.json | 28 ++--- src/translations/fr.json | 28 ++--- src/utils/error.ts | 12 +- src/utils/notifications-provider.ts | 4 +- 15 files changed, 116 insertions(+), 133 deletions(-) rename src/pages/{banners => announcements}/add-announcement-form.tsx (82%) rename src/pages/{banners => announcements}/announcements-page.tsx (54%) rename src/pages/{banners => announcements}/cancel-button-cell-renderer.tsx (100%) rename src/pages/{banners => announcements}/date-cell-renderer.tsx (100%) rename src/pages/{banners => announcements}/index.ts (80%) diff --git a/package-lock.json b/package-lock.json index 1ecb023..f02cf1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@gridsuite/commons-ui": "0.95.0", + "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.95.0.tgz", "@hookform/resolvers": "^4.0.0", "@mui/icons-material": "^5.16.14", "@mui/lab": "5.0.0-alpha.175", @@ -3074,8 +3074,8 @@ }, "node_modules/@gridsuite/commons-ui": { "version": "0.95.0", - "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.95.0.tgz", - "integrity": "sha512-squCtrVorLTHo0R4DitiHN9EAnuABc02sm1x1q5UstIbRZNPO/wS9YkZMcFO6ce+hJ1KBDSP7rmy5BXnJxswrg==", + "resolved": "file:../commons-ui/gridsuite-commons-ui-0.95.0.tgz", + "integrity": "sha512-RQySsQY87B+YV6B5U7DWHNMjI4A8E7lFIzhOnaiIwgtlaPpujutQUQQUL+35El1HbUCjRjy10zdppozUVJTneQ==", "license": "MPL-2.0", "dependencies": { "@ag-grid-community/locale": "^33.1.0", diff --git a/package.json b/package.json index f714abb..260f552 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@gridsuite/commons-ui": "0.95.0", + "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.95.0.tgz", "@hookform/resolvers": "^4.0.0", "@mui/icons-material": "^5.16.14", "@mui/lab": "5.0.0-alpha.175", diff --git a/src/components/App/app-top-bar.tsx b/src/components/App/app-top-bar.tsx index 4a20a7d..0293ec7 100644 --- a/src/components/App/app-top-bar.tsx +++ b/src/components/App/app-top-bar.tsx @@ -74,13 +74,13 @@ const tabs = new Map([ />, ], [ - MainPaths.banners, + MainPaths.announcements, } label={} - href={`/${MainPaths.banners}`} - value={MainPaths.banners} - key={`tab-${MainPaths.banners}`} + href={`/${MainPaths.announcements}`} + value={MainPaths.announcements} + key={`tab-${MainPaths.announcements}`} iconPosition="start" LinkComponent={forwardRef>((props, ref) => ( diff --git a/src/components/Grid/AgGrid.tsx b/src/components/Grid/AgGrid.tsx index 000d941..92c2f9f 100644 --- a/src/components/Grid/AgGrid.tsx +++ b/src/components/Grid/AgGrid.tsx @@ -37,7 +37,7 @@ type ForwardRef = typeof forwardRef; type ForwardRefComponent = ReturnType>; interface AgGridWithRef extends FunctionComponent> { - ( + ( props: PropsWithoutRef> & RefAttributes> ): ReturnType, AgGridRef>>; } diff --git a/src/pages/banners/add-announcement-form.tsx b/src/pages/announcements/add-announcement-form.tsx similarity index 82% rename from src/pages/banners/add-announcement-form.tsx rename to src/pages/announcements/add-announcement-form.tsx index ba6a339..c2a47a3 100644 --- a/src/pages/banners/add-announcement-form.tsx +++ b/src/pages/announcements/add-announcement-form.tsx @@ -31,7 +31,7 @@ interface AddAnnouncementProps { onAnnouncementCreated: () => void; } -const AddAnnouncementForm: FunctionComponent = ({ onAnnouncementCreated }) => { +export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly) { const intl = useIntl(); const [languageLocal] = useParameterState(PARAM_LANGUAGE); const { snackError } = useSnackMessage(); @@ -39,17 +39,15 @@ const AddAnnouncementForm: FunctionComponent = ({ onAnnoun const formSchema = yup .object() .shape({ - [MESSAGE]: yup.string().trim().required(), - [START_DATE]: yup.string().required(), - [END_DATE]: yup.string().required(), - [SEVERITY]: yup.string().required(), + [MESSAGE]: yup.string().trim().required(), // TODO not empty + [START_DATE]: yup.string().required(), //TODO date + [END_DATE]: yup.string().required(), // TODO date + [SEVERITY]: yup.string().required(), // TODO enum }) .required(); - const formMethods = useForm({ resolver: yupResolver(formSchema), }); - const { register, setValue, handleSubmit, formState } = formMethods; const onSubmit = useCallback( @@ -69,7 +67,7 @@ const AddAnnouncementForm: FunctionComponent = ({ onAnnoun let errorMessage = getErrorMessage(error) ?? ''; if (!handleAnnouncementCreationErrors(errorMessage, snackError)) { snackError({ - headerId: 'errCreateAnnouncement', + headerId: 'announcements.form.errCreateAnnouncement', messageTxt: errorMessage, }); } @@ -85,7 +83,7 @@ const AddAnnouncementForm: FunctionComponent = ({ onAnnoun = ({ onAnnoun setValue('startDate', newValue?.toISOString() ?? '')} /> @@ -107,34 +105,32 @@ const AddAnnouncementForm: FunctionComponent = ({ onAnnoun setValue('endDate', newValue?.toISOString() ?? '')} /> - - + - = ({ onAnnoun ); -}; - -export default AddAnnouncementForm; +} diff --git a/src/pages/banners/announcements-page.tsx b/src/pages/announcements/announcements-page.tsx similarity index 54% rename from src/pages/banners/announcements-page.tsx rename to src/pages/announcements/announcements-page.tsx index 9508b3f..d82b849 100644 --- a/src/pages/banners/announcements-page.tsx +++ b/src/pages/announcements/announcements-page.tsx @@ -5,25 +5,26 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback, useMemo, useRef, useState } from 'react'; +import type { UUID } from 'crypto'; +import { useCallback, useMemo, useRef, useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; -import { Grid } from '@mui/material'; -import { GridTableRef } from '../../components/Grid'; +import { Grid, type SxProps, type Theme } from '@mui/material'; +import { useSnackMessage } from '@gridsuite/commons-ui'; +import type { ColDef, GetRowIdParams, ValueGetterParams } from 'ag-grid-community'; +import { type GridTableRef } from '../../components/Grid'; import { Announcement, UserAdminSrv } from '../../services'; -import { ColDef, GetRowIdParams, ValueGetterParams } from 'ag-grid-community'; import AddAnnouncementForm from './add-announcement-form'; import { DateCellRenderer } from './date-cell-renderer'; import AgGrid from '../../components/Grid/AgGrid'; -import { useSnackMessage } from '@gridsuite/commons-ui'; import { CancelButtonCellRenderer } from './cancel-button-cell-renderer'; -import { UUID } from 'crypto'; const stylesLayout = { + root: { display: 'flex' }, columnContainer: { maxHeight: '60px', paddingLeft: '15px', }, -}; +} as const satisfies Record>; const defaultColDef: ColDef = { editable: false, @@ -34,11 +35,11 @@ const defaultColDef: ColDef = { sortable: true, }; -function getRowId(params: GetRowIdParams): string { +function getRowId(params: GetRowIdParams) { return params.data.id; } -const AnnouncementsPage: FunctionComponent = () => { +export default function AnnouncementsPage() { const intl = useIntl(); const gridRef = useRef>(null); @@ -61,9 +62,9 @@ const AnnouncementsPage: FunctionComponent = () => { const convertSeverity = useCallback( (severity: string) => { if (severity === UserAdminSrv.AnnouncementSeverity.INFO) { - return intl.formatMessage({ id: 'banners.table.info' }); + return intl.formatMessage({ id: 'announcements.severity.INFO' }); } else if (severity === UserAdminSrv.AnnouncementSeverity.WARN) { - return intl.formatMessage({ id: 'banners.table.warn' }); + return intl.formatMessage({ id: 'announcements.severity.WARN' }); } else { return ''; } @@ -72,14 +73,12 @@ const AnnouncementsPage: FunctionComponent = () => { ); const refreshGrid = useCallback(() => { - gridRef.current?.context?.refresh(); + gridRef.current?.context?.refresh?.(); }, []); const handleDeleteAnnouncement = useCallback( (announcementId: UUID) => { - UserAdminSrv.deleteAnnouncement(announcementId).then(() => { - refreshGrid(); - }); + UserAdminSrv.deleteAnnouncement(announcementId).then(refreshGrid); }, [refreshGrid] ); @@ -91,28 +90,28 @@ const AnnouncementsPage: FunctionComponent = () => { cellDataType: 'text', flex: 3, lockVisible: true, - headerName: intl.formatMessage({ id: 'banners.table.message' }), + headerName: intl.formatMessage({ id: 'announcements.table.message' }), }, { field: 'startDate', cellRenderer: DateCellRenderer, flex: 3, lockVisible: true, - headerName: intl.formatMessage({ id: 'banners.table.startDate' }), + headerName: intl.formatMessage({ id: 'announcements.table.startDate' }), }, { field: 'endDate', cellRenderer: DateCellRenderer, flex: 3, lockVisible: true, - headerName: intl.formatMessage({ id: 'banners.table.endDate' }), + headerName: intl.formatMessage({ id: 'announcements.table.endDate' }), }, { field: 'severity', cellDataType: 'text', flex: 2, lockVisible: true, - headerName: intl.formatMessage({ id: 'banners.table.severity' }), + headerName: intl.formatMessage({ id: 'announcements.severity' }), valueGetter: (value: ValueGetterParams) => convertSeverity(value.data.severity), }, { @@ -123,55 +122,47 @@ const AnnouncementsPage: FunctionComponent = () => { }, flex: 2, lockVisible: true, - headerName: intl.formatMessage({ id: 'banners.table.cancel' }), + headerName: intl.formatMessage({ id: 'announcements.table.cancel' }), }, ], [intl, convertSeverity, handleDeleteAnnouncement] ); return ( - <> - - - -

- -

-
- - - + + + +

+ +

+ + + +
- - -

- -

-
- - - - ref={gridRef} - rowData={data} - alwaysShowVerticalScroll={true} - onGridReady={loadDataAndSave} - columnDefs={columns} - defaultColDef={defaultColDef} - gridId="table-banners" - getRowId={getRowId} - context={useMemo( - () => ({ - refresh: loadDataAndSave, - }), - [loadDataAndSave] - )} - /> - + + +

+ +

+
+ + + + ref={gridRef} + rowData={data} + alwaysShowVerticalScroll + onGridReady={loadDataAndSave} + columnDefs={columns} + defaultColDef={defaultColDef} + gridId="table-banners" + getRowId={getRowId} + context={useMemo(() => ({ refresh: loadDataAndSave }), [loadDataAndSave])} + />
- +
); -}; -export default AnnouncementsPage; +} diff --git a/src/pages/banners/cancel-button-cell-renderer.tsx b/src/pages/announcements/cancel-button-cell-renderer.tsx similarity index 100% rename from src/pages/banners/cancel-button-cell-renderer.tsx rename to src/pages/announcements/cancel-button-cell-renderer.tsx diff --git a/src/pages/banners/date-cell-renderer.tsx b/src/pages/announcements/date-cell-renderer.tsx similarity index 100% rename from src/pages/banners/date-cell-renderer.tsx rename to src/pages/announcements/date-cell-renderer.tsx diff --git a/src/pages/banners/index.ts b/src/pages/announcements/index.ts similarity index 80% rename from src/pages/banners/index.ts rename to src/pages/announcements/index.ts index 79d51d8..efe0347 100644 --- a/src/pages/banners/index.ts +++ b/src/pages/announcements/index.ts @@ -5,4 +5,4 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { default as Banners } from './announcements-page'; +export { default as Announcements } from './announcements-page'; diff --git a/src/pages/index.ts b/src/pages/index.ts index fd8439e..4c5472f 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -8,4 +8,4 @@ export * from './users'; export * from './profiles'; export * from './groups'; -export * from './banners'; +export * from './announcements'; diff --git a/src/routes/utils.tsx b/src/routes/utils.tsx index ef2f555..427ee52 100644 --- a/src/routes/utils.tsx +++ b/src/routes/utils.tsx @@ -11,13 +11,13 @@ import ErrorPage from './ErrorPage'; import HomePage from './HomePage'; import { getPreLoginPath } from '@gridsuite/commons-ui'; import { FormattedMessage } from 'react-intl'; -import { Banners } from '../pages/banners'; +import { Announcements } from '../pages/announcements'; export enum MainPaths { users = 'users', profiles = 'profiles', groups = 'groups', - banners = 'banners', + announcements = 'announcements', } export function appRoutes(): RouteObject[] { @@ -52,10 +52,10 @@ export function appRoutes(): RouteObject[] { }, }, { - path: `/${MainPaths.banners}`, - element: , + path: `/${MainPaths.announcements}`, + element: , handle: { - appBar_tab: MainPaths.banners, + appBar_tab: MainPaths.announcements, }, }, ], diff --git a/src/translations/en.json b/src/translations/en.json index 7ca7ba9..8311a6f 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -108,18 +108,18 @@ "linked.path.display.noLink": "no configuration selected.", "linked.path.display.invalidLink": "invalid configurations link.", - "banners.programNewMessage": "New message scheduling", - "banners.programmedMessage": "Programmed messages", - "banners.table.message": "Message", - "banners.table.startDate": "Start date", - "banners.table.endDate": "Stop date", - "banners.table.severity": "Severity", - "banners.table.cancel": "Cancel", - "banners.table.info": "Info", - "banners.table.warn": "Warning", - "banners.form.message": "Warning message", - "errCreateAnnouncement": "Error while creating announcement: ", - "noOverlapAllowedErr": "The date overlaps with another announcement date", - "noSameDateErr": "The announcement start and end date must be different", - "startDateAfterEndDateErr": "The start date cannot be after the end date" + "announcements.programNewMessage": "New message scheduling", + "announcements.programmedMessage": "Programmed messages", + "announcements.table.message": "Message", + "announcements.table.startDate": "Start date", + "announcements.table.endDate": "Stop date", + "announcements.table.cancel": "Cancel", + "announcements.severity": "Severity", + "announcements.severity.INFO": "Info", + "announcements.severity.WARN": "Warning", + "announcements.form.message": "Announcement message", + "announcements.form.errCreateAnnouncement": "Error while creating announcement:", + "announcements.form.errCreateAnnouncement.noOverlapAllowedErr": "The date overlaps with another announcement date.", + "announcements.form.errCreateAnnouncement.noSameDateErr": "The announcement start and end date must be different.", + "announcements.form.errCreateAnnouncement.startDateAfterEndDateErr": "The start date cannot be after the end date." } diff --git a/src/translations/fr.json b/src/translations/fr.json index a1b46dd..651c059 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -109,18 +109,18 @@ "linked.path.display.noLink": "pas de configuration selectionnée.", "linked.path.display.invalidLink": "lien vers configurations invalide.", - "banners.programNewMessage": "Programmer un nouveau message", - "banners.programmedMessage": "Messages programmés", - "banners.table.message": "Message", - "banners.table.startDate": "Date de début", - "banners.table.endDate": "Date de fin", - "banners.table.severity": "Sévérité", - "banners.table.cancel": "Annulation", - "banners.table.info": "Info", - "banners.table.warn": "Attention", - "banners.form.message": "Message d'avertissement", - "errCreateAnnouncement": "Erreur lors de la création de l'annonce : ", - "noOverlapAllowedErr": "La date d'annonce chevauche une autre date d'annonce", - "noSameDateErr": "La date de début et de fin d'annonce doivent être différentes", - "startDateAfterEndDateErr": "La date de début d'annonce ne peut pas être après la date de fin" + "announcements.programNewMessage": "Programmer un nouveau message", + "announcements.programmedMessage": "Messages programmés", + "announcements.table.message": "Message", + "announcements.table.startDate": "Date de début", + "announcements.table.endDate": "Date de fin", + "announcements.table.cancel": "Annulation", + "announcements.severity": "Sévérité", + "announcements.severity.INFO": "Information", + "announcements.severity.WARN": "Avertissement", + "announcements.form.message": "Message d'annonce", + "announcements.form.errCreateAnnouncement": "Erreur lors de la création de l'annonce :", + "announcements.form.errCreateAnnouncement.noOverlapAllowedErr": "La date d'annonce chevauche une autre date d'annonce.", + "announcements.form.errCreateAnnouncement.noSameDateErr": "La date de début et de fin d'annonce doivent être différentes.", + "announcements.form.errCreateAnnouncement.startDateAfterEndDateErr": "La date de début d'annonce ne peut pas être après la date de fin." } diff --git a/src/utils/error.ts b/src/utils/error.ts index d9e0537..07ddca2 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -32,20 +32,20 @@ const START_DATE_AFTER_END_DATE = 'START_DATE_AFTER_END_DATE'; export function handleAnnouncementCreationErrors(error: string, snackError: Function): boolean { if (error.includes(OVERLAPPING_ANNOUNCEMENTS)) { snackError({ - headerId: 'errCreateAnnouncement', - messageId: 'noOverlapAllowedErr', + headerId: 'announcements.form.errCreateAnnouncement', + messageId: 'announcements.form.errCreateAnnouncement.noOverlapAllowedErr', }); return true; } else if (error.includes(SAME_START_END_DATE)) { snackError({ - headerId: 'errCreateAnnouncement', - messageId: 'noSameDateErr', + headerId: 'announcements.form.errCreateAnnouncement', + messageId: 'announcements.form.errCreateAnnouncement.noSameDateErr', }); return true; } else if (error.includes(START_DATE_AFTER_END_DATE)) { snackError({ - headerId: 'errCreateAnnouncement', - messageId: 'startDateAfterEndDateErr', + headerId: 'announcements.form.errCreateAnnouncement', + messageId: 'announcements.form.errCreateAnnouncement.startDateAfterEndDateErr', }); return true; } diff --git a/src/utils/notifications-provider.ts b/src/utils/notifications-provider.ts index 8abcfd0..77c6420 100644 --- a/src/utils/notifications-provider.ts +++ b/src/utils/notifications-provider.ts @@ -24,9 +24,7 @@ export function useNotificationsUrlGenerator() { ({ [NotificationsUrlKeys.CONFIG]: tokenId ? getUrlWithToken( - `${wsBase}${PREFIX_CONFIG_NOTIFICATION_WS}/notify?${new URLSearchParams({ - appName: APP_NAME, - })}` + `${wsBase}${PREFIX_CONFIG_NOTIFICATION_WS}/notify?${new URLSearchParams({ appName: APP_NAME })}` ) : undefined, [NotificationsUrlKeys.GLOBAL_CONFIG]: tokenId From 38512e210458292efd26f14a79f1a86f877aa872 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Mon, 28 Apr 2025 00:02:20 +0200 Subject: [PATCH 11/23] Fix date library and timezone management --- package-lock.json | 124 ++++++++++++++---- package.json | 6 +- src/components/App/app-wrapper.tsx | 45 +++++-- src/components/App/app.tsx | 9 +- src/module-mui.d.ts | 3 + .../announcements/add-announcement-form.tsx | 39 +++--- 6 files changed, 159 insertions(+), 67 deletions(-) diff --git a/package-lock.json b/package-lock.json index f02cf1c..35568ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "2.17.0-SNAPSHOT", "license": "MPL-2.0", "dependencies": { + "@date-fns/tz": "^1.2.0", + "@date-fns/utc": "^2.1.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.95.0.tgz", @@ -16,13 +18,13 @@ "@mui/icons-material": "^5.16.14", "@mui/lab": "5.0.0-alpha.175", "@mui/material": "^5.16.14", - "@mui/x-date-pickers": "^7.28.3", + "@mui/x-date-pickers": "^8.1.0", "@mui/x-tree-view": "^7.28.1", "@reduxjs/toolkit": "^2.5.1", "ag-grid-community": "^33.1.0", "ag-grid-react": "^33.1.0", "core-js": "^3.40.0", - "dayjs": "^1.11.13", + "date-fns": "^4.1.0", "notistack": "^3.0.2", "oidc-client": "^1.11.5", "react": "^18.3.1", @@ -2182,9 +2184,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz", - "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -2269,6 +2271,18 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@date-fns/tz": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz", + "integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==", + "license": "MIT" + }, + "node_modules/@date-fns/utc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@date-fns/utc/-/utc-2.1.0.tgz", + "integrity": "sha512-176grgAgU2U303rD2/vcOmNg0kGPbhzckuH1TEP2al7n0AQipZIy9P15usd2TKQCG1g+E1jX/ZVQSzs4sUDwgA==", + "license": "MIT" + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -3075,7 +3089,7 @@ "node_modules/@gridsuite/commons-ui": { "version": "0.95.0", "resolved": "file:../commons-ui/gridsuite-commons-ui-0.95.0.tgz", - "integrity": "sha512-RQySsQY87B+YV6B5U7DWHNMjI4A8E7lFIzhOnaiIwgtlaPpujutQUQQUL+35El1HbUCjRjy10zdppozUVJTneQ==", + "integrity": "sha512-FuAtyUfSCtCJX9oCTSigBBWs+cyQQo4CoKDi9zedtQb/BlN50D2D3rqEnejaWF8kkLzF/rAZ3Ys1MstvNOqjSA==", "license": "MPL-2.0", "dependencies": { "@ag-grid-community/locale": "^33.1.0", @@ -4048,10 +4062,13 @@ } }, "node_modules/@mui/types": { - "version": "7.2.21", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", - "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.1.tgz", + "integrity": "sha512-gUL8IIAI52CRXP/MixT1tJKt3SI6tVv4U/9soFsTtAsHzaJQptZ42ffdHZV3niX1ei0aUgMvOxBBN0KYqdG39g==", "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0" + }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -4092,14 +4109,15 @@ } }, "node_modules/@mui/x-date-pickers": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.28.3.tgz", - "integrity": "sha512-5umKB/DIMfDN+FAlzcrocix9PpoJDJ+5hMdlby8spTPObP4wCSN+wkEhk0vFC7qE9FAWXr4wjemaKvsNf41cCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-8.1.0.tgz", + "integrity": "sha512-RySt7KKo7+VYbj4tj374GckC8BzFLRfIauJAbMlC3gh02azJr2AoBAkFoOLyiDyG9y9U1+ZX/9QGuOcUsknncw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/utils": "^5.16.6 || ^6.0.0 || ^7.0.0 || ^7.0.0-beta", - "@mui/x-internals": "7.28.0", - "@types/react-transition-group": "^4.4.11", + "@babel/runtime": "^7.27.0", + "@mui/utils": "^7.0.2", + "@mui/x-internals": "8.0.0", + "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" @@ -4114,8 +4132,8 @@ "peerDependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0 || ^7.0.0-beta", - "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0 || ^7.0.0-beta", + "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", "dayjs": "^1.10.7", @@ -4156,6 +4174,56 @@ } } }, + "node_modules/@mui/x-date-pickers/node_modules/@mui/utils": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.0.2.tgz", + "integrity": "sha512-72gcuQjPzhj/MLmPHLCgZjy2VjOH4KniR/4qRtXTTXIEwbkgcN+Y5W/rC90rWtMmZbjt9svZev/z+QHUI4j74w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/types": "^7.4.1", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-date-pickers/node_modules/@mui/x-internals": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.0.0.tgz", + "integrity": "sha512-yQOWABTEAIW0wiAwpjAJ6uM47rG1cxrfRtL2WsIdje8F9JdCXO6/jAu7ROAiezw4EqhGYYU7DMrK5svn5tdZpQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/utils": "^7.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@mui/x-internals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.28.0.tgz", @@ -7226,11 +7294,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "license": "MIT" + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } }, "node_modules/debug": { "version": "4.4.0", @@ -13235,9 +13307,9 @@ } }, "node_modules/react-is": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", - "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", + "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", "license": "MIT" }, "node_modules/react-papaparse": { diff --git a/package.json b/package.json index 260f552..07aa14b 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "npm": "^10.9.2" }, "dependencies": { + "@date-fns/tz": "^1.2.0", + "@date-fns/utc": "^2.1.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.95.0.tgz", @@ -21,13 +23,13 @@ "@mui/icons-material": "^5.16.14", "@mui/lab": "5.0.0-alpha.175", "@mui/material": "^5.16.14", - "@mui/x-date-pickers": "^7.28.3", + "@mui/x-date-pickers": "^8.1.0", "@mui/x-tree-view": "^7.28.1", "@reduxjs/toolkit": "^2.5.1", "ag-grid-community": "^33.1.0", "ag-grid-react": "^33.1.0", "core-js": "^3.40.0", - "dayjs": "^1.11.13", + "date-fns": "^4.1.0", "notistack": "^3.0.2", "oidc-client": "^1.11.5", "react": "^18.3.1", diff --git a/src/components/App/app-wrapper.tsx b/src/components/App/app-wrapper.tsx index 84e7afe..80f1141 100644 --- a/src/components/App/app-wrapper.tsx +++ b/src/components/App/app-wrapper.tsx @@ -6,10 +6,14 @@ */ import App from './app'; -import { FunctionComponent, useMemo } from 'react'; +import { FunctionComponent, PropsWithChildren, useMemo } from 'react'; import { CssBaseline, responsiveFontSizes, ThemeOptions } from '@mui/material'; import { createTheme, StyledEngineProvider, Theme, ThemeProvider } from '@mui/material/styles'; import { enUS as MuiCoreEnUS, frFR as MuiCoreFrFR } from '@mui/material/locale'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; +import { enUS as MuiDatePickersEnUS, frFR as MuiDatePickersFrFR } from '@mui/x-date-pickers/locales'; +import { enUS as dateFnsEnUS, fr as dateFnsFr } from 'date-fns/locale'; import { cardErrorBoundaryEn, cardErrorBoundaryFr, @@ -26,7 +30,7 @@ import { topBarFr, NotificationsProvider, } from '@gridsuite/commons-ui'; -import { IntlConfig, IntlProvider } from 'react-intl'; +import { type IntlConfig, IntlProvider } from 'react-intl'; import { Provider, useSelector } from 'react-redux'; import messages_en from '../../translations/en.json'; import messages_fr from '../../translations/fr.json'; @@ -103,7 +107,8 @@ const getMuiTheme = (theme: GsTheme, locale: GsLangUser): Theme => { return responsiveFontSizes( createTheme( theme === LIGHT_THEME ? lightTheme : darkTheme, - locale === LANG_FRENCH ? MuiCoreFrFR : MuiCoreEnUS // MUI core translations + locale === LANG_FRENCH ? MuiCoreFrFR : MuiCoreEnUS, // MUI core translations + locale === LANG_FRENCH ? MuiDatePickersFrFR : MuiDatePickersEnUS // MUI x-date-pickers translations ) ); }; @@ -125,10 +130,21 @@ const messages: Record = { const basename = new URL(document.baseURI ?? '').pathname; +function intlToDateFnsLocale(lng: GsLangUser) { + switch (lng) { + case LANG_FRENCH: + return dateFnsFr; + case LANG_ENGLISH: + return dateFnsEnUS; + default: + return undefined; + } +} + /** * Layer injecting Theme, Internationalization (i18n) and other tools (snackbar, error boundary, ...) */ -const AppWrapperRouterLayout: typeof App = (props, context) => { +const AppWrapperRouterLayout: typeof App = (props: Readonly>) => { const computedLanguage = useSelector((state: AppState) => state.computedLanguage); const theme = useSelector((state: AppState) => state[PARAM_THEME]); const themeCompiled = useMemo(() => getMuiTheme(theme, computedLanguage), [computedLanguage, theme]); @@ -137,14 +153,19 @@ const AppWrapperRouterLayout: typeof App = (props, context) => { - - - - - {props.children} - - - + + + + + + {props.children} + + + + diff --git a/src/components/App/app.tsx b/src/components/App/app.tsx index 314c5fe..86746c1 100644 --- a/src/components/App/app.tsx +++ b/src/components/App/app.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, PropsWithChildren, useCallback, useEffect } from 'react'; +import { type PropsWithChildren, useCallback, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Grid } from '@mui/material'; import { @@ -24,7 +24,7 @@ import AppTopBar from './app-top-bar'; import { useDebugRender } from '../../utils/hooks'; import { AppDispatch } from '../../redux/store'; -const App: FunctionComponent> = (props, context) => { +export default function App({ children }: Readonly>) { useDebugRender('app'); const { snackError } = useSnackMessage(); const dispatch = useDispatch(); @@ -102,9 +102,8 @@ const App: FunctionComponent> = (props, context) => {
- {/*Router outlet ->*/ props.children} + {/*Router outlet ->*/ children}
); -}; -export default App; +} diff --git a/src/module-mui.d.ts b/src/module-mui.d.ts index d60c6d1..015fddf 100644 --- a/src/module-mui.d.ts +++ b/src/module-mui.d.ts @@ -8,6 +8,9 @@ import { CSSObject } from '@mui/styled-engine'; import { Theme as MuiTheme, ThemeOptions as MuiThemeOptions } from '@mui/material/styles/createTheme'; +// https://mui.com/x/react-date-pickers/quickstart/#theme-augmentation +import type {} from '@mui/x-date-pickers/themeAugmentation'; + declare module '@mui/material/styles/createTheme' { export * from '@mui/material/styles/createTheme'; diff --git a/src/pages/announcements/add-announcement-form.tsx b/src/pages/announcements/add-announcement-form.tsx index c2a47a3..c646685 100644 --- a/src/pages/announcements/add-announcement-form.tsx +++ b/src/pages/announcements/add-announcement-form.tsx @@ -8,16 +8,13 @@ import { CustomFormProvider, SubmitButton, useSnackMessage } from '@gridsuite/commons-ui'; import Grid from '@mui/material/Grid'; import { FormattedMessage, useIntl } from 'react-intl'; -import React, { FunctionComponent, useCallback } from 'react'; +import { useCallback } from 'react'; import { FormControl, InputLabel, MenuItem, Select, TextField } from '@mui/material'; -import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers'; -import { UserAdminSrv, Announcement } from '../../services'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { DateTimePicker } from '@mui/x-date-pickers'; +import { Announcement, UserAdminSrv } from '../../services'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import yup from '../../utils/yup-config'; -import 'dayjs/locale/fr'; -import 'dayjs/locale/en'; import { useParameterState } from '../../components/parameters'; import { PARAM_LANGUAGE } from '../../utils/config-params'; import { getErrorMessage, handleAnnouncementCreationErrors } from '../../utils/error'; @@ -91,24 +88,22 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< />
- - setValue('startDate', newValue?.toISOString() ?? '')} - /> - + setValue('startDate', newValue?.toISOString() ?? '')} //TODO startOf(min) + timezone="system" + /> - - setValue('endDate', newValue?.toISOString() ?? '')} - /> - + setValue('endDate', newValue?.toISOString() ?? '')} //TODO endOf(min) + timezone="system" + /> From f41fe51dbbaf66125875899d92fd5ca06549a707 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Mon, 28 Apr 2025 04:21:23 +0200 Subject: [PATCH 12/23] Resolve bad import of `@mui/x-date-pickers` to `date-fns` internal file --- vite.config.ts | 15 +++++++++++++++ vite.shim.x-date-pickers.js | 10 ++++++++++ 2 files changed, 25 insertions(+) create mode 100644 vite.shim.x-date-pickers.js diff --git a/vite.config.ts b/vite.config.ts index 3a0195a..44b2877 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -11,6 +11,7 @@ import { CommonServerOptions, defineConfig } from 'vite'; import eslint from 'vite-plugin-eslint'; import svgr from 'vite-plugin-svgr'; import tsconfigPaths from 'vite-tsconfig-paths'; +import path from 'node:path'; const serverSettings: CommonServerOptions = { port: 3002, @@ -43,4 +44,18 @@ export default defineConfig((config) => ({ build: { outDir: 'build', }, + resolve: { + alias: { + /* "@mui/x-date-pickers/AdapterDateFns/AdapterDateFns" do an import from 'date-fns/_lib/format/longFormatters' + * which cause rollup error '[commonjs--resolver] Missing "./_lib/format/longFormatters" specifier in "date-fns" package'. + * - we fix the no default import with a shim that will fix that + * - we do a second alias to resolve the import to a non-exported file to date-fns/_lib/... + */ + 'date-fns/_lib/format/longFormatters': path.resolve(import.meta.dirname, 'vite.shim.x-date-pickers.js'), + 'virtual:date-fns/_lib/format/longFormatters': path.resolve( + import.meta.dirname, + 'node_modules/date-fns/_lib/format/longFormatters' + ), + }, + }, })); diff --git a/vite.shim.x-date-pickers.js b/vite.shim.x-date-pickers.js new file mode 100644 index 0000000..97acc61 --- /dev/null +++ b/vite.shim.x-date-pickers.js @@ -0,0 +1,10 @@ +/* + * Copyright © 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { longFormatters } from 'virtual:date-fns/_lib/format/longFormatters'; +export default longFormatters; // patch bad import of @mui/x-date-pickers/AdapterDateFns/AdapterDateFns.js +export * from 'virtual:date-fns/_lib/format/longFormatters'; From 697c708083e86ac15611e24fe68d3a7c41200add Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Fri, 2 May 2025 09:07:32 +0200 Subject: [PATCH 13/23] big review --- package-lock.json | 85 +++++----- package.json | 3 +- src/components/App/app-wrapper.tsx | 5 +- .../announcements/add-announcement-form.tsx | 158 +++++++++--------- .../announcements/announcements-page.tsx | 64 +++---- src/services/user-admin.ts | 52 +++--- src/utils/api-rest.ts | 9 +- 7 files changed, 185 insertions(+), 191 deletions(-) diff --git a/package-lock.json b/package-lock.json index 35568ca..2ce32ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@mui/icons-material": "^5.16.14", "@mui/lab": "5.0.0-alpha.175", "@mui/material": "^5.16.14", - "@mui/x-date-pickers": "^8.1.0", + "@mui/x-date-pickers": "^7.29.1", "@mui/x-tree-view": "^7.28.1", "@reduxjs/toolkit": "^2.5.1", "ag-grid-community": "^33.1.0", @@ -30,6 +30,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.54.2", + "react-hook-form-mui": "^7.6.0", "react-intl": "^7.1.6", "react-redux": "^9.2.0", "react-router": "^7.4.1", @@ -4109,15 +4110,15 @@ } }, "node_modules/@mui/x-date-pickers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-8.1.0.tgz", - "integrity": "sha512-RySt7KKo7+VYbj4tj374GckC8BzFLRfIauJAbMlC3gh02azJr2AoBAkFoOLyiDyG9y9U1+ZX/9QGuOcUsknncw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.29.1.tgz", + "integrity": "sha512-0RibuJ1YOgUVNqS4aSIXmxpqZ5YWLsxSM84GFlm3M7WW8UbwBY2QzG4vcOsLOYUwk2aqhcP6cpnrIyoLYUsS7g==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.27.0", - "@mui/utils": "^7.0.2", - "@mui/x-internals": "8.0.0", - "@types/react-transition-group": "^4.4.12", + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0 || ^7.0.0", + "@mui/x-internals": "7.29.0", + "@types/react-transition-group": "^4.4.11", "clsx": "^2.1.1", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" @@ -4174,44 +4175,14 @@ } } }, - "node_modules/@mui/x-date-pickers/node_modules/@mui/utils": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.0.2.tgz", - "integrity": "sha512-72gcuQjPzhj/MLmPHLCgZjy2VjOH4KniR/4qRtXTTXIEwbkgcN+Y5W/rC90rWtMmZbjt9svZev/z+QHUI4j74w==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.27.0", - "@mui/types": "^7.4.1", - "@types/prop-types": "^15.7.14", - "clsx": "^2.1.1", - "prop-types": "^15.8.1", - "react-is": "^19.1.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@mui/x-date-pickers/node_modules/@mui/x-internals": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.0.0.tgz", - "integrity": "sha512-yQOWABTEAIW0wiAwpjAJ6uM47rG1cxrfRtL2WsIdje8F9JdCXO6/jAu7ROAiezw4EqhGYYU7DMrK5svn5tdZpQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.29.0.tgz", + "integrity": "sha512-+Gk6VTZIFD70XreWvdXBwKd8GZ2FlSCuecQFzm6znwqXg1ZsndavrhG9tkxpxo2fM1Zf7Tk8+HcOO0hCbhTQFA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.27.0", - "@mui/utils": "^7.0.2" + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0 || ^7.0.0" }, "engines": { "node": ">=14.0.0" @@ -13281,6 +13252,34 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-hook-form-mui": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/react-hook-form-mui/-/react-hook-form-mui-7.6.0.tgz", + "integrity": "sha512-TGNL5N3vr7fe7MihuC0T1D1yKuXrB/km8Y92AtlVBGA2LvICaPU8unfcVzeTWj9/1pS1lUZAMjCsDsT/KU3jZg==", + "license": "MIT", + "workspaces": [ + "apps/*", + "packages/*" + ], + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@mui/icons-material": ">= 5.x <8", + "@mui/material": ">= 5.x <8", + "@mui/x-date-pickers": ">=7.17.0 <8", + "react": ">=17 <20", + "react-hook-form": ">=7.33.1" + }, + "peerDependenciesMeta": { + "@mui/icons-material": { + "optional": true + }, + "@mui/x-date-pickers": { + "optional": true + } + } + }, "node_modules/react-intl": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-7.1.6.tgz", diff --git a/package.json b/package.json index 07aa14b..6d9aacd 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@mui/icons-material": "^5.16.14", "@mui/lab": "5.0.0-alpha.175", "@mui/material": "^5.16.14", - "@mui/x-date-pickers": "^8.1.0", + "@mui/x-date-pickers": "^7.29.1", "@mui/x-tree-view": "^7.28.1", "@reduxjs/toolkit": "^2.5.1", "ag-grid-community": "^33.1.0", @@ -35,6 +35,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.54.2", + "react-hook-form-mui": "^7.6.0", "react-intl": "^7.1.6", "react-redux": "^9.2.0", "react-router": "^7.4.1", diff --git a/src/components/App/app-wrapper.tsx b/src/components/App/app-wrapper.tsx index 80f1141..0f4d802 100644 --- a/src/components/App/app-wrapper.tsx +++ b/src/components/App/app-wrapper.tsx @@ -15,9 +15,9 @@ import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import { enUS as MuiDatePickersEnUS, frFR as MuiDatePickersFrFR } from '@mui/x-date-pickers/locales'; import { enUS as dateFnsEnUS, fr as dateFnsFr } from 'date-fns/locale'; import { + CardErrorBoundary, cardErrorBoundaryEn, cardErrorBoundaryFr, - CardErrorBoundary, GsLangUser, GsTheme, LANG_ENGLISH, @@ -25,10 +25,10 @@ import { LIGHT_THEME, loginEn, loginFr, + NotificationsProvider, SnackbarProvider, topBarEn, topBarFr, - NotificationsProvider, } from '@gridsuite/commons-ui'; import { type IntlConfig, IntlProvider } from 'react-intl'; import { Provider, useSelector } from 'react-redux'; @@ -155,6 +155,7 @@ const AppWrapperRouterLayout: typeof App = (props: Readonly diff --git a/src/pages/announcements/add-announcement-form.tsx b/src/pages/announcements/add-announcement-form.tsx index c646685..fbb1475 100644 --- a/src/pages/announcements/add-announcement-form.tsx +++ b/src/pages/announcements/add-announcement-form.tsx @@ -5,18 +5,19 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { CustomFormProvider, SubmitButton, useSnackMessage } from '@gridsuite/commons-ui'; -import Grid from '@mui/material/Grid'; -import { FormattedMessage, useIntl } from 'react-intl'; import { useCallback } from 'react'; -import { FormControl, InputLabel, MenuItem, Select, TextField } from '@mui/material'; -import { DateTimePicker } from '@mui/x-date-pickers'; -import { Announcement, UserAdminSrv } from '../../services'; -import { useForm } from 'react-hook-form'; -import { yupResolver } from '@hookform/resolvers/yup'; +import { Grid } from '@mui/material'; +import { useIntl } from 'react-intl'; +import { type Option, SubmitButton, useSnackMessage } from '@gridsuite/commons-ui'; import yup from '../../utils/yup-config'; -import { useParameterState } from '../../components/parameters'; -import { PARAM_LANGUAGE } from '../../utils/config-params'; +import { type InferType } from 'yup'; +import { type SubmitHandler, useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { FormContainer, SelectElement, TextareaAutosizeElement } from 'react-hook-form-mui'; +import { DateTimePickerElement, type DateTimePickerElementProps } from 'react-hook-form-mui/date-pickers'; +import { TZDate } from '@date-fns/tz'; +import { addSeconds } from 'date-fns'; +import { UserAdminSrv } from '../../services'; import { getErrorMessage, handleAnnouncementCreationErrors } from '../../utils/error'; export const MESSAGE = 'message'; @@ -24,49 +25,64 @@ export const START_DATE = 'startDate'; export const END_DATE = 'endDate'; export const SEVERITY = 'severity'; -interface AddAnnouncementProps { - onAnnouncementCreated: () => void; -} +export type AddAnnouncementFormProps = { + onAnnouncementCreated?: () => void; +}; + +const severitySelect: Option[] = Object.values(UserAdminSrv.AnnouncementSeverity).map((value) => ({ + id: value, + label: `announcements.severity.${value}`, +})); + +const formSchema = yup + .object() + .shape({ + [MESSAGE]: yup.string().trim().min(1).required(), + [START_DATE]: yup.string().datetime({ precision: 0 }).required(), + [END_DATE]: yup.string().datetime({ precision: 0 }).required(), + [SEVERITY]: yup + .string() + .oneOf(Object.values(UserAdminSrv.AnnouncementSeverity)) + .required(), + }) + .required(); +type FormSchema = InferType; -export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly) { +const datetimePickerTransform: NonNullable['transform']> = { + input: (value) => (value && new TZDate(value)) || null, + output: (value, context) => value?.toISOString() || '', +}; + +export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly) { const intl = useIntl(); - const [languageLocal] = useParameterState(PARAM_LANGUAGE); const { snackError } = useSnackMessage(); - const formSchema = yup - .object() - .shape({ - [MESSAGE]: yup.string().trim().required(), // TODO not empty - [START_DATE]: yup.string().required(), //TODO date - [END_DATE]: yup.string().required(), // TODO date - [SEVERITY]: yup.string().required(), // TODO enum - }) - .required(); - const formMethods = useForm({ + const formContext = useForm({ resolver: yupResolver(formSchema), + /*TODO defaultValues: { + [MESSAGE]: null, + [START_DATE]: null, + [END_DATE]: null, + [SEVERITY]: null, + },*/ }); - const { register, setValue, handleSubmit, formState } = formMethods; + const { register, setValue, handleSubmit, formState, control, getValues } = formContext; + const startDateValue = getValues(START_DATE); - const onSubmit = useCallback( - (params: any) => { - let startDate = new Date(params.startDate).toISOString(); - let endDate = new Date(params.endDate).toISOString(); - const newAnnouncement = { - id: crypto.randomUUID(), + const onSubmit = useCallback>( + (params) => { + UserAdminSrv.addAnnouncement({ + //id: crypto.randomUUID(), message: params.message, - startDate: startDate, - endDate: endDate, + startDate: params.startDate, + endDate: params.endDate, severity: params.severity, - } as Announcement; - UserAdminSrv.addAnnouncement(newAnnouncement) - .then(() => onAnnouncementCreated()) + }) + .then(() => onAnnouncementCreated?.()) .catch((error) => { let errorMessage = getErrorMessage(error) ?? ''; if (!handleAnnouncementCreationErrors(errorMessage, snackError)) { - snackError({ - headerId: 'announcements.form.errCreateAnnouncement', - messageTxt: errorMessage, - }); + snackError({ headerId: 'announcements.form.errCreateAnnouncement', messageTxt: errorMessage }); } }); }, @@ -74,67 +90,57 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< ); return ( - + formContext={formContext} onSuccess={onSubmit}> + name={END_DATE} /> - + name={MESSAGE} label={intl.formatMessage({ id: 'announcements.form.message' })} - multiline - rows={4} + minRows={2} + maxRows={5} fullWidth - inputProps={{ maxLength: 200 }} + //inputProps={{ maxLength: 200 }} /> - name={START_DATE} label={intl.formatMessage({ id: 'announcements.table.startDate' })} - onChange={(newValue) => setValue('startDate', newValue?.toISOString() ?? '')} //TODO startOf(min) + transform={datetimePickerTransform} //TODO round startOf(min) timezone="system" + timeSteps={{ hours: 1, minutes: 1, seconds: 0 }} + disablePast /> - name={END_DATE} label={intl.formatMessage({ id: 'announcements.table.endDate' })} - onChange={(newValue) => setValue('endDate', newValue?.toISOString() ?? '')} //TODO endOf(min) + transform={datetimePickerTransform} //TODO round startOf(min) timezone="system" + timeSteps={{ hours: 1, minutes: 1, seconds: 0 }} + disablePast + minDateTime={startDateValue ? addSeconds(new TZDate(startDateValue), 1) : undefined} /> - - - - - - + + name={SEVERITY} + label={intl.formatMessage({ id: 'announcements.severity' })} + options={severitySelect} + fullWidth + /> - + ); } diff --git a/src/pages/announcements/announcements-page.tsx b/src/pages/announcements/announcements-page.tsx index d82b849..f68508e 100644 --- a/src/pages/announcements/announcements-page.tsx +++ b/src/pages/announcements/announcements-page.tsx @@ -8,7 +8,7 @@ import type { UUID } from 'crypto'; import { useCallback, useMemo, useRef, useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; -import { Grid, type SxProps, type Theme } from '@mui/material'; +import { Grid, type SxProps, type Theme, Typography } from '@mui/material'; import { useSnackMessage } from '@gridsuite/commons-ui'; import type { ColDef, GetRowIdParams, ValueGetterParams } from 'ag-grid-community'; import { type GridTableRef } from '../../components/Grid'; @@ -17,6 +17,7 @@ import AddAnnouncementForm from './add-announcement-form'; import { DateCellRenderer } from './date-cell-renderer'; import AgGrid from '../../components/Grid/AgGrid'; import { CancelButtonCellRenderer } from './cancel-button-cell-renderer'; +import { getErrorMessage } from '../../utils/error'; const stylesLayout = { root: { display: 'flex' }, @@ -33,6 +34,7 @@ const defaultColDef: ColDef = { cellRenderer: 'agAnimateSlideCellRenderer', rowDrag: false, sortable: true, + lockVisible: true, }; function getRowId(params: GetRowIdParams) { @@ -42,22 +44,17 @@ function getRowId(params: GetRowIdParams) { export default function AnnouncementsPage() { const intl = useIntl(); const gridRef = useRef>(null); - const { snackError } = useSnackMessage(); const [data, setData] = useState(null); - const loadDataAndSave = useCallback( - function loadDataAndSave(): Promise { - return UserAdminSrv.fetchAnnouncementList().then(setData, (error) => { - snackError({ - messageTxt: error.message, - headerId: 'table.error.retrieve', - }); - }); - }, - [snackError] - ); + const loadDataAndSave = useCallback(async (): Promise => { + try { + setData(await UserAdminSrv.fetchAnnouncementList()); + } catch (error) { + snackError({ messageTxt: getErrorMessage(error) ?? undefined, headerId: 'table.error.retrieve' }); + } + }, [snackError]); const convertSeverity = useCallback( (severity: string) => { @@ -89,39 +86,32 @@ export default function AnnouncementsPage() { field: 'message', cellDataType: 'text', flex: 3, - lockVisible: true, headerName: intl.formatMessage({ id: 'announcements.table.message' }), }, { field: 'startDate', cellRenderer: DateCellRenderer, flex: 3, - lockVisible: true, headerName: intl.formatMessage({ id: 'announcements.table.startDate' }), }, { field: 'endDate', cellRenderer: DateCellRenderer, flex: 3, - lockVisible: true, headerName: intl.formatMessage({ id: 'announcements.table.endDate' }), }, { field: 'severity', cellDataType: 'text', flex: 2, - lockVisible: true, headerName: intl.formatMessage({ id: 'announcements.severity' }), valueGetter: (value: ValueGetterParams) => convertSeverity(value.data.severity), }, { field: 'id', cellRenderer: CancelButtonCellRenderer, - cellRendererParams: { - onClickHandler: handleDeleteAnnouncement, - }, + cellRendererParams: { onClickHandler: handleDeleteAnnouncement }, flex: 2, - lockVisible: true, headerName: intl.formatMessage({ id: 'announcements.table.cancel' }), }, ], @@ -133,7 +123,7 @@ export default function AnnouncementsPage() {

- +

@@ -143,24 +133,22 @@ export default function AnnouncementsPage() { -

+ -

+
- - - - ref={gridRef} - rowData={data} - alwaysShowVerticalScroll - onGridReady={loadDataAndSave} - columnDefs={columns} - defaultColDef={defaultColDef} - gridId="table-banners" - getRowId={getRowId} - context={useMemo(() => ({ refresh: loadDataAndSave }), [loadDataAndSave])} - /> - + + + ref={gridRef} + rowData={data} + alwaysShowVerticalScroll + onGridReady={loadDataAndSave} + columnDefs={columns} + defaultColDef={defaultColDef} + gridId="table-announcements" + getRowId={getRowId} + context={useMemo(() => ({ refresh: loadDataAndSave }), [loadDataAndSave])} + />
diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index c325065..bf86b74 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -6,7 +6,7 @@ */ import { User } from 'oidc-client'; -import { backendFetch, backendFetchJson, getRestBase } from '../utils/api-rest'; +import { backendFetch, backendFetchJson, backendFetchText, getRestBase } from '../utils/api-rest'; import { extractUserSub, getToken, getUser } from '../utils/api'; import { UUID } from 'crypto'; @@ -291,52 +291,50 @@ export enum AnnouncementSeverity { } export function sanitizeString(val: string | null | undefined) { - const trimedValue = val?.trim(); - return trimedValue === '' ? null : trimedValue; + const trimmedValue = val?.trim(); + return trimmedValue === '' ? null : trimmedValue; } -export type Announcement = { - id: UUID; +export type NewAnnouncement = { startDate: string; endDate: string; message: string; - severity: string; + severity: AnnouncementSeverity; +}; +export type Announcement = NewAnnouncement & { + id: UUID; }; -export function addAnnouncement(announcement: Announcement): Promise { +export async function addAnnouncement(announcement: NewAnnouncement): Promise { console.debug(`Creating announcement ...`); - return backendFetch( + return backendFetchText( `${USER_ADMIN_URL}/announcements?startDate=${announcement.startDate}&endDate=${announcement.endDate}&severity=${announcement.severity}`, { method: 'post', headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', + Accept: 'plain/text', + 'Content-Type': 'plain/text', }, body: sanitizeString(announcement.message), } - ) - .then(() => undefined) - .catch((reason) => { - console.error(`Error while creating announcement : ${reason}`); - throw reason; - }); + ).catch((reason) => { + console.error(`Error while creating announcement : ${reason}`); + throw reason; + }) as Promise; } -export function fetchAnnouncementList(): Promise { +export function fetchAnnouncementList() { console.debug(`Fetching announcement ...`); - return backendFetchJson(`${USER_ADMIN_URL}/announcements`, { method: 'get' }).catch((reason) => { + return backendFetchJson(`${USER_ADMIN_URL}/announcements`, { method: 'get' }).catch((reason) => { console.error(`Error while fetching announcement : ${reason}`); throw reason; - }) as Promise; + }); } -export function deleteAnnouncement(announcementId: UUID): Promise { - console.debug(`Deleting announcement ...`); - return backendFetch(`${USER_ADMIN_URL}/announcements/${announcementId}`, { method: 'delete' }) - .then(() => undefined) - .catch((reason) => { - console.error(`Error while deleting announcement : ${reason}`); - throw reason; - }); +export async function deleteAnnouncement(announcementId: UUID): Promise { + console.debug(`Deleting announcement ${announcementId}...`); + await backendFetch(`${USER_ADMIN_URL}/announcements/${announcementId}`, { method: 'delete' }).catch((reason) => { + console.error(`Error while deleting announcement : ${reason}`); + throw reason; + }); } diff --git a/src/utils/api-rest.ts b/src/utils/api-rest.ts index 01b9e0d..884829e 100644 --- a/src/utils/api-rest.ts +++ b/src/utils/api-rest.ts @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import type { JsonValue } from 'type-fest'; import { getToken, parseError, Token } from './api'; export type { Token } from './api'; @@ -60,10 +61,10 @@ export function backendFetch(url: Url, init?: InitRequest, token?: Token): Promi return safeFetch(url, prepareRequest(init, token)); } -export function backendFetchText(url: Url, init?: InitRequest, token?: Token): Promise { - return backendFetch(url, init, token).then((safeResponse: Response) => safeResponse.text()); +export function backendFetchText(url: Url, init?: InitRequest, token?: Token) { + return backendFetch(url, init, token).then((safeResponse) => safeResponse.text()) as Promise; } -export function backendFetchJson(url: Url, init?: InitRequest, token?: Token): Promise { - return backendFetch(url, init, token).then((safeResponse: Response) => safeResponse.json()); +export function backendFetchJson(url: Url, init?: InitRequest, token?: Token): Promise { + return backendFetch(url, init, token).then((safeResponse) => safeResponse.json()); } From 7d06097769ba3cbcd6bc668ffd63cf6350c78b25 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Fri, 2 May 2025 09:09:36 +0200 Subject: [PATCH 14/23] clean --- .../announcements/add-announcement-form.tsx | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/pages/announcements/add-announcement-form.tsx b/src/pages/announcements/add-announcement-form.tsx index fbb1475..384b58f 100644 --- a/src/pages/announcements/add-announcement-form.tsx +++ b/src/pages/announcements/add-announcement-form.tsx @@ -7,6 +7,7 @@ import { useCallback } from 'react'; import { Grid } from '@mui/material'; +import { type DateOrTimeView } from '@mui/x-date-pickers'; import { useIntl } from 'react-intl'; import { type Option, SubmitButton, useSnackMessage } from '@gridsuite/commons-ui'; import yup from '../../utils/yup-config'; @@ -16,7 +17,7 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { FormContainer, SelectElement, TextareaAutosizeElement } from 'react-hook-form-mui'; import { DateTimePickerElement, type DateTimePickerElementProps } from 'react-hook-form-mui/date-pickers'; import { TZDate } from '@date-fns/tz'; -import { addSeconds } from 'date-fns'; +import { endOfMinute, startOfMinute } from 'date-fns'; import { UserAdminSrv } from '../../services'; import { getErrorMessage, handleAnnouncementCreationErrors } from '../../utils/error'; @@ -49,9 +50,10 @@ const formSchema = yup type FormSchema = InferType; const datetimePickerTransform: NonNullable['transform']> = { - input: (value) => (value && new TZDate(value)) || null, - output: (value, context) => value?.toISOString() || '', + input: (value) => (value ? new TZDate(value) : null), + output: (value) => value?.toISOString() ?? '', }; +const pickerView = ['year', 'month', 'day', 'hours', 'minutes'] as const satisfies readonly DateOrTimeView[]; export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly) { const intl = useIntl(); @@ -66,7 +68,7 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< [SEVERITY]: null, },*/ }); - const { register, setValue, handleSubmit, formState, control, getValues } = formContext; + const { formState, getValues } = formContext; const startDateValue = getValues(START_DATE); const onSubmit = useCallback>( @@ -74,8 +76,8 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< UserAdminSrv.addAnnouncement({ //id: crypto.randomUUID(), message: params.message, - startDate: params.startDate, - endDate: params.endDate, + startDate: startOfMinute(new TZDate(params.startDate)).toISOString(), + endDate: endOfMinute(new TZDate(params.endDate)).toISOString(), severity: params.severity, }) .then(() => onAnnouncementCreated?.()) @@ -107,8 +109,9 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< name={START_DATE} label={intl.formatMessage({ id: 'announcements.table.startDate' })} - transform={datetimePickerTransform} //TODO round startOf(min) + transform={datetimePickerTransform} timezone="system" + views={pickerView} timeSteps={{ hours: 1, minutes: 1, seconds: 0 }} disablePast /> @@ -117,11 +120,12 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< name={END_DATE} label={intl.formatMessage({ id: 'announcements.table.endDate' })} - transform={datetimePickerTransform} //TODO round startOf(min) + transform={datetimePickerTransform} timezone="system" + views={pickerView} timeSteps={{ hours: 1, minutes: 1, seconds: 0 }} disablePast - minDateTime={startDateValue ? addSeconds(new TZDate(startDateValue), 1) : undefined} + minDateTime={startDateValue ? new TZDate(startDateValue) : undefined} />
From f4423119f70e4e8da1356a20041fc425ec59c2f4 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Fri, 2 May 2025 14:08:50 +0200 Subject: [PATCH 15/23] Fix DateFns conflict with hook-form --- package-lock.json | 14 -------------- package.json | 2 -- src/pages/announcements/add-announcement-form.tsx | 10 ++++------ 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2ce32ac..b9afa9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,6 @@ "version": "2.17.0-SNAPSHOT", "license": "MPL-2.0", "dependencies": { - "@date-fns/tz": "^1.2.0", - "@date-fns/utc": "^2.1.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.95.0.tgz", @@ -2272,18 +2270,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@date-fns/tz": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz", - "integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==", - "license": "MIT" - }, - "node_modules/@date-fns/utc": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@date-fns/utc/-/utc-2.1.0.tgz", - "integrity": "sha512-176grgAgU2U303rD2/vcOmNg0kGPbhzckuH1TEP2al7n0AQipZIy9P15usd2TKQCG1g+E1jX/ZVQSzs4sUDwgA==", - "license": "MIT" - }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", diff --git a/package.json b/package.json index 6d9aacd..cc7388b 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,6 @@ "npm": "^10.9.2" }, "dependencies": { - "@date-fns/tz": "^1.2.0", - "@date-fns/utc": "^2.1.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.95.0.tgz", diff --git a/src/pages/announcements/add-announcement-form.tsx b/src/pages/announcements/add-announcement-form.tsx index 384b58f..88567f1 100644 --- a/src/pages/announcements/add-announcement-form.tsx +++ b/src/pages/announcements/add-announcement-form.tsx @@ -16,8 +16,6 @@ import { type SubmitHandler, useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { FormContainer, SelectElement, TextareaAutosizeElement } from 'react-hook-form-mui'; import { DateTimePickerElement, type DateTimePickerElementProps } from 'react-hook-form-mui/date-pickers'; -import { TZDate } from '@date-fns/tz'; -import { endOfMinute, startOfMinute } from 'date-fns'; import { UserAdminSrv } from '../../services'; import { getErrorMessage, handleAnnouncementCreationErrors } from '../../utils/error'; @@ -50,7 +48,7 @@ const formSchema = yup type FormSchema = InferType; const datetimePickerTransform: NonNullable['transform']> = { - input: (value) => (value ? new TZDate(value) : null), + input: (value) => (value ? new Date(value) : null), output: (value) => value?.toISOString() ?? '', }; const pickerView = ['year', 'month', 'day', 'hours', 'minutes'] as const satisfies readonly DateOrTimeView[]; @@ -76,8 +74,8 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< UserAdminSrv.addAnnouncement({ //id: crypto.randomUUID(), message: params.message, - startDate: startOfMinute(new TZDate(params.startDate)).toISOString(), - endDate: endOfMinute(new TZDate(params.endDate)).toISOString(), + startDate: params.startDate, + endDate: params.endDate, severity: params.severity, }) .then(() => onAnnouncementCreated?.()) @@ -125,7 +123,7 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< views={pickerView} timeSteps={{ hours: 1, minutes: 1, seconds: 0 }} disablePast - minDateTime={startDateValue ? new TZDate(startDateValue) : undefined} + minDateTime={startDateValue ? new Date(startDateValue) : undefined} /> From 196bbe5a54236e80ef3676536b806fe9e59f0068 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Fri, 2 May 2025 14:14:37 +0200 Subject: [PATCH 16/23] fixing page design & form validation --- .../announcements/add-announcement-form.tsx | 129 +++++++++++------- .../announcements/announcements-page.tsx | 27 ++-- 2 files changed, 93 insertions(+), 63 deletions(-) diff --git a/src/pages/announcements/add-announcement-form.tsx b/src/pages/announcements/add-announcement-form.tsx index 88567f1..01dee8c 100644 --- a/src/pages/announcements/add-announcement-form.tsx +++ b/src/pages/announcements/add-announcement-form.tsx @@ -5,11 +5,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { Grid } from '@mui/material'; import { type DateOrTimeView } from '@mui/x-date-pickers'; import { useIntl } from 'react-intl'; -import { type Option, SubmitButton, useSnackMessage } from '@gridsuite/commons-ui'; +import { SubmitButton, useSnackMessage } from '@gridsuite/commons-ui'; import yup from '../../utils/yup-config'; import { type InferType } from 'yup'; import { type SubmitHandler, useForm } from 'react-hook-form'; @@ -28,19 +28,26 @@ export type AddAnnouncementFormProps = { onAnnouncementCreated?: () => void; }; -const severitySelect: Option[] = Object.values(UserAdminSrv.AnnouncementSeverity).map((value) => ({ - id: value, - label: `announcements.severity.${value}`, -})); - const formSchema = yup .object() .shape({ - [MESSAGE]: yup.string().trim().min(1).required(), - [START_DATE]: yup.string().datetime({ precision: 0 }).required(), - [END_DATE]: yup.string().datetime({ precision: 0 }).required(), + [MESSAGE]: yup.string().nullable().trim().min(1).required(), + [START_DATE]: yup.string().nullable().datetime().required(), + [END_DATE]: yup + .string() + .nullable() + .datetime() + .required() + .when(START_DATE, (startDate, schema) => + schema.test( + 'is-after-start', + 'End date must be after start date', + (endDate) => !startDate || !endDate || new Date(endDate) > new Date(startDate as unknown as string) + ) + ), [SEVERITY]: yup .string() + .nullable() .oneOf(Object.values(UserAdminSrv.AnnouncementSeverity)) .required(), }) @@ -59,12 +66,16 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< const formContext = useForm({ resolver: yupResolver(formSchema), - /*TODO defaultValues: { + defaultValues: { + // @ts-expect-error: nullable() is called, so null is accepted as default value [MESSAGE]: null, + // @ts-expect-error: nullable() is called, so null is accepted as default value [START_DATE]: null, + // @ts-expect-error: nullable() is called, so null is accepted as default value [END_DATE]: null, + // @ts-expect-error: nullable() is called, so null is accepted as default value [SEVERITY]: null, - },*/ + }, }); const { formState, getValues } = formContext; const startDateValue = getValues(START_DATE); @@ -72,7 +83,6 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< const onSubmit = useCallback>( (params) => { UserAdminSrv.addAnnouncement({ - //id: crypto.randomUUID(), message: params.message, startDate: params.startDate, endDate: params.endDate, @@ -90,51 +100,68 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< ); return ( - formContext={formContext} onSuccess={onSubmit}> - name={END_DATE} /> - - - - name={MESSAGE} - label={intl.formatMessage({ id: 'announcements.form.message' })} - minRows={2} - maxRows={5} - fullWidth - //inputProps={{ maxLength: 200 }} - /> - - - - name={START_DATE} - label={intl.formatMessage({ id: 'announcements.table.startDate' })} - transform={datetimePickerTransform} - timezone="system" - views={pickerView} - timeSteps={{ hours: 1, minutes: 1, seconds: 0 }} - disablePast - /> + + formContext={formContext} + onSuccess={onSubmit} + //criteriaMode="all" + //mode="all" + mode="onChange" + reValidateMode="onChange" + //reValidateMode="onChange" + FormProps={{ style: { height: '100%' } }} + > + + + + + name={START_DATE} + label={intl.formatMessage({ id: 'announcements.table.startDate' })} + transform={datetimePickerTransform} + timezone="system" + views={pickerView} + timeSteps={{ hours: 1, minutes: 1, seconds: 0 }} + disablePast + /> + + + + name={END_DATE} + label={intl.formatMessage({ id: 'announcements.table.endDate' })} + transform={datetimePickerTransform} + timezone="system" + views={pickerView} + timeSteps={{ hours: 1, minutes: 1, seconds: 0 }} + disablePast + minDateTime={startDateValue ? new Date(startDateValue) : undefined} + /> + - - - name={END_DATE} - label={intl.formatMessage({ id: 'announcements.table.endDate' })} - transform={datetimePickerTransform} - timezone="system" - views={pickerView} - timeSteps={{ hours: 1, minutes: 1, seconds: 0 }} - disablePast - minDateTime={startDateValue ? new Date(startDateValue) : undefined} - /> - - + name={SEVERITY} label={intl.formatMessage({ id: 'announcements.severity' })} - options={severitySelect} + options={useMemo( + () => + Object.values(UserAdminSrv.AnnouncementSeverity).map((value) => ({ + id: value, + label: intl.formatMessage({ id: `announcements.severity.${value}` }), + })), + [intl] + )} fullWidth /> - + + + name={MESSAGE} + label={intl.formatMessage({ id: 'announcements.form.message' })} + style={{ height: '100%' }} + rows={5} // why does it do nothing even if the field is set as multiline?! + fullWidth + //inputProps={{ maxLength: 200 }} + /> + + for the columns didn't work return ( - - - -

+ + + + -

+ +
+ + - + - - - - + + + - + ref={gridRef} rowData={data} From 8782889186d1784f076c72a5e1a6602b0ba7cff2 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Fri, 2 May 2025 14:18:33 +0200 Subject: [PATCH 17/23] update --- src/services/user-admin.ts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index bf86b74..ba081ac 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -6,7 +6,7 @@ */ import { User } from 'oidc-client'; -import { backendFetch, backendFetchJson, backendFetchText, getRestBase } from '../utils/api-rest'; +import { backendFetch, backendFetchJson, getRestBase } from '../utils/api-rest'; import { extractUserSub, getToken, getUser } from '../utils/api'; import { UUID } from 'crypto'; @@ -305,12 +305,12 @@ export type Announcement = NewAnnouncement & { id: UUID; }; -export async function addAnnouncement(announcement: NewAnnouncement): Promise { +export async function addAnnouncement(announcement: NewAnnouncement) { console.debug(`Creating announcement ...`); - return backendFetchText( + return backendFetchJson( `${USER_ADMIN_URL}/announcements?startDate=${announcement.startDate}&endDate=${announcement.endDate}&severity=${announcement.severity}`, { - method: 'post', + method: 'put', headers: { Accept: 'plain/text', 'Content-Type': 'plain/text', @@ -318,23 +318,25 @@ export async function addAnnouncement(announcement: NewAnnouncement): Promise { - console.error(`Error while creating announcement : ${reason}`); + console.error('Error while creating announcement:', reason); throw reason; - }) as Promise; + }); } -export function fetchAnnouncementList() { +export async function fetchAnnouncementList() { console.debug(`Fetching announcement ...`); - return backendFetchJson(`${USER_ADMIN_URL}/announcements`, { method: 'get' }).catch((reason) => { - console.error(`Error while fetching announcement : ${reason}`); + try { + return await backendFetchJson(`${USER_ADMIN_URL}/announcements`, { method: 'get' }); + } catch (reason) { + console.error('Error while fetching announcement:', reason); throw reason; - }); + } } export async function deleteAnnouncement(announcementId: UUID): Promise { console.debug(`Deleting announcement ${announcementId}...`); await backendFetch(`${USER_ADMIN_URL}/announcements/${announcementId}`, { method: 'delete' }).catch((reason) => { - console.error(`Error while deleting announcement : ${reason}`); + console.error('Error while deleting announcement:', reason); throw reason; }); } From dadc9f37ad34383b976381aa5f3590c161a9e773 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Fri, 2 May 2025 14:38:28 +0200 Subject: [PATCH 18/23] fix mime type --- src/services/user-admin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index ba081ac..35b4f6c 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -312,8 +312,8 @@ export async function addAnnouncement(announcement: NewAnnouncement) { { method: 'put', headers: { - Accept: 'plain/text', - 'Content-Type': 'plain/text', + Accept: 'application/json', + 'Content-Type': 'text/plain', }, body: sanitizeString(announcement.message), } From ef74ff5099a049bbf198b23dc0cdb4137345b55b Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Fri, 2 May 2025 15:51:21 +0200 Subject: [PATCH 19/23] design fix --- .../announcements/add-announcement-form.tsx | 2 +- .../announcements/announcements-page.tsx | 76 +++++++++---------- .../cancel-button-cell-renderer.tsx | 27 ------- .../announcements/cancel-cell-renderer.tsx | 29 +++++++ .../announcements/date-cell-renderer.tsx | 40 ---------- src/translations/fr.json | 2 +- 6 files changed, 69 insertions(+), 107 deletions(-) delete mode 100644 src/pages/announcements/cancel-button-cell-renderer.tsx create mode 100644 src/pages/announcements/cancel-cell-renderer.tsx delete mode 100644 src/pages/announcements/date-cell-renderer.tsx diff --git a/src/pages/announcements/add-announcement-form.tsx b/src/pages/announcements/add-announcement-form.tsx index 01dee8c..643a29f 100644 --- a/src/pages/announcements/add-announcement-form.tsx +++ b/src/pages/announcements/add-announcement-form.tsx @@ -155,8 +155,8 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< name={MESSAGE} label={intl.formatMessage({ id: 'announcements.form.message' })} - style={{ height: '100%' }} rows={5} // why does it do nothing even if the field is set as multiline?! + //style={{ height: '100%' }} fullWidth //inputProps={{ maxLength: 200 }} /> diff --git a/src/pages/announcements/announcements-page.tsx b/src/pages/announcements/announcements-page.tsx index e8d3e02..fca1e68 100644 --- a/src/pages/announcements/announcements-page.tsx +++ b/src/pages/announcements/announcements-page.tsx @@ -8,25 +8,16 @@ import type { UUID } from 'crypto'; import { useCallback, useMemo, useRef, useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; -import { Divider, Grid, type SxProps, type Theme, Typography } from '@mui/material'; +import { Divider, Grid, Typography } from '@mui/material'; import { useSnackMessage } from '@gridsuite/commons-ui'; -import type { ColDef, GetRowIdParams, ValueGetterParams } from 'ag-grid-community'; +import type { ColDef, GetRowIdParams, ValueFormatterFunc } from 'ag-grid-community'; import { type GridTableRef } from '../../components/Grid'; import { Announcement, UserAdminSrv } from '../../services'; import AddAnnouncementForm from './add-announcement-form'; -import { DateCellRenderer } from './date-cell-renderer'; import AgGrid from '../../components/Grid/AgGrid'; -import { CancelButtonCellRenderer } from './cancel-button-cell-renderer'; +import CancelCellRenderer from './cancel-cell-renderer'; import { getErrorMessage } from '../../utils/error'; -const stylesLayout = { - root: { display: 'flex' }, - columnContainer: { - maxHeight: '60px', - paddingLeft: '15px', - }, -} as const satisfies Record>; - const defaultColDef: ColDef = { editable: false, resizable: true, @@ -56,19 +47,25 @@ export default function AnnouncementsPage() { } }, [snackError]); - const convertSeverity = useCallback( - (severity: string) => { - if (severity === UserAdminSrv.AnnouncementSeverity.INFO) { - return intl.formatMessage({ id: 'announcements.severity.INFO' }); - } else if (severity === UserAdminSrv.AnnouncementSeverity.WARN) { - return intl.formatMessage({ id: 'announcements.severity.WARN' }); - } else { - return ''; + const renderSeverity = useCallback>>( + (params) => { + switch (params.value) { + case UserAdminSrv.AnnouncementSeverity.INFO: + return intl.formatMessage({ id: 'announcements.severity.INFO' }); + case UserAdminSrv.AnnouncementSeverity.WARN: + return intl.formatMessage({ id: 'announcements.severity.WARN' }); + default: + return params.value || ''; } }, [intl] ); + const renderDate = useCallback>>( + (params) => (params.value ? intl.formatDate(params.value, { dateStyle: 'short', timeStyle: 'short' }) : ''), + [intl] + ); + const refreshGrid = useCallback(() => { gridRef.current?.context?.refresh?.(); }, []); @@ -82,45 +79,48 @@ export default function AnnouncementsPage() { const columns = useMemo( (): ColDef[] => [ - { - field: 'message', - cellDataType: 'text', - flex: 3, - headerName: intl.formatMessage({ id: 'announcements.table.message' }), - }, { field: 'startDate', - cellRenderer: DateCellRenderer, - flex: 3, + valueFormatter: renderDate, headerName: intl.formatMessage({ id: 'announcements.table.startDate' }), + sort: 'asc', + sortIndex: 1, + initialWidth: 150, }, { field: 'endDate', - cellRenderer: DateCellRenderer, - flex: 3, + valueFormatter: renderDate, headerName: intl.formatMessage({ id: 'announcements.table.endDate' }), + sort: 'asc', + sortIndex: 2, + initialWidth: 150, }, { field: 'severity', - cellDataType: 'text', - flex: 2, + valueFormatter: renderSeverity, headerName: intl.formatMessage({ id: 'announcements.severity' }), - valueGetter: (value: ValueGetterParams) => convertSeverity(value.data.severity), + initialWidth: 150, + }, + { + field: 'message', + cellDataType: 'text', + flex: 1, + headerName: intl.formatMessage({ id: 'announcements.table.message' }), }, { field: 'id', - cellRenderer: CancelButtonCellRenderer, + cellRenderer: CancelCellRenderer, cellRendererParams: { onClickHandler: handleDeleteAnnouncement }, - flex: 2, - headerName: intl.formatMessage({ id: 'announcements.table.cancel' }), + headerName: '', + initialWidth: 70, }, ], - [intl, convertSeverity, handleDeleteAnnouncement] + [renderDate, intl, handleDeleteAnnouncement, renderSeverity] ); // Note: using for the columns didn't work return ( - + diff --git a/src/pages/announcements/cancel-button-cell-renderer.tsx b/src/pages/announcements/cancel-button-cell-renderer.tsx deleted file mode 100644 index bb7773f..0000000 --- a/src/pages/announcements/cancel-button-cell-renderer.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2025, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -import { Box, IconButton } from '@mui/material'; -import { DisabledByDefault } from '@mui/icons-material'; -import { UUID } from 'crypto'; - -export type CancelButtonCellRendererProps = { value: UUID; onClickHandler: Function }; - -export function CancelButtonCellRenderer({ value, onClickHandler }: Readonly) { - return ( - - { - onClickHandler(value); - }} - > - - - - ); -} diff --git a/src/pages/announcements/cancel-cell-renderer.tsx b/src/pages/announcements/cancel-cell-renderer.tsx new file mode 100644 index 0000000..bb6d0bc --- /dev/null +++ b/src/pages/announcements/cancel-cell-renderer.tsx @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import type { UUID } from 'crypto'; +import { useCallback } from 'react'; +import { IconButton, Tooltip } from '@mui/material'; +import { Delete } from '@mui/icons-material'; +import { FormattedMessage } from 'react-intl'; + +export type CancelButtonCellRendererProps = { value: UUID; onClickHandler: Function }; + +export default function CancelCellRenderer({ value, onClickHandler }: Readonly) { + return ( + }> + { + onClickHandler(value); + }, [onClickHandler, value])} + > + + + + ); +} diff --git a/src/pages/announcements/date-cell-renderer.tsx b/src/pages/announcements/date-cell-renderer.tsx deleted file mode 100644 index 80d7021..0000000 --- a/src/pages/announcements/date-cell-renderer.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2025, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -import { useIntl } from 'react-intl'; -import { Box, Tooltip } from '@mui/material'; - -export type DateCellRendererProps = { value: string }; - -export function DateCellRenderer({ value }: Readonly) { - const intl = useIntl(); - - const dateValue = new Date(value); - if (!Number.isNaN(dateValue.getDate())) { - const time = new Intl.DateTimeFormat(intl.locale, { - timeStyle: 'medium', - hour12: false, - }).format(dateValue); - const displayedDate = - intl.locale === 'en' ? dateValue.toISOString().substring(0, 10) : dateValue.toLocaleDateString(intl.locale); - - const cellText = displayedDate + ' ' + time; - - const fullDate = new Intl.DateTimeFormat(intl.locale, { - dateStyle: 'long', - timeStyle: 'long', - hour12: false, - }).format(dateValue); - - return ( - - - {cellText} - - - ); - } -} diff --git a/src/translations/fr.json b/src/translations/fr.json index 651c059..fb3c765 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -114,7 +114,7 @@ "announcements.table.message": "Message", "announcements.table.startDate": "Date de début", "announcements.table.endDate": "Date de fin", - "announcements.table.cancel": "Annulation", + "announcements.table.cancel": "Annulé", "announcements.severity": "Sévérité", "announcements.severity.INFO": "Information", "announcements.severity.WARN": "Avertissement", From cc5aa4f40a12ec94cd6b2d36ed9d91fe2e2f5c75 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 14 May 2025 09:02:14 +0200 Subject: [PATCH 20/23] review --- src/pages/announcements/add-announcement-form.tsx | 8 ++------ src/pages/announcements/announcements-page.tsx | 4 +++- src/pages/announcements/cancel-cell-renderer.tsx | 15 ++++++--------- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/pages/announcements/add-announcement-form.tsx b/src/pages/announcements/add-announcement-form.tsx index 643a29f..48d42f4 100644 --- a/src/pages/announcements/add-announcement-form.tsx +++ b/src/pages/announcements/add-announcement-form.tsx @@ -103,11 +103,9 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< formContext={formContext} onSuccess={onSubmit} - //criteriaMode="all" - //mode="all" - mode="onChange" + //criteriaMode="all" ? + mode="onChange" // or maybe mode "all"? reValidateMode="onChange" - //reValidateMode="onChange" FormProps={{ style: { height: '100%' } }} > @@ -156,9 +154,7 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< name={MESSAGE} label={intl.formatMessage({ id: 'announcements.form.message' })} rows={5} // why does it do nothing even if the field is set as multiline?! - //style={{ height: '100%' }} fullWidth - //inputProps={{ maxLength: 200 }} /> diff --git a/src/pages/announcements/announcements-page.tsx b/src/pages/announcements/announcements-page.tsx index fca1e68..e48f682 100644 --- a/src/pages/announcements/announcements-page.tsx +++ b/src/pages/announcements/announcements-page.tsx @@ -118,6 +118,8 @@ export default function AnnouncementsPage() { [renderDate, intl, handleDeleteAnnouncement, renderSeverity] ); + const gridContext = useMemo(() => ({ refresh: loadDataAndSave }), [loadDataAndSave]); + // Note: using for the columns didn't work return ( @@ -150,7 +152,7 @@ export default function AnnouncementsPage() { defaultColDef={defaultColDef} gridId="table-announcements" getRowId={getRowId} - context={useMemo(() => ({ refresh: loadDataAndSave }), [loadDataAndSave])} + context={gridContext} /> diff --git a/src/pages/announcements/cancel-cell-renderer.tsx b/src/pages/announcements/cancel-cell-renderer.tsx index bb6d0bc..900804e 100644 --- a/src/pages/announcements/cancel-cell-renderer.tsx +++ b/src/pages/announcements/cancel-cell-renderer.tsx @@ -6,22 +6,19 @@ */ import type { UUID } from 'crypto'; import { useCallback } from 'react'; -import { IconButton, Tooltip } from '@mui/material'; +import { IconButton, type IconButtonProps, Tooltip } from '@mui/material'; import { Delete } from '@mui/icons-material'; import { FormattedMessage } from 'react-intl'; -export type CancelButtonCellRendererProps = { value: UUID; onClickHandler: Function }; +export type CancelButtonCellRendererProps = { value: UUID; onClickHandler: (value: UUID) => void }; export default function CancelCellRenderer({ value, onClickHandler }: Readonly) { + const handleClick = useCallback>(() => { + onClickHandler(value); + }, [onClickHandler, value]); return ( }> - { - onClickHandler(value); - }, [onClickHandler, value])} - > + From d16a0ee680c6e4142cc475e1a4e8c7c759a36419 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 14 May 2025 17:05:51 +0200 Subject: [PATCH 21/23] review --- package-lock.json | 49 ++++++++++++++++--- package.json | 4 +- .../announcements/add-announcement-form.tsx | 7 +-- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a193ab..acb0601 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,12 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.95.0.tgz", + "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.98.0.tgz", "@hookform/resolvers": "^4.0.0", "@mui/icons-material": "^5.16.14", "@mui/lab": "5.0.0-alpha.175", "@mui/material": "^5.16.14", - "@mui/x-date-pickers": "^7.29.1", + "@mui/x-date-pickers": "^7.29.3", "@mui/x-tree-view": "^7.28.1", "@reduxjs/toolkit": "^2.5.1", "ag-grid-community": "^33.1.0", @@ -3074,19 +3074,21 @@ } }, "node_modules/@gridsuite/commons-ui": { - "version": "0.95.0", - "resolved": "file:../commons-ui/gridsuite-commons-ui-0.95.0.tgz", - "integrity": "sha512-FuAtyUfSCtCJX9oCTSigBBWs+cyQQo4CoKDi9zedtQb/BlN50D2D3rqEnejaWF8kkLzF/rAZ3Ys1MstvNOqjSA==", + "version": "0.98.0", + "resolved": "file:../commons-ui/gridsuite-commons-ui-0.98.0.tgz", + "integrity": "sha512-uo1leDfigiWf4ht3PAk3mm5Q0Pb1SiN7sBpL67kmETYosdQZkLpZuoACLxTyB7ZGOFh88qB0lbQjorTBe6v5Hg==", "license": "MPL-2.0", "dependencies": { "@ag-grid-community/locale": "^33.1.0", "@hello-pangea/dnd": "^18.0.1", + "@material-symbols/svg-400": "^0.31.2", "@react-querybuilder/dnd": "^8.2.0", "@react-querybuilder/material": "^8.2.0", "autosuggest-highlight": "^3.3.4", "clsx": "^2.1.1", "jwt-decode": "^4.0.0", "localized-countries": "^2.0.0", + "mui-nested-menu": "^4.0.0", "oidc-client": "^1.11.5", "prop-types": "^15.8.1", "react-csv-downloader": "^3.3.0", @@ -3794,6 +3796,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@material-symbols/svg-400": { + "version": "0.31.2", + "resolved": "https://registry.npmjs.org/@material-symbols/svg-400/-/svg-400-0.31.2.tgz", + "integrity": "sha512-XKl1pC00ogBHw2NGqJt31XgwXX5bLVk9BgujxPe6lAE015L5Ji5BIG8le6/6xf5QViIqcRj4IWyh69JlJO/Gyg==", + "license": "Apache-2.0" + }, "node_modules/@mui/base": { "version": "5.0.0-beta.40-0", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40-0.tgz", @@ -4096,9 +4104,9 @@ } }, "node_modules/@mui/x-date-pickers": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.29.1.tgz", - "integrity": "sha512-0RibuJ1YOgUVNqS4aSIXmxpqZ5YWLsxSM84GFlm3M7WW8UbwBY2QzG4vcOsLOYUwk2aqhcP6cpnrIyoLYUsS7g==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.29.3.tgz", + "integrity": "sha512-/A0/8fpLnEFeJKr5YQsI8jqlWPJlOtgfCGcqXHVDOLxgV3lW49+Kh5TZAc1yi6HKT3AG6k4DkNwTuu/RjJeMFA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", @@ -12206,6 +12214,31 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mui-nested-menu": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/mui-nested-menu/-/mui-nested-menu-4.0.1.tgz", + "integrity": "sha512-o/UaG3oXvHI+phKZzTJdX/fAqgJXQC5xjo/KjMrJq8XtShs+n+JmVYCqD6ATIyoTamEt7+5LAjcIy4iyARcKdg==", + "license": "MIT", + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material": "^5.0.0 || ^6.0.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", diff --git a/package.json b/package.json index 0205a46..08589f0 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,12 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.95.0.tgz", + "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.98.0.tgz", "@hookform/resolvers": "^4.0.0", "@mui/icons-material": "^5.16.14", "@mui/lab": "5.0.0-alpha.175", "@mui/material": "^5.16.14", - "@mui/x-date-pickers": "^7.29.1", + "@mui/x-date-pickers": "^7.29.3", "@mui/x-tree-view": "^7.28.1", "@reduxjs/toolkit": "^2.5.1", "ag-grid-community": "^33.1.0", diff --git a/src/pages/announcements/add-announcement-form.tsx b/src/pages/announcements/add-announcement-form.tsx index 48d42f4..6e6b382 100644 --- a/src/pages/announcements/add-announcement-form.tsx +++ b/src/pages/announcements/add-announcement-form.tsx @@ -158,12 +158,7 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< /> - + From 18cd5a59058b58ea3033d9935bf3aac73034afc1 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 15 May 2025 14:08:47 +0200 Subject: [PATCH 22/23] update --- package-lock.json | 9 +++++---- package.json | 2 +- src/pages/announcements/add-announcement-form.tsx | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index acb0601..0cd0068 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.98.0.tgz", + "@gridsuite/commons-ui": "0.100.0", "@hookform/resolvers": "^4.0.0", "@mui/icons-material": "^5.16.14", "@mui/lab": "5.0.0-alpha.175", @@ -3074,14 +3074,15 @@ } }, "node_modules/@gridsuite/commons-ui": { - "version": "0.98.0", - "resolved": "file:../commons-ui/gridsuite-commons-ui-0.98.0.tgz", - "integrity": "sha512-uo1leDfigiWf4ht3PAk3mm5Q0Pb1SiN7sBpL67kmETYosdQZkLpZuoACLxTyB7ZGOFh88qB0lbQjorTBe6v5Hg==", + "version": "0.100.0", + "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.100.0.tgz", + "integrity": "sha512-eQfRkNh4NIfFUz+hPLiu4XXQz7wzUg6jpMcQsJ0D5oqvztS1amIHgtdFkSxUBhPzwIvQ22mGpo2yeSdIi/IY/Q==", "license": "MPL-2.0", "dependencies": { "@ag-grid-community/locale": "^33.1.0", "@hello-pangea/dnd": "^18.0.1", "@material-symbols/svg-400": "^0.31.2", + "@mui/base": "^5.0.0-beta.40-0", "@react-querybuilder/dnd": "^8.2.0", "@react-querybuilder/material": "^8.2.0", "autosuggest-highlight": "^3.3.4", diff --git a/package.json b/package.json index 08589f0..4b1890e 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.98.0.tgz", + "@gridsuite/commons-ui": "0.100.0", "@hookform/resolvers": "^4.0.0", "@mui/icons-material": "^5.16.14", "@mui/lab": "5.0.0-alpha.175", diff --git a/src/pages/announcements/add-announcement-form.tsx b/src/pages/announcements/add-announcement-form.tsx index 6e6b382..d7bd256 100644 --- a/src/pages/announcements/add-announcement-form.tsx +++ b/src/pages/announcements/add-announcement-form.tsx @@ -77,7 +77,7 @@ export default function AddAnnouncementForm({ onAnnouncementCreated }: Readonly< [SEVERITY]: null, }, }); - const { formState, getValues } = formContext; + const { getValues } = formContext; const startDateValue = getValues(START_DATE); const onSubmit = useCallback>( From c4e87939b96e2c2b4517beba932b900613bcb418 Mon Sep 17 00:00:00 2001 From: Tristan <135599584+Tristan-WorkGH@users.noreply.github.com> Date: Thu, 15 May 2025 14:22:01 +0200 Subject: [PATCH 23/23] fix import --- src/components/App/app-wrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/App/app-wrapper.tsx b/src/components/App/app-wrapper.tsx index 0f4d802..9dc16ae 100644 --- a/src/components/App/app-wrapper.tsx +++ b/src/components/App/app-wrapper.tsx @@ -6,7 +6,7 @@ */ import App from './app'; -import { FunctionComponent, PropsWithChildren, useMemo } from 'react'; +import { FunctionComponent, type PropsWithChildren, useMemo } from 'react'; import { CssBaseline, responsiveFontSizes, ThemeOptions } from '@mui/material'; import { createTheme, StyledEngineProvider, Theme, ThemeProvider } from '@mui/material/styles'; import { enUS as MuiCoreEnUS, frFR as MuiCoreFrFR } from '@mui/material/locale';