saleor-dashboard/src/components/AssignProductDialog/AssignProductDialog.tsx
Michał Droń 5dbd6fed8a
Fix assigning products to collection (#2320)
* Reset modal state in collection details

* cover initial state

* modal updates

* cover case when there is no items to add

Co-authored-by: andrzejewsky <vox3r69@gmail.com>
2022-09-21 15:35:05 +02:00

193 lines
5.8 KiB
TypeScript

import {
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TableBody,
TableCell,
TableRow,
TextField,
} from "@material-ui/core";
import ConfirmButton from "@saleor/components/ConfirmButton";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import TableCellAvatar from "@saleor/components/TableCellAvatar";
import { SearchProductsQuery } from "@saleor/graphql";
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
import useSearchQuery from "@saleor/hooks/useSearchQuery";
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
import { maybe } from "@saleor/misc";
import useScrollableDialogStyle from "@saleor/styles/useScrollableDialogStyle";
import { DialogProps, FetchMoreProps, RelayToFlat } from "@saleor/types";
import React, { useEffect } from "react";
import InfiniteScroll from "react-infinite-scroll-component";
import { FormattedMessage, useIntl } from "react-intl";
import BackButton from "../BackButton";
import Checkbox from "../Checkbox";
import { messages } from "./messages";
import { useStyles } from "./styles";
export interface AssignProductDialogFormData {
products: RelayToFlat<SearchProductsQuery["search"]>;
query: string;
}
export interface AssignProductDialogProps extends FetchMoreProps, DialogProps {
confirmButtonState: ConfirmButtonTransitionState;
products: RelayToFlat<SearchProductsQuery["search"]>;
selectedIds?: Record<string, boolean>;
loading: boolean;
onFetch: (value: string) => void;
onSubmit: (data: string[]) => void;
}
const scrollableTargetId = "assignProductScrollableDialog";
const AssignProductDialog: React.FC<AssignProductDialogProps> = props => {
const {
confirmButtonState,
hasMore,
open,
loading,
products,
onClose,
onFetch,
onFetchMore,
onSubmit,
selectedIds,
} = props;
const classes = useStyles(props);
const scrollableDialogClasses = useScrollableDialogStyle({});
const intl = useIntl();
const [query, onQueryChange, queryReset] = useSearchQuery(onFetch);
const [productsDict, setProductsDict] = React.useState(selectedIds || {});
useEffect(() => {
if (selectedIds) {
setProductsDict(prev => {
const prevIds = Object.keys(prev);
const newIds = Object.keys(selectedIds);
const preSelected = newIds
.filter(n => !prevIds.includes(n))
.reduce((p, c) => ({ ...p, [c]: true }), {});
return { ...prev, ...preSelected };
});
}
}, [selectedIds]);
useModalDialogOpen(open, {
onOpen: () => {
queryReset();
setProductsDict(selectedIds);
},
});
const handleSubmit = () => {
const selectedProductsAsArray = Object.keys(productsDict)
.filter(key => productsDict[key])
.map(key => key);
onSubmit(selectedProductsAsArray);
};
const handleChange = productId => {
setProductsDict(prev => ({
...prev,
[productId]: !prev[productId] ?? true,
}));
};
return (
<Dialog
onClose={onClose}
open={open}
classes={{ paper: scrollableDialogClasses.dialog }}
fullWidth
maxWidth="sm"
>
<DialogTitle>
<FormattedMessage {...messages.assignVariantDialogHeader} />
</DialogTitle>
<DialogContent>
<TextField
name="query"
value={query}
onChange={onQueryChange}
label={intl.formatMessage(messages.assignProductDialogSearch)}
placeholder={intl.formatMessage(messages.assignProductDialogContent)}
fullWidth
InputProps={{
autoComplete: "off",
endAdornment: loading && <CircularProgress size={16} />,
}}
/>
</DialogContent>
<DialogContent
className={scrollableDialogClasses.scrollArea}
id={scrollableTargetId}
>
<InfiniteScroll
dataLength={products?.length}
next={onFetchMore}
hasMore={hasMore}
scrollThreshold="100px"
loader={
<div className={scrollableDialogClasses.loadMoreLoaderContainer}>
<CircularProgress size={16} />
</div>
}
scrollableTarget={scrollableTargetId}
>
<ResponsiveTable key="table">
<TableBody>
{products &&
products.map(product => {
const isSelected = productsDict[product.id] || false;
return (
<TableRow
key={product.id}
data-test-id="assign-product-table-row"
>
<TableCellAvatar
className={classes.avatar}
thumbnail={maybe(() => product.thumbnail.url)}
/>
<TableCell className={classes.colName}>
{product.name}
</TableCell>
<TableCell
padding="checkbox"
className={classes.checkboxCell}
>
<Checkbox
checked={isSelected}
onChange={() => handleChange(product.id)}
/>
</TableCell>
</TableRow>
);
})}
</TableBody>
</ResponsiveTable>
</InfiniteScroll>
</DialogContent>
<DialogActions>
<BackButton onClick={onClose} />
<ConfirmButton
data-test-id="submit"
transitionState={confirmButtonState}
type="submit"
onClick={handleSubmit}
>
<FormattedMessage {...messages.assignProductDialogButton} />
</ConfirmButton>
</DialogActions>
</Dialog>
);
};
AssignProductDialog.displayName = "AssignProductDialog";
export default AssignProductDialog;