Savepoint
This commit is contained in:
parent
7db1a3456b
commit
02c483950c
45 changed files with 5136 additions and 256 deletions
|
@ -1,10 +1,7 @@
|
|||
import { useContext } from "react";
|
||||
|
||||
// Import Contexts
|
||||
import { JobProvider } from "./ctx/JobContext.jsx";
|
||||
import { ViewProvider } from "./ctx/ViewContext.jsx";
|
||||
import { StoreProvider } from "./ctx/StoreContext.jsx";
|
||||
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
// Import Views
|
||||
import Views from "./Views.jsx";
|
||||
|
||||
|
@ -13,9 +10,9 @@ export default function Dashboard() {
|
|||
<div className="qualiteer">
|
||||
<JobProvider>
|
||||
<StoreProvider>
|
||||
<ViewProvider>
|
||||
<BrowserRouter>
|
||||
<Views />
|
||||
</ViewProvider>
|
||||
</BrowserRouter>
|
||||
</StoreProvider>
|
||||
</JobProvider>
|
||||
</div>
|
||||
|
|
189
src/Views.jsx
189
src/Views.jsx
|
@ -1,21 +1,23 @@
|
|||
import { useContext, useState } from "react";
|
||||
import ViewContext from "./ctx/ViewContext.jsx";
|
||||
|
||||
import * as React from "react";
|
||||
import {
|
||||
Routes,
|
||||
Route,
|
||||
Link,
|
||||
BrowserRouter,
|
||||
Navigate,
|
||||
useLocation,
|
||||
} from "react-router-dom";
|
||||
import AppBar from "@mui/material/AppBar";
|
||||
import Badge, { BadgeProps } from "@mui/material/Badge";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import Box from "@mui/material/Box";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import Container from "@mui/material/Container";
|
||||
import Avatar from "@mui/material/Avatar";
|
||||
import Button from "@mui/material/Button";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Drawer from "@mui/material/Drawer";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import List from "@mui/material/List";
|
||||
|
@ -24,79 +26,124 @@ import NotificationsIcon from "@mui/icons-material/Notifications";
|
|||
import WorkIcon from "@mui/icons-material/Work";
|
||||
import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import ErrorIcon from "@mui/icons-material/Error";
|
||||
import WarningIcon from "@mui/icons-material/Warning";
|
||||
import InfoIcon from "@mui/icons-material/Info";
|
||||
// Import Pages
|
||||
import Failing from "./views/Failing.jsx";
|
||||
import Alerting from "./views/Alerting.jsx";
|
||||
import Jobs from "./views/Jobs.jsx";
|
||||
import Catalog from "./views/Catalog.jsx";
|
||||
import Settings from "./views/Settings.jsx";
|
||||
import About from "./views/About.jsx";
|
||||
|
||||
const pages = ["failing", "alerting", "jobs", "tests", "settings"];
|
||||
const pages = ["failing", "alerting", "jobs", "catalog", "settings", "about"];
|
||||
const icons = [
|
||||
ErrorIcon,
|
||||
NotificationsIcon,
|
||||
WorkIcon,
|
||||
FormatListBulletedIcon,
|
||||
SettingsIcon,
|
||||
<Badge badgeContent={4} color="error">
|
||||
<WarningIcon />
|
||||
</Badge>,
|
||||
<NotificationsIcon />,
|
||||
<Badge badgeContent={4} color="primary">
|
||||
<WorkIcon />
|
||||
</Badge>,
|
||||
<FormatListBulletedIcon />,
|
||||
<SettingsIcon />,
|
||||
<InfoIcon />,
|
||||
];
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
export default function Views() {
|
||||
const [view, setView] = useState(pages[0]);
|
||||
const location = useLocation();
|
||||
const [drawerOpen, setDrawer] = React.useState(false);
|
||||
|
||||
const toggleDrawer = () => setDrawer(!drawerOpen);
|
||||
const closeDrawer = () => setDrawer(false);
|
||||
const openPage = (e) => setView(e.target.outerText.toLowerCase());
|
||||
|
||||
const reloadPage = () => window.location.reload(false);
|
||||
|
||||
const SideBadge = styled(Badge)(({ theme }) => ({
|
||||
"& .MuiBadge-badge": {
|
||||
right: -6,
|
||||
top: 10,
|
||||
padding: "0 4px",
|
||||
},
|
||||
}));
|
||||
|
||||
const navHeader = () => {
|
||||
const pathStr =
|
||||
location.pathname.charAt(1).toUpperCase() + location.pathname.slice(2);
|
||||
if (location.pathname !== "/failing") return pathStr;
|
||||
return (
|
||||
<SideBadge badgeContent={4} color="error" overlap="circular">
|
||||
{pathStr}
|
||||
</SideBadge>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AppBar position="static" color="secondary">
|
||||
<Container maxWidth="xl">
|
||||
<Toolbar disableGutters>
|
||||
<Drawer open={drawerOpen} onClose={closeDrawer}>
|
||||
{" "}
|
||||
<Box sx={{ width: 250 }} role="presentation">
|
||||
<List>
|
||||
{pages.map((text, index) => (
|
||||
<ListItemButton
|
||||
key={text}
|
||||
onClick={openPage}
|
||||
selected={view === text}
|
||||
>
|
||||
<ListItemIcon>{/*icons[index]*/}</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={text.charAt(0).toUpperCase() + text.slice(1)}
|
||||
/>
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Drawer>
|
||||
<IconButton
|
||||
size="large"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="menu"
|
||||
sx={{ mr: 2 }}
|
||||
onClick={toggleDrawer}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Typography
|
||||
variant="h6"
|
||||
noWrap
|
||||
component="div"
|
||||
sx={{ mr: 2, display: { xs: "none", md: "flex" } }}
|
||||
>
|
||||
{view.charAt(0).toUpperCase() + view.slice(1)}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
noWrap
|
||||
component="div"
|
||||
sx={{ flexGrow: 1, display: { xs: "flex", md: "none" } }}
|
||||
>
|
||||
{view.charAt(0).toUpperCase() + view.slice(1)}
|
||||
</Typography>
|
||||
<Box sx={{ flexGrow: 0 }}>
|
||||
<Avatar alt="Remy Sharp" src="/assets/QA.jpg" />
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
<div className="view">
|
||||
<AppBar
|
||||
position="fixed"
|
||||
sx={{ bgcolor: "black", zIndex: (theme) => theme.zIndex.drawer + 1 }}
|
||||
>
|
||||
<Box sx={{ flexGrow: 1, margin: "0 20px" }}>
|
||||
<Toolbar disableGutters>
|
||||
<IconButton
|
||||
size="large"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="menu"
|
||||
sx={{ mr: 2 }}
|
||||
onClick={toggleDrawer}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Drawer open={drawerOpen} onClose={closeDrawer}>
|
||||
<Toolbar />
|
||||
<Box sx={{ width: 250, overflow: "auto" }} role="presentation">
|
||||
<List>
|
||||
{pages.map((text, index) => (
|
||||
<ListItemButton
|
||||
key={text}
|
||||
component={Link}
|
||||
to={"/" + text}
|
||||
selected={location.pathname === "/" + text}
|
||||
onClick={closeDrawer}
|
||||
>
|
||||
<ListItemIcon>{icons[index]}</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={text.charAt(0).toUpperCase() + text.slice(1)}
|
||||
/>
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Drawer>
|
||||
<Typography
|
||||
variant="h6"
|
||||
noWrap
|
||||
component="div"
|
||||
sx={{ flexGrow: 1 }}
|
||||
>
|
||||
{navHeader()}
|
||||
</Typography>
|
||||
<Avatar alt="Remy Sharp" src="/assets/QA.png" onClick={reloadPage}/>
|
||||
</Toolbar>
|
||||
</Box>
|
||||
</AppBar>
|
||||
|
||||
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
||||
<Toolbar />
|
||||
<Routes>
|
||||
<Route exact path="/" element={<Navigate to="/failing" replace />} />
|
||||
<Route path="/failing" element={<Failing />} />
|
||||
<Route path="/alerting" element={<Alerting />} />
|
||||
<Route path="/jobs" element={<Jobs />} />
|
||||
<Route path="/catalog" element={<Catalog />} />
|
||||
<Route path="/settings" element={<Settings pages={pages} />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
</Routes>
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -36,16 +36,32 @@ const reducer = (state, action) => {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const JobProvider = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
|
||||
const jobUpdate = (job, jobId) => dispatch({ type: ACTIONS.UPDATE, jobId, job });
|
||||
const jobCreate = (job) =>
|
||||
dispatch({ type: ACTIONS.CREATE, job: { ...job, log: [] } });
|
||||
const jobDelete = (jobId) => dispatch({ type: ACTIONS.DELETE, jobId });
|
||||
|
||||
function retryAll(failing){
|
||||
// Query Full Locator
|
||||
console.log("Would retry all failing tests!");
|
||||
}
|
||||
|
||||
function jobBuilder(){
|
||||
|
||||
}
|
||||
|
||||
const context = {
|
||||
state,
|
||||
dispatch,
|
||||
jobUpdate: (job, jobId) => dispatch({ type: ACTIONS.UPDATE, jobId, job }),
|
||||
jobCreate: (job) =>
|
||||
dispatch({ type: ACTIONS.CREATE, job: { ...job, log: [] } }),
|
||||
jobDelete: (jobId) => dispatch({ type: ACTIONS.DELETE, jobId }),
|
||||
jobUpdate,
|
||||
jobCreate,
|
||||
jobDelete,
|
||||
retryAll
|
||||
};
|
||||
const contextValue = useMemo(() => context, [state, dispatch]);
|
||||
|
||||
|
|
|
@ -5,9 +5,18 @@ const ACTIONS = {
|
|||
UPDATE: "u",
|
||||
};
|
||||
|
||||
const initialState = {};
|
||||
const initialState = {
|
||||
intervals: [],
|
||||
failing: [],
|
||||
regions: [],
|
||||
focusJob: false,
|
||||
simplifiedControls: false,
|
||||
defaultRegion: "us", // Local Store
|
||||
defaultPage: "failing", // Local Store
|
||||
};
|
||||
|
||||
const reducer = (state, action) => {
|
||||
const { store } = action;
|
||||
// Actions
|
||||
switch (action.type) {
|
||||
case ACTIONS.UPDATE:
|
||||
|
@ -23,7 +32,7 @@ export const StoreProvider = ({ children }) => {
|
|||
const context = {
|
||||
state,
|
||||
dispatch,
|
||||
updateStore: (store) => dispatch(state, { type: ACTIONS.UPDATE, store }),
|
||||
updateStore: (store) => dispatch({ type: ACTIONS.UPDATE, store }),
|
||||
};
|
||||
const contextValue = useMemo(() => context, [state, dispatch]);
|
||||
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import React, { useReducer, createContext, useMemo } from "react";
|
||||
const ViewContext = createContext();
|
||||
|
||||
const ACTIONS = {
|
||||
UPDATE: "u",
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
activePage: "Home",
|
||||
};
|
||||
|
||||
const reducer = (state, action) => {
|
||||
// Actions
|
||||
switch (action.type) {
|
||||
case ACTIONS.UPDATE:
|
||||
return { ...state };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const ViewProvider = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
|
||||
const contextValue = useMemo(() => ({ state, dispatch }), [state, dispatch]);
|
||||
|
||||
return (
|
||||
<ViewContext.Provider value={contextValue}>{children}</ViewContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewContext;
|
32
src/views/About.jsx
Normal file
32
src/views/About.jsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import Typography from "@mui/material/Typography";
|
||||
import Link from "@mui/material/Link";
|
||||
import Container from "@mui/material/Container";
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
const memeUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
|
||||
const repoUrl = "https://gitlab.com/dunemask/Qualiteer";
|
||||
|
||||
export default function About() {
|
||||
return (
|
||||
<div className="about">
|
||||
<Container maxWidth="sm">
|
||||
<Typography variant="h6" gutterBottom component="div">
|
||||
<Box fontWeight='bold' display='inline'>Why?</Box>
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
Qualiteer was designed to solve the issue of "on call". A state of being in which QA tests will fail, stiring everyone into a frenzy of what is broken in production! 🤯
|
||||
Qualiteer gives users power to resolve and reattempt failing tests, run a particular suite of tests, and mute pesky alerts reminding you the navbar's color changed... 🤦♂️
|
||||
</Typography>
|
||||
<br/>
|
||||
<Typography variant="subtitle1" style={{ wordWrap: "break-word", whiteSpace:"normal" }}>
|
||||
<Box fontWeight='bold' display='inline'>{"Repository: "} </Box>
|
||||
<Link href={repoUrl} >{repoUrl}</Link>
|
||||
</Typography>
|
||||
<br/>
|
||||
<div style={{justifyContent:"center", width:"100%", display:"flex"}}>
|
||||
<Link href={memeUrl} variant="h6" underline="none">Qualiteer</Link>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
61
src/views/Alerting.jsx
Normal file
61
src/views/Alerting.jsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
import { useState, useContext } from "react";
|
||||
import StoreContext from "../ctx/StoreContext.jsx";
|
||||
|
||||
import SpeedDial from '@mui/material/SpeedDial';
|
||||
import SpeedDialAction from '@mui/material/SpeedDialAction';
|
||||
import SpeedDialIcon from '@mui/material/SpeedDialIcon';
|
||||
|
||||
import Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
|
||||
export default function Alerting() {
|
||||
const { state: store, updateStore } = useContext(StoreContext);
|
||||
|
||||
const [alertDialogOpen, setAlertDialogOpen] = useState(false);
|
||||
const quickAlertClick = () => setAlertDialogOpen(!alertDialogOpen);
|
||||
|
||||
function silenceAlert(){
|
||||
|
||||
}
|
||||
const handleClose = (confirmed) => () => {
|
||||
quickAlertClick();
|
||||
if(!confirmed) return;
|
||||
silenceAlert();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="alerting">
|
||||
<Dialog
|
||||
open={alertDialogOpen}
|
||||
onClose={handleClose()}
|
||||
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
|
||||
maxWidth="xs"
|
||||
>
|
||||
<DialogTitle>
|
||||
Silence Alert
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose()}>Cancel</Button>
|
||||
<Button onClick={handleClose(true)} autoFocus>
|
||||
Silence
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<SpeedDial
|
||||
ariaLabel="Silence Alert"
|
||||
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
||||
icon={<SpeedDialIcon />}
|
||||
onClick={quickAlertClick}
|
||||
open={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
29
src/views/Catalog.jsx
Normal file
29
src/views/Catalog.jsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { useContext } from "react";
|
||||
import StoreContext from "../ctx/StoreContext.jsx";
|
||||
import JobContext from "../ctx/JobContext.jsx";
|
||||
|
||||
import TextField from "@mui/material/TextField";
|
||||
|
||||
import CatalogSearch from "./components/CatalogSearch.jsx";
|
||||
|
||||
export default function Catalog() {
|
||||
const {
|
||||
state: jobState,
|
||||
dispatch: jobDispatch,
|
||||
jobUpdate,
|
||||
jobCreate,
|
||||
} = useContext(JobContext);
|
||||
|
||||
const { state: store, updateStore } = useContext(StoreContext);
|
||||
|
||||
return (
|
||||
<div className="catalog">
|
||||
<CatalogSearch />
|
||||
<TextField
|
||||
label="Search Catalog"
|
||||
type="search"
|
||||
variant="filled"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
70
src/views/Failing.jsx
Normal file
70
src/views/Failing.jsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { useState, useContext } from "react";
|
||||
import StoreContext from "../ctx/StoreContext.jsx";
|
||||
import JobContext from "../ctx/JobContext.jsx";
|
||||
|
||||
import SpeedDial from '@mui/material/SpeedDial';
|
||||
import SpeedDialAction from '@mui/material/SpeedDialAction';
|
||||
import SpeedDialIcon from '@mui/material/SpeedDialIcon';
|
||||
|
||||
import Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
|
||||
import ReplayIcon from '@mui/icons-material/Replay';
|
||||
|
||||
export default function Failing() {
|
||||
const {
|
||||
state: jobState,
|
||||
retryAll
|
||||
} = useContext(JobContext);
|
||||
|
||||
const { state: store, updateStore } = useContext(StoreContext);
|
||||
|
||||
const [retryAllOpen, setRetryAllOpen] = useState(false);
|
||||
const retryAllClick = () => setRetryAllOpen(!retryAllOpen);
|
||||
const handleClose = (confirmed) => ()=> {
|
||||
retryAllClick();
|
||||
if(!confirmed) return;
|
||||
retryAll(store.failing);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="failing">
|
||||
|
||||
|
||||
<Dialog
|
||||
open={retryAllOpen}
|
||||
onClose={handleClose()}
|
||||
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
|
||||
maxWidth="xs"
|
||||
>
|
||||
<DialogTitle>
|
||||
Retry all failing tests?
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
This will create x jobs and run y tests
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose()}>Cancel</Button>
|
||||
<Button onClick={handleClose(true)} autoFocus>
|
||||
Yes
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<SpeedDial
|
||||
ariaLabel="Retry All"
|
||||
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
||||
icon={<ReplayIcon />}
|
||||
onClick={retryAllClick}
|
||||
open={false}
|
||||
/>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
55
src/views/Jobs.jsx
Normal file
55
src/views/Jobs.jsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { useState, useContext } from "react";
|
||||
import StoreContext from "../ctx/StoreContext.jsx";
|
||||
import JobContext from "../ctx/JobContext.jsx";
|
||||
|
||||
|
||||
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
||||
import SpeedDial from '@mui/material/SpeedDial';
|
||||
import SpeedDialAction from '@mui/material/SpeedDialAction';
|
||||
import SpeedDialIcon from '@mui/material/SpeedDialIcon';
|
||||
|
||||
import PageviewIcon from '@mui/icons-material/Pageview';
|
||||
import ViewColumnIcon from '@mui/icons-material/ViewColumn';
|
||||
import ViewCarouselIcon from '@mui/icons-material/ViewCarousel';
|
||||
|
||||
export default function Jobs() {
|
||||
const {
|
||||
state: jobState,
|
||||
dispatch: jobDispatch,
|
||||
jobUpdate,
|
||||
jobCreate,
|
||||
} = useContext(JobContext);
|
||||
|
||||
const { state: store, updateStore } = useContext(StoreContext);
|
||||
|
||||
const [quickOpen, setQuickOpen] = useState(false);
|
||||
|
||||
const quickOpenClick = () => setQuickOpen(!quickOpen);
|
||||
const quickOpenClose = () => setQuickOpen(false);
|
||||
|
||||
const actions = [
|
||||
{name: "Suite", icon: <ViewCarouselIcon/>}, {name: "Compound", icon: <ViewColumnIcon/>}, {name: "Manual", icon: <PageviewIcon/>}
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="jobs">
|
||||
<ClickAwayListener onClickAway={quickOpenClose}>
|
||||
<SpeedDial
|
||||
ariaLabel="New Job"
|
||||
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
||||
icon={<SpeedDialIcon />}
|
||||
onClick={quickOpenClick}
|
||||
open={quickOpen}
|
||||
>
|
||||
{actions.map((action) => (
|
||||
<SpeedDialAction
|
||||
key={action.name}
|
||||
icon={action.icon}
|
||||
tooltipTitle={action.name}
|
||||
/>
|
||||
))}
|
||||
</SpeedDial>
|
||||
</ClickAwayListener>
|
||||
</div>
|
||||
);
|
||||
}
|
124
src/views/Settings.jsx
Normal file
124
src/views/Settings.jsx
Normal file
|
@ -0,0 +1,124 @@
|
|||
import { useContext, useState, useEffect } from "react";
|
||||
import StoreContext from "../ctx/StoreContext.jsx";
|
||||
|
||||
import MultiOptionDialog from "./components/MultiOptionDialog.jsx";
|
||||
|
||||
import * as React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Switch from "@mui/material/Switch";
|
||||
import SummarizeIcon from '@mui/icons-material/Summarize';
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
|
||||
export default function Settings(props) {
|
||||
|
||||
const { state: store, updateStore } = useContext(StoreContext);
|
||||
const { regions } = store;
|
||||
const { pages } = props;
|
||||
|
||||
const defaultDialog = {title: "", options: [], current: null, onSelect: null, open: false};
|
||||
const [dialog, setDialog] = React.useState(defaultDialog);
|
||||
|
||||
const optionSettings = {region: {
|
||||
title: "Region",
|
||||
options: ["us", "au"],
|
||||
current: store.defaultRegion,
|
||||
onSelect: (r) => updateStore({defaultRegion: r})
|
||||
},
|
||||
defaultPage: {
|
||||
title: "Default Page",
|
||||
options: ["failing", "alerting"],
|
||||
current: store.defaultPage,
|
||||
onSelect: (p) => updateStore({defaultPage: p})
|
||||
}}
|
||||
|
||||
const handleOptionsMenu = (s) => {
|
||||
setDialog({...s, open:true});
|
||||
};
|
||||
|
||||
const handleClose = (newValue, onSelect) => {
|
||||
setDialog({...dialog, open:false})
|
||||
if (!newValue) return;
|
||||
onSelect(newValue);
|
||||
};
|
||||
|
||||
const handleToggle = (booleanSetting) => ()=> {
|
||||
const storeUpdate = {};
|
||||
storeUpdate[booleanSetting] = !store[booleanSetting];
|
||||
updateStore(storeUpdate)
|
||||
}
|
||||
|
||||
function MultiOptionSubtext(props){
|
||||
return( <React.Fragment>
|
||||
<Typography
|
||||
sx={{ display: 'inline' }}
|
||||
component="span"
|
||||
variant="body2"
|
||||
color="primary"
|
||||
>
|
||||
{props.value}
|
||||
</Typography>
|
||||
</React.Fragment>)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
|
||||
<List component="div" role="group">
|
||||
|
||||
<ListItem
|
||||
button
|
||||
divider
|
||||
aria-haspopup="true"
|
||||
aria-label="default page"
|
||||
onClick={() => handleOptionsMenu(optionSettings.defaultPage)}
|
||||
>
|
||||
<ListItemText primary="Default Page" secondary={
|
||||
<MultiOptionSubtext value={optionSettings.defaultPage.current} />
|
||||
}/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem
|
||||
button
|
||||
divider
|
||||
aria-haspopup="true"
|
||||
aria-label="region"
|
||||
onClick={() => handleOptionsMenu(optionSettings.region)}
|
||||
>
|
||||
<ListItemText primary="Region" secondary={<MultiOptionSubtext value={optionSettings.region.current} />} />
|
||||
</ListItem>
|
||||
|
||||
<ListItem button divider>
|
||||
<ListItemText primary="Simplified Controls" />
|
||||
<Switch
|
||||
edge="end"
|
||||
onChange={handleToggle("simplifiedControls")}
|
||||
checked={store.simplifiedControls}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem button divider>
|
||||
<ListItemText primary="Focus New Jobs" />
|
||||
<Switch
|
||||
edge="end"
|
||||
onChange={handleToggle("focusJob")}
|
||||
checked={store.focusJob}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<MultiOptionDialog
|
||||
id="multi-options-menu"
|
||||
keepMounted
|
||||
open={dialog.open}
|
||||
onClose={handleClose}
|
||||
dialog={dialog}
|
||||
/>
|
||||
</List>
|
||||
</Box>
|
||||
);
|
||||
}
|
31
src/views/components/CatalogSearch.jsx
Normal file
31
src/views/components/CatalogSearch.jsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import * as React from 'react';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import InputBase from '@mui/material/InputBase';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import DirectionsIcon from '@mui/icons-material/Directions';
|
||||
import ClearOutlinedIcon from "@mui/icons-material/ClearOutlined";
|
||||
|
||||
export default function SearchBar(props) {
|
||||
return (
|
||||
<Paper
|
||||
component="form"
|
||||
sx={{ display: 'flex', alignItems: 'center'}}
|
||||
>
|
||||
<InputBase
|
||||
sx={{flex: 1 }}
|
||||
placeholder="Search Catalog"
|
||||
inputProps={{ 'aria-label': `search catalog` }}
|
||||
/>
|
||||
<IconButton type="submit" sx={{ p: '18px' }} aria-label="search">
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
<Divider sx={{ height: 28, m: 0.5 }} orientation="vertical" />
|
||||
<IconButton sx={{ p: '8px' }} aria-label="clear">
|
||||
<ClearOutlinedIcon />
|
||||
</IconButton>
|
||||
</Paper>
|
||||
);
|
||||
}
|
73
src/views/components/MultiOptionDialog.jsx
Normal file
73
src/views/components/MultiOptionDialog.jsx
Normal file
|
@ -0,0 +1,73 @@
|
|||
import {useState, useRef, useEffect} from "react";
|
||||
|
||||
import Button from "@mui/material/Button"
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import RadioGroup from '@mui/material/RadioGroup';
|
||||
import Radio from '@mui/material/Radio';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
|
||||
|
||||
export default function MultiOptionDialog(props) {
|
||||
|
||||
const { dialog: dialogProp, onClose, open, ...other } = props;
|
||||
const [value, setValue] = useState(dialogProp.current);
|
||||
const [dialog, setDialog] = useState(dialogProp);
|
||||
|
||||
const radioGroupRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
setDialog(dialogProp);
|
||||
setValue(dialogProp.current);
|
||||
}, [dialogProp, open]);
|
||||
|
||||
const handleEntering = () => {
|
||||
if (radioGroupRef.current != null) radioGroupRef.current.focus();
|
||||
};
|
||||
|
||||
const handleCancel = () => onClose();
|
||||
|
||||
const handleOk = () => onClose(value, dialog.onSelect);
|
||||
|
||||
|
||||
const handleChange = (e) =>{ setValue(e.target.value);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
|
||||
maxWidth="xs"
|
||||
TransitionProps={{ onEntering: handleEntering }}
|
||||
open={open}
|
||||
{...other}
|
||||
>
|
||||
<DialogTitle>{dialog.title}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<RadioGroup
|
||||
ref={radioGroupRef}
|
||||
aria-label={dialogProp.title}
|
||||
name={dialog.title}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{dialog.options.map((option) => (
|
||||
<FormControlLabel
|
||||
value={option}
|
||||
key={option}
|
||||
control={<Radio />}
|
||||
label={option}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button autoFocus onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleOk}>Ok</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue