diff --git a/src/frontend/src/components/AppDialog.tsx b/src/frontend/src/components/AppDialog.tsx index cc045890a..1356d875f 100644 --- a/src/frontend/src/components/AppDialog.tsx +++ b/src/frontend/src/components/AppDialog.tsx @@ -40,7 +40,7 @@ const AppDialog: FC = ({ sx={theme => ({ padding: `0 ${theme.spacing(3)} ${theme.spacing(2)}`, - '& > :not(:first-child)': { + '& > :not(:first-of-type)': { marginLeft: theme.spacing(2), }, })} diff --git a/src/frontend/src/features/__shared__/hooks/useDialog.ts b/src/frontend/src/features/__shared__/hooks/useDialog.ts index a11fa2bca..e7e06fe2e 100644 --- a/src/frontend/src/features/__shared__/hooks/useDialog.ts +++ b/src/frontend/src/features/__shared__/hooks/useDialog.ts @@ -12,7 +12,9 @@ export interface DialogHookResult show: DialogActionFn; } -// TODO: make centralized redux modals, remove modal hook +/** + @deprecated + */ export default function useDialog( confirmAction: DialogConfirmActionFn, ): DialogHookResult { diff --git a/src/frontend/src/features/pages/components/PageCreateEditDialog/PageCreateEditDialog.test.tsx b/src/frontend/src/features/pages/components/PageCreateEditDialog/PageCreateEditDialog.test.tsx deleted file mode 100644 index c42cd53d5..000000000 --- a/src/frontend/src/features/pages/components/PageCreateEditDialog/PageCreateEditDialog.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { create } from 'src/test-utils'; -import { PageCreateEdit } from '../../models'; -import PageCreateEditDialog from './PageCreateEditDialog'; - -test('page can be created', async () => { - const submitFn = vi.fn(); - const ui = create - .component( - , - ) - .withReduxStore() - .please(); - - render(ui); - const pageDate = screen.getByRole('textbox', { name: /page date/i }); - await waitFor(() => expect(pageDate).toHaveDisplayValue('22.10.2023')); - await userEvent.click(screen.getByText(/create/i)); - - expect(submitFn).toHaveBeenCalledWith({ - date: '2023-10-22', - } as PageCreateEdit); -}); diff --git a/src/frontend/src/features/pages/components/PageCreateEditDialog/PageCreateEditDialog.tsx b/src/frontend/src/features/pages/components/PageCreateEditDialog/PageCreateEditDialog.tsx deleted file mode 100644 index 0a74bfc8f..000000000 --- a/src/frontend/src/features/pages/components/PageCreateEditDialog/PageCreateEditDialog.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { - Dialog, - DialogTitle, - DialogContent, - DialogActions, - Button, - DialogProps, -} from '@mui/material'; -import React, { useEffect, useMemo } from 'react'; -import { DatePicker } from 'src/components'; -import { useAppDispatch, useAppSelector } from 'src/features/__shared__/hooks'; -import { DialogCustomActionProps } from 'src/features/__shared__/types'; -import { PageCreateEdit } from 'src/features/pages/models'; -import { getDateForNewPage } from 'src/features/pages/thunks'; -import { useInput } from 'src/hooks'; -import { formatDate } from 'src/utils'; -import { mapToDateInputProps } from 'src/utils/inputMapping'; -import { validateDate } from 'src/utils/validation'; - -interface PageCreateEditDialogProps extends DialogProps, DialogCustomActionProps { - page?: PageCreateEdit; -} - -function useInitialDate(isDialogOpened: boolean, page?: PageCreateEdit) { - const dateForNewPage = useAppSelector(state => state.pages.dateForNewPage); - const dateForNewPageLoading = useAppSelector(state => state.pages.dateForNewPageLoading); - const dispatch = useAppDispatch(); - const isNewPage = !page; - - useEffect(() => { - if (isDialogOpened && isNewPage) { - dispatch(getDateForNewPage()); - } - }, [dispatch, isDialogOpened, isNewPage]); - - return useMemo(() => { - if (!isNewPage) { - return new Date(page.date); - } - - if (dateForNewPageLoading === 'succeeded' && dateForNewPage) { - return new Date(dateForNewPage); - } - - return new Date(); - }, [dateForNewPage, dateForNewPageLoading, isNewPage, page]); -} - -const PageCreateEditDialog: React.FC = ({ - page, - onDialogConfirm, - onDialogCancel, - ...dialogProps -}) => { - const { open: isDialogOpened } = dialogProps; - const initialDate = useInitialDate(isDialogOpened, page); - const isNewPage = !page; - const title = isNewPage ? 'New page' : 'Edit page'; - const submitText = isNewPage ? 'Create' : 'Save'; - - const { - inputProps: dateInputProps, - value: date, - setValue: setDate, - isInvalid: isDateInvalid, - } = useInput({ - initialValue: null, - errorHelperText: 'Date is required', - validate: validateDate, - mapToInputProps: mapToDateInputProps, - }); - - useEffect(() => { - if (isDialogOpened && initialDate) { - setDate(initialDate); - } - }, [initialDate, isDialogOpened, setDate]); - - const handleSubmitClick = (): void => { - if (date) { - onDialogConfirm({ - date: formatDate(date), - }); - } - }; - - return ( - - {title} - - - - - - - - - ); -}; - -export default PageCreateEditDialog; diff --git a/src/frontend/src/features/pages/components/PageCreateEditDialog/index.ts b/src/frontend/src/features/pages/components/PageCreateEditDialog/index.ts deleted file mode 100644 index 082337950..000000000 --- a/src/frontend/src/features/pages/components/PageCreateEditDialog/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import PageCreateEditDialog from './PageCreateEditDialog'; - -export default PageCreateEditDialog; diff --git a/src/frontend/src/features/pages/components/PageInputDialog/PageInputDialog.test.tsx b/src/frontend/src/features/pages/components/PageInputDialog/PageInputDialog.test.tsx new file mode 100644 index 000000000..302d3c606 --- /dev/null +++ b/src/frontend/src/features/pages/components/PageInputDialog/PageInputDialog.test.tsx @@ -0,0 +1,29 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { create } from 'src/test-utils'; +import { PageCreateEdit } from '../../models'; +import PageInputDialog from './PageInputDialog'; + +describe('when initial date is specified', () => { + test('should create new page with that date', async () => { + const submitFn = vi.fn(); + const ui = create + .component( + , + ) + .please(); + + render(ui); + const createButton = await screen.findByRole('button', { name: /create/i }); + await userEvent.click(createButton); + + expect(submitFn).toHaveBeenCalledWith<[PageCreateEdit]>({ date: '2023-10-22' }); + }); +}); diff --git a/src/frontend/src/features/pages/components/PageInputDialog/PageInputDialog.tsx b/src/frontend/src/features/pages/components/PageInputDialog/PageInputDialog.tsx new file mode 100644 index 000000000..f4e69c7b3 --- /dev/null +++ b/src/frontend/src/features/pages/components/PageInputDialog/PageInputDialog.tsx @@ -0,0 +1,82 @@ +import { Button } from '@mui/material'; +import { FC, useEffect } from 'react'; +import { AppDialog, DatePicker } from 'src/components'; +import { useInput } from 'src/hooks'; +import { formatDate } from 'src/utils'; +import { mapToDateInputProps } from 'src/utils/inputMapping'; +import { validateDate } from 'src/utils/validation'; +import { PageCreateEdit } from '../../models'; + +type PageInputDialogProps = { + title: string; + isOpened: boolean; + submitText: string; + initialDate: Date; + onClose: () => void; + onSubmit: (page: PageCreateEdit) => void; +}; + +const PageInputDialog: FC = ({ + title, + isOpened, + submitText, + initialDate, + onClose, + onSubmit, +}) => { + const dateInput = useInput({ + initialValue: initialDate, + errorHelperText: 'Date is required', + validate: validateDate, + mapToInputProps: mapToDateInputProps, + }); + + const setDate = dateInput.setValue; + + useEffect(() => { + if (isOpened && initialDate) { + setDate(initialDate); + } + }, [initialDate, isOpened, setDate]); + + const handleSubmitClick = (): void => { + if (dateInput.value) { + onSubmit({ + date: formatDate(dateInput.value), + }); + } + }; + + return ( + + } + actionSubmit={ + + } + actionCancel={ + + } + /> + ); +}; + +export default PageInputDialog; diff --git a/src/frontend/src/features/pages/components/PageInputDialog/index.ts b/src/frontend/src/features/pages/components/PageInputDialog/index.ts new file mode 100644 index 000000000..35223dec0 --- /dev/null +++ b/src/frontend/src/features/pages/components/PageInputDialog/index.ts @@ -0,0 +1,3 @@ +import PageInputDialog from './PageInputDialog'; + +export { PageInputDialog }; diff --git a/src/frontend/src/features/pages/components/PagesTableRow.tsx b/src/frontend/src/features/pages/components/PagesTableRow.tsx index 84bc6f15a..6de509a5d 100644 --- a/src/frontend/src/features/pages/components/PagesTableRow.tsx +++ b/src/frontend/src/features/pages/components/PagesTableRow.tsx @@ -1,29 +1,22 @@ import EditIcon from '@mui/icons-material/Edit'; import { TableRow, TableCell, Checkbox, Tooltip, IconButton, Link } from '@mui/material'; -import makeStyles from '@mui/styles/makeStyles'; -import dateFnsFormat from 'date-fns/format'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; -import { useAppDispatch, useDialog, useAppSelector } from '../../__shared__/hooks'; +import { formatDate } from 'src/utils'; +import { useAppDispatch, useAppSelector } from '../../__shared__/hooks'; import { PageCreateEdit, PageItem } from '../models'; import { pageSelected } from '../slice'; import { editPage } from '../thunks'; -import PageCreateEditDialog from './PageCreateEditDialog'; +import { PageInputDialog } from './PageInputDialog'; type PagesTableRowProps = { page: PageItem; }; -const useStyles = makeStyles(() => ({ - pageDateLink: { - // TODO: use theme value after Material 5 migration - fontWeight: 'bold', - }, -})); - const PagesTableRow: React.FC = ({ page }: PagesTableRowProps) => { - const classes = useStyles(); - const pageDate = dateFnsFormat(new Date(page.date), 'dd.MM.yyyy'); + const pageDate = new Date(page.date); + + const operationStatus = useAppSelector(state => state.pages.operationStatus); const isPageSelected = useAppSelector(state => state.pages.selectedPageIds.some(id => id === page.id), @@ -31,14 +24,30 @@ const PagesTableRow: React.FC = ({ page }: PagesTableRowProp const dispatch = useAppDispatch(); - const pageEditDialog = useDialog(pageInfo => { + const [isDialogOpened, setIsDialogOpened] = useState(false); + + useEffect(() => { + if (operationStatus === 'succeeded') { + setIsDialogOpened(false); + } + }, [operationStatus]); + + const handleOpenDialog = () => { + setIsDialogOpened(true); + }; + + const handleCloseDialog = () => { + setIsDialogOpened(false); + }; + + const handleEditPage = ({ date }: PageCreateEdit) => { dispatch( editPage({ id: page.id, - page: pageInfo, + page: { date }, }), ); - }); + }; const handleSelectPage = (): void => { dispatch( @@ -49,13 +58,16 @@ const PagesTableRow: React.FC = ({ page }: PagesTableRowProp ); }; - const handleEditClick = (): void => { - pageEditDialog.show(); - }; - return ( - + @@ -65,17 +77,17 @@ const PagesTableRow: React.FC = ({ page }: PagesTableRowProp to={`/pages/${page.id}`} variant="body1" color="primary" - className={classes.pageDateLink} underline="hover" + fontWeight="bold" > - {pageDate} + {formatDate(pageDate)} {page.countCalories} {page.countNotes} - + diff --git a/src/frontend/src/features/pages/components/PagesToolbar/PagesToolbar.tsx b/src/frontend/src/features/pages/components/PagesToolbar/PagesToolbar.tsx index 8d61c5103..1b01c1ec3 100644 --- a/src/frontend/src/features/pages/components/PagesToolbar/PagesToolbar.tsx +++ b/src/frontend/src/features/pages/components/PagesToolbar/PagesToolbar.tsx @@ -2,7 +2,7 @@ import AddIcon from '@mui/icons-material/Add'; import DeleteIcon from '@mui/icons-material/Delete'; import FilterListIcon from '@mui/icons-material/FilterList'; import { Box, IconButton, Popover, styled, Toolbar, Tooltip, Typography } from '@mui/material'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { ConfirmationDialog } from 'src/features/__shared__/components'; import { useAppDispatch, @@ -13,7 +13,8 @@ import { import { useToolbarStyles } from 'src/features/__shared__/styles'; import { PageCreateEdit } from 'src/features/pages/models'; import { createPage, deletePages } from 'src/features/pages/thunks'; -import PageCreateEditDialog from '../PageCreateEditDialog'; +import { useDateForNewPage } from '../../hooks'; +import { PageInputDialog } from '../PageInputDialog'; import PagesFilter from '../PagesFilter'; import ShowMoreTableOptions from '../ShowMoreTableOptions'; @@ -24,29 +25,50 @@ type PagesToolbarProps = React.PropsWithChildren; const PagesToolbar: React.FC = ({ children }) => { const classes = useToolbarStyles(); const selectedPageIds = useAppSelector(state => state.pages.selectedPageIds); + const operationStatus = useAppSelector(state => state.pages.operationStatus); const dispatch = useAppDispatch(); const [filter, showFilter] = usePopover(); - const pageCreateDialog = useDialog(page => { - dispatch(createPage(page)); - }); + const [isDialogOpened, setIsDialogOpened] = useState(false); + const dateForNewPage = useDateForNewPage(isDialogOpened); + + useEffect(() => { + if (operationStatus === 'succeeded') { + setIsDialogOpened(false); + } + }, [operationStatus]); const pagesDeleteDialog = useDialog(() => { dispatch(deletePages(selectedPageIds)); }); - const handleAddClick = (): void => { - pageCreateDialog.show(); + const handleOpenDialog = (): void => { + setIsDialogOpened(true); + }; + + const handleCloseDialog = () => { + setIsDialogOpened(false); }; - const handleDeleteClick = (): void => { + const handleCreatePage = (page: PageCreateEdit) => { + dispatch(createPage(page)); + }; + + const handleDelete = (): void => { pagesDeleteDialog.show(); }; return ( - + = ({ children }) => { - + @@ -84,7 +106,7 @@ const PagesToolbar: React.FC = ({ children }) => { - + diff --git a/src/frontend/src/features/pages/hooks/index.ts b/src/frontend/src/features/pages/hooks/index.ts new file mode 100644 index 000000000..527889564 --- /dev/null +++ b/src/frontend/src/features/pages/hooks/index.ts @@ -0,0 +1 @@ +export * from './useDateForNewPage'; diff --git a/src/frontend/src/features/pages/hooks/useDateForNewPage.ts b/src/frontend/src/features/pages/hooks/useDateForNewPage.ts new file mode 100644 index 000000000..64691b3f2 --- /dev/null +++ b/src/frontend/src/features/pages/hooks/useDateForNewPage.ts @@ -0,0 +1,20 @@ +import { useEffect, useMemo } from 'react'; +import { useAppDispatch, useAppSelector } from 'src/hooks'; +import { getDateForNewPage } from '../thunks'; + +export const useDateForNewPage = (isInitialized: boolean): Date => { + const dateForNewPage = useAppSelector(state => state.pages.dateForNewPage); + const status = useAppSelector(state => state.pages.dateForNewPageLoading); + const dispatch = useAppDispatch(); + + useEffect(() => { + if (isInitialized) { + dispatch(getDateForNewPage()); + } + }, [dispatch, isInitialized]); + + return useMemo( + () => (status === 'succeeded' && dateForNewPage ? new Date(dateForNewPage) : new Date()), + [dateForNewPage, status], + ); +};