mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
remove duplicate ConnectServiceSelect code
This commit is contained in:
@@ -1,204 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import _get from "lodash/get";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
List,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Typography,
|
||||
Radio,
|
||||
} from "@mui/material";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
|
||||
import { IConnectServiceSelectProps } from ".";
|
||||
import useStyles from "./styles";
|
||||
import Loading from "@src/components/Loading";
|
||||
|
||||
export interface IPopupContentsProps
|
||||
extends Omit<IConnectServiceSelectProps, "className" | "TextFieldProps"> {}
|
||||
|
||||
// TODO: Implement infinite scroll here
|
||||
export default function PopupContents({
|
||||
value = [],
|
||||
onChange,
|
||||
config,
|
||||
|
||||
docRef,
|
||||
}: IPopupContentsProps) {
|
||||
const url = config.url;
|
||||
const titleKey = config.titleKey ?? config.primaryKey;
|
||||
const subtitleKey = config.subtitleKey;
|
||||
const resultsKey = config.resultsKey;
|
||||
const primaryKey = config.primaryKey;
|
||||
const multiple = Boolean(config.multiple);
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
// Webservice search query
|
||||
const [query, setQuery] = useState("");
|
||||
// Webservice response
|
||||
const [response, setResponse] = useState<any | null>(null);
|
||||
|
||||
const [docData, setDocData] = useState<any | null>(null);
|
||||
useEffect(() => {
|
||||
docRef.get().then((d) => setDocData(d.data()));
|
||||
}, []);
|
||||
|
||||
const hits: any["hits"] = _get(response, resultsKey) ?? [];
|
||||
const [search] = useDebouncedCallback(
|
||||
async (query: string) => {
|
||||
if (!docData) return;
|
||||
if (!url) return;
|
||||
const uri = new URL(url),
|
||||
params = { q: query };
|
||||
Object.keys(params).forEach((key) =>
|
||||
uri.searchParams.append(key, params[key])
|
||||
);
|
||||
|
||||
const resp = await fetch(uri.toString(), {
|
||||
method: "POST",
|
||||
body: JSON.stringify(docData),
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
|
||||
const jsonBody = await resp.json();
|
||||
setResponse(jsonBody);
|
||||
},
|
||||
1000,
|
||||
{ leading: true }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
search(query);
|
||||
}, [query, docData]);
|
||||
|
||||
if (!response) return <Loading />;
|
||||
|
||||
const select = (hit: any) => () => {
|
||||
if (multiple) onChange([...value, hit]);
|
||||
else onChange([hit]);
|
||||
};
|
||||
const deselect = (hit: any) => () => {
|
||||
if (multiple)
|
||||
onChange(value.filter((v) => v[primaryKey] !== hit[primaryKey]));
|
||||
else onChange([]);
|
||||
};
|
||||
|
||||
const selectedValues = value?.map((item) => _get(item, primaryKey));
|
||||
|
||||
const clearSelection = () => onChange([]);
|
||||
|
||||
return (
|
||||
<Grid container direction="column" className={classes.grid}>
|
||||
<Grid item className={classes.searchRow}>
|
||||
<TextField
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
fullWidth
|
||||
variant="filled"
|
||||
margin="dense"
|
||||
label="Search items"
|
||||
className={classes.noMargins}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs className={classes.listRow}>
|
||||
<List className={classes.list}>
|
||||
{hits.map((hit) => {
|
||||
const isSelected =
|
||||
selectedValues.indexOf(_get(hit, primaryKey)) !== -1;
|
||||
console.log(`Selected Values: ${selectedValues}`);
|
||||
return (
|
||||
<React.Fragment key={_get(hit, primaryKey)}>
|
||||
<MenuItem
|
||||
dense
|
||||
onClick={isSelected ? deselect(hit) : select(hit)}
|
||||
>
|
||||
<ListItemIcon className={classes.checkboxContainer}>
|
||||
{multiple ? (
|
||||
<Checkbox
|
||||
edge="start"
|
||||
checked={isSelected}
|
||||
tabIndex={-1}
|
||||
color="secondary"
|
||||
className={classes.checkbox}
|
||||
disableRipple
|
||||
inputProps={{
|
||||
"aria-labelledby": `label-${_get(hit, primaryKey)}`,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Radio
|
||||
edge="start"
|
||||
checked={isSelected}
|
||||
tabIndex={-1}
|
||||
color="secondary"
|
||||
className={classes.checkbox}
|
||||
disableRipple
|
||||
inputProps={{
|
||||
"aria-labelledby": `label-${_get(hit, primaryKey)}`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
id={`label-${_get(hit, primaryKey)}`}
|
||||
primary={_get(hit, titleKey)}
|
||||
secondary={!subtitleKey ? "" : _get(hit, subtitleKey)}
|
||||
/>
|
||||
</MenuItem>
|
||||
<Divider className={classes.divider} />
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</Grid>
|
||||
|
||||
{multiple && (
|
||||
<Grid item className={clsx(classes.footerRow, classes.selectedRow)}>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography
|
||||
variant="button"
|
||||
color="textSecondary"
|
||||
className={classes.selectedNum}
|
||||
>
|
||||
{value?.length} of {hits?.length}
|
||||
</Typography>
|
||||
|
||||
<Button
|
||||
disabled={!value || value.length === 0}
|
||||
onClick={clearSelection}
|
||||
color="primary"
|
||||
className={classes.selectAllButton}
|
||||
>
|
||||
Clear selection
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { lazy, Suspense } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { TextField, TextFieldProps } from "@mui/material";
|
||||
import useStyles from "./styles";
|
||||
import Loading from "@src/components/Loading";
|
||||
import ErrorBoundary from "@src/components/ErrorBoundary";
|
||||
|
||||
const PopupContents = lazy(
|
||||
() => import("./PopupContents" /* webpackChunkName: "PopupContents" */)
|
||||
);
|
||||
|
||||
export type ServiceValue = { value: string; [prop: string]: any };
|
||||
|
||||
export interface IConnectServiceSelectProps {
|
||||
value: ServiceValue[];
|
||||
onChange: (value: ServiceValue[]) => void;
|
||||
row: any;
|
||||
config: {
|
||||
displayKey: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
editable?: boolean;
|
||||
/** Optional style overrides for root MUI `TextField` component */
|
||||
className?: string;
|
||||
/** Override any props of the root MUI `TextField` component */
|
||||
TextFieldProps?: Partial<TextFieldProps>;
|
||||
docRef: firebase.default.firestore.DocumentReference;
|
||||
}
|
||||
|
||||
export default function ConnectServiceSelect({
|
||||
value = [],
|
||||
className,
|
||||
TextFieldProps = {},
|
||||
...props
|
||||
}: IConnectServiceSelectProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
const sanitisedValue = Array.isArray(value) ? value : [];
|
||||
|
||||
return (
|
||||
<TextField
|
||||
label=""
|
||||
hiddenLabel
|
||||
variant={"filled" as any}
|
||||
select
|
||||
value={sanitisedValue}
|
||||
className={clsx(classes.root, className)}
|
||||
{...TextFieldProps}
|
||||
SelectProps={{
|
||||
renderValue: (value) => `${(value as any[]).length} selected`,
|
||||
displayEmpty: true,
|
||||
classes: { root: classes.selectRoot },
|
||||
...TextFieldProps.SelectProps,
|
||||
// Must have this set to prevent MUI transforming `value`
|
||||
// prop for this component to a comma-separated string
|
||||
MenuProps: {
|
||||
classes: { paper: classes.paper, list: classes.menuChild },
|
||||
MenuListProps: { disablePadding: true },
|
||||
anchorOrigin: { vertical: "bottom", horizontal: "center" },
|
||||
transformOrigin: { vertical: "top", horizontal: "center" },
|
||||
...TextFieldProps.SelectProps?.MenuProps,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ErrorBoundary>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<PopupContents value={sanitisedValue} {...props} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
</TextField>
|
||||
);
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import { makeStyles, createStyles } from "@mui/styles";
|
||||
|
||||
export const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
root: { minWidth: 200 },
|
||||
selectRoot: { paddingRight: theme.spacing(4) },
|
||||
|
||||
paper: { overflow: "hidden", maxHeight: "calc(100% - 48px)" },
|
||||
menuChild: {
|
||||
padding: `0 ${theme.spacing(2)}`,
|
||||
minWidth: 340,
|
||||
// Need to set fixed height here so popup is positioned correctly
|
||||
height: 340,
|
||||
},
|
||||
|
||||
grid: { outline: 0 },
|
||||
|
||||
noMargins: { margin: 0 },
|
||||
|
||||
searchRow: { marginTop: theme.spacing(2) },
|
||||
|
||||
listRow: {
|
||||
background: `${theme.palette.background.paper} no-repeat`,
|
||||
position: "relative",
|
||||
margin: theme.spacing(0, -2),
|
||||
maxWidth: `calc(100% + ${theme.spacing(4)})`,
|
||||
|
||||
"&::before, &::after": {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 9,
|
||||
|
||||
display: "block",
|
||||
height: 16,
|
||||
|
||||
background: `linear-gradient(to bottom, #fff, rgba(255, 255, 255, 0))`,
|
||||
},
|
||||
|
||||
"&::after": {
|
||||
top: "auto",
|
||||
bottom: 0,
|
||||
background: `linear-gradient(to top, #fff, rgba(255, 255, 255, 0))`,
|
||||
},
|
||||
},
|
||||
list: () => {
|
||||
let maxHeightDeductions = 0;
|
||||
maxHeightDeductions -= 64; // search box
|
||||
maxHeightDeductions -= 48; // multiple
|
||||
maxHeightDeductions += 8; // footer padding
|
||||
|
||||
return {
|
||||
padding: theme.spacing(2, 0),
|
||||
overflowY: "auto" as "auto",
|
||||
// height: `calc(340px - ${-maxHeightDeductions}px)`,
|
||||
height: 340 + maxHeightDeductions,
|
||||
};
|
||||
},
|
||||
|
||||
checkboxContainer: { minWidth: theme.spacing(36 / 8) },
|
||||
checkbox: {
|
||||
padding: theme.spacing(6 / 8, 9 / 8),
|
||||
"&:hover": { background: "transparent" },
|
||||
},
|
||||
|
||||
divider: { margin: theme.spacing(0, 2, 0, 6.5) },
|
||||
|
||||
footerRow: { marginBottom: theme.spacing(2) },
|
||||
selectedRow: {
|
||||
"$listRow + &": { marginTop: -theme.spacing(1) },
|
||||
"$footerRow + &": { marginTop: -theme.spacing(2) },
|
||||
|
||||
marginBottom: 0,
|
||||
"& > div": { height: 48 },
|
||||
},
|
||||
selectAllButton: { marginRight: -theme.spacing(1) },
|
||||
selectedNum: { fontFeatureSettings: '"tnum"' },
|
||||
})
|
||||
);
|
||||
|
||||
export default useStyles;
|
||||
Reference in New Issue
Block a user