Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,22 @@
"prop-types": "^15.7.2",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-hook-form": "^7.46.1",
"react-hook-form": "^7.51.1",
"react-intl": "^6.0.0",
"react-redux": "^8.0.0",
"react-router-dom": "^6.0.0",
"react-scripts": "^5.0.0",
"react-window": "^1.8.5",
"reconnecting-websocket": "^4.4.0",
"redux": "^4.0.5",
"tinyduration": "^3.3.0",
"type-fest": "^4.11.1",
"typeface-roboto": "^1.0.0",
"typescript": "^5.1.3",
"yup": "^1.2.0"
"yup": "^1.4.0"
},
"scripts": {
"start": "react-scripts start",
"start": "PORT=3002 react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --watchAll=false",
"test:watch": "react-scripts test",
Expand Down
8 changes: 4 additions & 4 deletions public/idpSettings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"authority": "http://172.17.0.1:9090/",
"client_id": "my-client-2",
"redirect_uri": "http://localhost:3000/sign-in-callback",
"post_logout_redirect_uri": "http://localhost:3000/logout-callback",
"silent_redirect_uri": "http://localhost:3000/silent-renew-callback",
"client_id": "gridadmin-local",
"redirect_uri": "http://localhost:3002/sign-in-callback",
"post_logout_redirect_uri": "http://localhost:3002/logout-callback",
"silent_redirect_uri": "http://localhost:3002/silent-renew-callback",
"scope": "openid"
}
11 changes: 11 additions & 0 deletions src/components/App/app-top-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
useState,
} from 'react';
import { capitalize, Tab, TabProps, Tabs, useTheme } from '@mui/material';
import { Announcement } from '@mui/icons-material';
import { PeopleAlt } from '@mui/icons-material';
import { logout, TopBar } from '@gridsuite/commons-ui';
import { useParameterState } from '../parameters';
Expand Down Expand Up @@ -56,6 +57,16 @@ const tabs = new Map<MainPaths, ReactElement>([
key={`tab-${MainPaths.users}`}
/>,
],
[
MainPaths.announcements,
<TabNavLink
icon={<Announcement />}
label={<FormattedMessage id="appBar.tabs.announcement" />}
href={`/${MainPaths.announcements}`}
value={MainPaths.announcements}
key={`tab-${MainPaths.announcements}`}
/>,
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid storing ReactElement in a variable ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just use the existing pattern, if you think we should refactor this, we need to do an other PR.
How bad is it ?

]);

const AppTopBar: FunctionComponent = () => {
Expand Down
51 changes: 51 additions & 0 deletions src/pages/announcements/Announcements.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2024, 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, useState } from 'react';
import { useIntl } from 'react-intl';
import { AppBar, Button, Grid, List, Toolbar } from '@mui/material';
import { AddCircleOutline } from '@mui/icons-material';
import { CreateAnnouncementDialog } from './CreateAnnouncementDialog';
import { useAnnouncements } from './useAnnouncements';
import { ListItemAnnouncement } from './ListItemAnnouncement';

const Announcements: FunctionComponent = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can separate in different components the top bar from the announcements list

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm extracting the Topbar/Toolbar combo, you think about something else ?

const intl = useIntl();
const [openDialog, setOpenDialog] = useState(false);
const announcements = useAnnouncements();

return (
<Grid item container spacing={2} direction="column">
<Grid item>
<AppBar position="static" color="default">
<Toolbar variant="dense">
<Button
onClick={() => setOpenDialog(true)}
variant="outlined"
startIcon={<AddCircleOutline />}
size="small"
>
{intl.formatMessage({ id: 'announcements.add' })}
</Button>
</Toolbar>
</AppBar>
</Grid>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you extract that in common component with Users ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The layout part

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok doing it

<CreateAnnouncementDialog
open={openDialog}
onClose={() => setOpenDialog(false)}
/>
<Grid item>
<List sx={{ maxWidth: 1400 }}>
{announcements.map((announcement) => (
<ListItemAnnouncement {...announcement} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AnnouncementItem ? And I think I prefer when the arguments are an object.
<AnnouncementItem item={announcement} /> ? I think it makes it more readable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer like this, for me this way is clearer.
export const ListItemAnnouncement: FunctionComponent<Announcement> = (
No need to define a new ListItemAnnouncementProps with just one item which is an Announcement...
It's already an item itself, so why giving an inner item prop ?
It follow the pattern ListItemText.
But if it's rly a bad way and if the second reviewer agree with you, I'll change it.

))}
</List>
</Grid>
</Grid>
);
};
export default Announcements;
87 changes: 87 additions & 0 deletions src/pages/announcements/CreateAnnouncementDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2024, 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 } from 'react';
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
} from '@mui/material';
import { FormattedMessage } from 'react-intl';
import { FormProvider, useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { DurationInput } from './DurationInput';
import { SubmitButton, TextInput } from '@gridsuite/commons-ui';
import {
AnnouncementFormData,
emptyFormData,
formSchema,
fromFrontToBack,
MESSAGE,
} from './utils';
import { UserAdminSrv } from '../../services';

interface CreateAnnouncementDialogProps {
open: boolean;
onClose: () => void;
}

export const CreateAnnouncementDialog: FunctionComponent<
CreateAnnouncementDialogProps
> = (props) => {
const formMethods = useForm<AnnouncementFormData>({
defaultValues: emptyFormData,
//@ts-ignore because yup TS is broken
resolver: yupResolver(formSchema),
});

const { handleSubmit, reset } = formMethods;

const onSubmit = useCallback(
(formData: AnnouncementFormData) => {
UserAdminSrv.createAnnouncement(fromFrontToBack(formData));
reset();
props.onClose();
},
[props, reset]
);

return (
//@ts-ignore because RHF TS is broken
<FormProvider validationSchema={formSchema} {...formMethods}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure ? It's not just that we shouldn't define this property ? Schema is in the yup resolver already, no ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I'm wondering if we don't do it wrong since the start haha

<Dialog open={props.open} onClose={props.onClose}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can unwrap {open, onClose} in the method argument directly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer like this then we know it comes from the props. Maybe something to define in the team I agree.

<DialogTitle>
<FormattedMessage id="announcements.dialog.title" />
</DialogTitle>
<DialogContent>
<Grid container spacing={2} direction="column">
<Grid item>
<TextInput
name={MESSAGE}
label={'announcements.dialog.input'}
formProps={{
multiline: true,
variant: 'filled',
minRows: 3,
}}
/>
</Grid>
<DurationInput />
</Grid>
</DialogContent>
<DialogActions>
<SubmitButton
onClick={handleSubmit(onSubmit)}
variant="outlined"
/>
</DialogActions>
</Dialog>
</FormProvider>
);
};
73 changes: 73 additions & 0 deletions src/pages/announcements/DurationInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (c) 2024, 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 { Grid, Typography } from '@mui/material';
import { DAYS, DURATION, HOURS, MINUTES } from './utils';
import { IntegerInput } from '@gridsuite/commons-ui';
import { useIntl } from 'react-intl';

const centerStyle = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
};

const DaysAdornment = {
position: 'end',
text: 'd',
};
const HoursAdornment = {
position: 'end',
text: 'h',
};
const MinutesAdornment = {
position: 'end',
text: 'm',
};

export const DurationInput = () => {
const intl = useIntl();

return (
<Grid item container columns={30} justifyContent="center">
<Grid item xs={6} sx={centerStyle}>
<Typography variant="body1">
{intl.formatMessage({
id: 'announcements.dialog.duration',
})}
</Typography>
</Grid>
<Grid item xs={4}>
<IntegerInput
name={`${DURATION}.${DAYS}`}
label={'duration.days'}
adornment={DaysAdornment}
/>
</Grid>
<Grid item xs={1} sx={centerStyle}>
<Typography variant="h6">:</Typography>
</Grid>
<Grid item xs={4}>
<IntegerInput
name={`${DURATION}.${HOURS}`}
label={'duration.hours'}
adornment={HoursAdornment}
/>
</Grid>
<Grid item xs={1} sx={centerStyle}>
<Typography variant="h6">:</Typography>
</Grid>
<Grid item xs={4}>
<IntegerInput
name={`${DURATION}.${MINUTES}`}
label={'duration.minutes'}
adornment={MinutesAdornment}
/>
</Grid>
</Grid>
);
};
Loading