building data grid for users. currently has example grid with dummy data
This commit is contained in:
parent
2690a2a5ad
commit
d050fdee55
8 changed files with 780 additions and 632 deletions
1099
package-lock.json
generated
1099
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -23,6 +23,7 @@
|
||||||
"@mui/icons-material": "^6.1.6",
|
"@mui/icons-material": "^6.1.6",
|
||||||
"@mui/material": "^6.1.6",
|
"@mui/material": "^6.1.6",
|
||||||
"@mui/styles": "^6.1.6",
|
"@mui/styles": "^6.1.6",
|
||||||
|
"@mui/x-data-grid": "^7.22.2",
|
||||||
"@mui/x-date-pickers": "^7.22.2",
|
"@mui/x-date-pickers": "^7.22.2",
|
||||||
"@sentry/react": "^8.38.0",
|
"@sentry/react": "^8.38.0",
|
||||||
"@types/classnames": "^2.3.0",
|
"@types/classnames": "^2.3.0",
|
||||||
|
|
@ -35,6 +36,7 @@
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-image": "^4.1.0",
|
"react-image": "^4.1.0",
|
||||||
|
"react-infinite-scroller": "^1.2.6",
|
||||||
"react-router-dom": "^6.28.0"
|
"react-router-dom": "^6.28.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -43,6 +45,7 @@
|
||||||
"@types/lodash": "^4.17.13",
|
"@types/lodash": "^4.17.13",
|
||||||
"@types/react": "^18.3.10",
|
"@types/react": "^18.3.10",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@types/react-infinite-scroller": "^1.2.5",
|
||||||
"@vitejs/plugin-react": "^4.3.2",
|
"@vitejs/plugin-react": "^4.3.2",
|
||||||
"eslint": "^9.11.1",
|
"eslint": "^9.11.1",
|
||||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,8 @@ export default function UserWrapper(props: Props) {
|
||||||
if (hasFetched.current) return;
|
if (hasFetched.current) return;
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
userAPI.getUserWithTeam(user_id).then(resp => {
|
userAPI.getUserWithTeam(user_id).then(resp => {
|
||||||
|
console.log(resp.data.team)
|
||||||
|
console.log(resp.data.user)
|
||||||
setGlobal({
|
setGlobal({
|
||||||
user: resp.data.user ? User.create(resp.data.user) : User.create(),
|
user: resp.data.user ? User.create(resp.data.user) : User.create(),
|
||||||
team: resp.data.team ? Team.create(resp.data.team) : Team.create(),
|
team: resp.data.team ? Team.create(resp.data.team) : Team.create(),
|
||||||
|
|
|
||||||
88
src/common/Loader.tsx
Normal file
88
src/common/Loader.tsx
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { Theme, useTheme } from "@mui/material";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import classnames from "classnames";
|
||||||
|
import { getDarkLogo, getLightLogo } from "services/whiteLabel";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
const smallSize = theme.breakpoints.down("sm") ? 32 : theme.breakpoints.up("lg") ? 64 : 48;
|
||||||
|
|
||||||
|
return ({
|
||||||
|
loader: {
|
||||||
|
height: "20vh",
|
||||||
|
width: "20vh",
|
||||||
|
position: "relative"
|
||||||
|
},
|
||||||
|
"@keyframes spin": {
|
||||||
|
from: {
|
||||||
|
transform: "rotate(0deg)"
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
transform: "rotate(360deg)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
spinner: {
|
||||||
|
position: "absolute",
|
||||||
|
width: "20vh",
|
||||||
|
height: "20vh",
|
||||||
|
lineHeight: "20vh",
|
||||||
|
margin: "0 auto",
|
||||||
|
borderRadius: "50%",
|
||||||
|
zIndex: 9,
|
||||||
|
animation: "$spin 0.75s linear infinite"
|
||||||
|
},
|
||||||
|
lightSpinner: {
|
||||||
|
borderTop: "0.25rem solid #fff"
|
||||||
|
},
|
||||||
|
darkSpinner: {
|
||||||
|
borderTop: "0.25rem solid #404040"
|
||||||
|
},
|
||||||
|
spinnerLogo: {
|
||||||
|
display: "block",
|
||||||
|
height: "8vh",
|
||||||
|
position: "absolute",
|
||||||
|
left: "50%",
|
||||||
|
top: "50%",
|
||||||
|
transform: "translate(-50%, -50%)"
|
||||||
|
},
|
||||||
|
smallLoader: {
|
||||||
|
height: smallSize,
|
||||||
|
width: smallSize
|
||||||
|
},
|
||||||
|
smallSpinner: {
|
||||||
|
width: smallSize,
|
||||||
|
height: smallSize,
|
||||||
|
lineHeight: smallSize,
|
||||||
|
borderTopWidth: theme.spacing(0.25)
|
||||||
|
},
|
||||||
|
smallSpinnerLogo: {
|
||||||
|
height: smallSize / 2
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
size?: "small" | "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Loader(props: Props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const theme = useTheme();
|
||||||
|
const { size } = props;
|
||||||
|
const isDarkTheme = theme.palette.mode === "dark";
|
||||||
|
return (
|
||||||
|
<div className={classnames(classes.loader, size === "small" && classes.smallLoader)}>
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
classes.spinner,
|
||||||
|
isDarkTheme ? classes.lightSpinner : classes.darkSpinner,
|
||||||
|
size === "small" && classes.smallSpinner
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
className={classnames(classes.spinnerLogo, size === "small" && classes.smallSpinnerLogo)}
|
||||||
|
src={isDarkTheme ? getLightLogo() : getDarkLogo()}
|
||||||
|
alt={process.env.REACT_APP_WEBSITE_TITLE + "-loader"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
182
src/common/SearchSelect.tsx
Normal file
182
src/common/SearchSelect.tsx
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
import { Autocomplete, Avatar, CircularProgress, createFilterOptions } from "@mui/material";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
// import Autocomplete, { createFilterOptions } from "@material-ui/lab/Autocomplete";
|
||||||
|
import { isArray } from "lodash";
|
||||||
|
import React, { CSSProperties } from "react";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() => ({
|
||||||
|
input: {
|
||||||
|
minWidth: `0 !important`
|
||||||
|
}})
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface Option {
|
||||||
|
value: any;
|
||||||
|
label: string;
|
||||||
|
group?: string;
|
||||||
|
new?: boolean;
|
||||||
|
icon?: string;
|
||||||
|
defaultCableID?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
selected?: Option | Option[] | null;
|
||||||
|
changeSelection?: (newSelected: Option | null) => void;
|
||||||
|
changeMulti?: (newSelected: Option[] | null) => void;
|
||||||
|
options: Option[];
|
||||||
|
label?: string;
|
||||||
|
group?: boolean;
|
||||||
|
creatable?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
multiple?: boolean;
|
||||||
|
variant?: "filled" | "outlined" | "standard";
|
||||||
|
style?: CSSProperties;
|
||||||
|
getOptionSelected?: any;
|
||||||
|
onChange?: (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
|
||||||
|
onKeyDown?: (e: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SearchSelect(props: Props) {
|
||||||
|
const {
|
||||||
|
options,
|
||||||
|
changeSelection,
|
||||||
|
changeMulti,
|
||||||
|
selected,
|
||||||
|
label,
|
||||||
|
creatable,
|
||||||
|
loading,
|
||||||
|
group,
|
||||||
|
disabled,
|
||||||
|
multiple,
|
||||||
|
variant,
|
||||||
|
style,
|
||||||
|
getOptionSelected,
|
||||||
|
onChange,
|
||||||
|
onKeyDown
|
||||||
|
} = props;
|
||||||
|
const classes = useStyles();
|
||||||
|
const filter = createFilterOptions<Option>({
|
||||||
|
stringify: (option: Option) => option.label + option.group
|
||||||
|
});
|
||||||
|
|
||||||
|
const stringToOption = (s: string): Option => {
|
||||||
|
return { value: s, label: s } as Option;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
id={label + "-autocomplete"}
|
||||||
|
value={selected === undefined ? null : selected}
|
||||||
|
multiple={multiple}
|
||||||
|
limitTags={2}
|
||||||
|
autoHighlight
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={(_, newValue) => {
|
||||||
|
if (multiple === true) {
|
||||||
|
if (changeMulti) {
|
||||||
|
if (Array.isArray(newValue)) {
|
||||||
|
changeMulti(newValue.map(v => (typeof v === "string" ? stringToOption(v) : v)));
|
||||||
|
} else {
|
||||||
|
changeMulti(
|
||||||
|
newValue
|
||||||
|
? [typeof newValue === "string" ? stringToOption(newValue) : newValue]
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("SearchSelect requires changeMulti to be defined when 'multiple' is true");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (changeSelection) {
|
||||||
|
if (Array.isArray(newValue)) {
|
||||||
|
console.warn("changeSelection requires a single element but received an array");
|
||||||
|
} else {
|
||||||
|
changeSelection(typeof newValue === "string" ? stringToOption(newValue) : newValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
"SearchSelect requires changeSelection to be defined when 'multiple' is false"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
options={options.sort((a, b) => {
|
||||||
|
let aGroup = a.group ? a.group : "";
|
||||||
|
let bGroup = b.group ? b.group : "";
|
||||||
|
if (bGroup > aGroup) return -1;
|
||||||
|
if (aGroup > bGroup) return 1;
|
||||||
|
return b.label > a.label ? -1 : 1;
|
||||||
|
})}
|
||||||
|
filterOptions={(options, params) => {
|
||||||
|
const filtered = filter(options as Option[], params);
|
||||||
|
|
||||||
|
if (creatable && params.inputValue !== "") {
|
||||||
|
filtered.push({
|
||||||
|
value: params.inputValue,
|
||||||
|
label: params.inputValue,
|
||||||
|
new: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}}
|
||||||
|
getOptionLabel={option => {
|
||||||
|
if (option instanceof Option) {
|
||||||
|
return option.text + " - " + option.value
|
||||||
|
} else {
|
||||||
|
return option as string
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
// getOptionSelected={
|
||||||
|
// getOptionSelected ? getOptionSelected : (o: { value: any; }, v: { value: any; }) => (o && v ? o.value === v.value : false)
|
||||||
|
// }
|
||||||
|
groupBy={group ? option => (option && option.group ? option.group : "") : undefined}
|
||||||
|
renderInput={params => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label={label}
|
||||||
|
variant={variant ? variant : "outlined"}
|
||||||
|
onChange={onChange}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
InputProps={{
|
||||||
|
...params.InputProps,
|
||||||
|
startAdornment:
|
||||||
|
!isArray(selected) && selected?.icon ? <Avatar src={selected?.icon} /> : null,
|
||||||
|
endAdornment: (
|
||||||
|
<React.Fragment>
|
||||||
|
{loading ? <CircularProgress color="inherit" size={20} /> : null}
|
||||||
|
{params.InputProps.endAdornment}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
// renderOption={params => (
|
||||||
|
// <div
|
||||||
|
// style={{
|
||||||
|
// display: "flex",
|
||||||
|
// flexDirection: "row"
|
||||||
|
// }}>
|
||||||
|
// <div style={{ marginTop: "auto", marginBottom: "auto" }}>
|
||||||
|
// {!isArray(params) && params?.icon ? <Avatar src={params?.icon} /> : ""}
|
||||||
|
// </div>
|
||||||
|
// <div style={{ marginTop: "auto", marginBottom: "auto", marginLeft: "1rem" }}>
|
||||||
|
// {params.label}
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
loading={loading}
|
||||||
|
selectOnFocus
|
||||||
|
clearOnBlur
|
||||||
|
freeSolo={creatable}
|
||||||
|
handleHomeEndKeys
|
||||||
|
fullWidth
|
||||||
|
classes={{
|
||||||
|
input: classes.input
|
||||||
|
}}
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import { Typography } from "@mui/material";
|
||||||
import { useAuth0 } from "@auth0/auth0-react";
|
import { useAuth0 } from "@auth0/auth0-react";
|
||||||
import Teams from "pages/Teams";
|
import Teams from "pages/Teams";
|
||||||
import SideNavigator from "./SideNavigator";
|
import SideNavigator from "./SideNavigator";
|
||||||
|
import Users from "pages/Users";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean,
|
open: boolean,
|
||||||
|
|
@ -52,6 +53,7 @@ export default function Router(props: Props) {
|
||||||
{/* Page routes */}
|
{/* Page routes */}
|
||||||
<Route index element={hello()} />
|
<Route index element={hello()} />
|
||||||
<Route path="/teams" element={<Teams/>} />
|
<Route path="/teams" element={<Teams/>} />
|
||||||
|
<Route path="/users" element={<Users/>} />
|
||||||
{/* <Route
|
{/* <Route
|
||||||
path="/teams/:teamID"
|
path="/teams/:teamID"
|
||||||
element={<Team />}
|
element={<Team />}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ChevronRight, People, PeopleOutline, PeopleOutlined, PeopleSharp } from "@mui/icons-material";
|
import { ChevronRight, People, PeopleOutline, PeopleOutlined, PeopleSharp, Person } from "@mui/icons-material";
|
||||||
import ChevronLeft from "@mui/icons-material/ChevronLeft";
|
import ChevronLeft from "@mui/icons-material/ChevronLeft";
|
||||||
import { darken, Divider, Grid2 as Grid, IconButton, lighten, List, ListItemButton, ListItemIcon, ListItemText, SwipeableDrawer, Theme, Toolbar, Tooltip, useTheme } from "@mui/material";
|
import { darken, Divider, Grid2 as Grid, IconButton, lighten, List, ListItemButton, ListItemIcon, ListItemText, SwipeableDrawer, Theme, Toolbar, Tooltip, useTheme } from "@mui/material";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
@ -132,6 +132,20 @@ export default function SideNavigator(props: Props) {
|
||||||
{open && <ListItemText primary="Teams" />}
|
{open && <ListItemText primary="Teams" />}
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<Tooltip title="Users" placement="right">
|
||||||
|
<ListItemButton
|
||||||
|
id="tour-dashboard"
|
||||||
|
component={Link}
|
||||||
|
to="/users"
|
||||||
|
onClick={onClose}
|
||||||
|
classes={getClasses("/user")}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Person />
|
||||||
|
</ListItemIcon>
|
||||||
|
{open && <ListItemText primary="Users" />}
|
||||||
|
</ListItemButton>
|
||||||
|
</Tooltip>
|
||||||
</List>
|
</List>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export interface IUserAPIContext {
|
||||||
// getUserSettings: (userID: string) => Promise<any>;
|
// getUserSettings: (userID: string) => Promise<any>;
|
||||||
// updateUserSettings: (userID: string, body: pond.IUserSettings) => Promise<any>;
|
// updateUserSettings: (userID: string, body: pond.IUserSettings) => Promise<any>;
|
||||||
// updateUserStatus: (userID: string, body: pond.IUserStatus) => Promise<any>;
|
// updateUserStatus: (userID: string, body: pond.IUserStatus) => Promise<any>;
|
||||||
// updateUser: (id: string, body: pond.User) => Promise<any>;
|
updateUser: (id: string, body: pond.User) => Promise<any>;
|
||||||
// getUserObjectHistory: (
|
// getUserObjectHistory: (
|
||||||
// id: string,
|
// id: string,
|
||||||
// objects: number[],
|
// objects: number[],
|
||||||
|
|
@ -208,9 +208,9 @@ export default function UserProvider(props: PropsWithChildren<any>) {
|
||||||
// return put(pondURL("/users/" + userID + "/status"), body);
|
// return put(pondURL("/users/" + userID + "/status"), body);
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// const updateUser = (id: string, body: pond.User) => {
|
const updateUser = (id: string, body: pond.User) => {
|
||||||
// return put(pondURL("/users/" + id), body);
|
return put(pondURL("/users/" + id), body);
|
||||||
// };
|
};
|
||||||
|
|
||||||
// const getUserObjectHistory = (id: string, objects: number[], start: string, end: string) => {
|
// const getUserObjectHistory = (id: string, objects: number[], start: string, end: string) => {
|
||||||
// return get<pond.GetUserObjectHistoriesResponse>(
|
// return get<pond.GetUserObjectHistoriesResponse>(
|
||||||
|
|
@ -242,7 +242,7 @@ export default function UserProvider(props: PropsWithChildren<any>) {
|
||||||
// getUserSettings,
|
// getUserSettings,
|
||||||
// updateUserSettings,
|
// updateUserSettings,
|
||||||
// updateUserStatus,
|
// updateUserStatus,
|
||||||
// updateUser,
|
updateUser,
|
||||||
// getUserObjectHistory
|
// getUserObjectHistory
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue