diff options
author | Herbert Eiselt <herbert.eiselt@highstreet-technologies.com> | 2019-02-28 15:23:42 +0100 |
---|---|---|
committer | Herbert Eiselt <herbert.eiselt@highstreet-technologies.com> | 2019-02-28 15:24:28 +0100 |
commit | 7446f23b3abc30d7c53f2eaa951742371c071171 (patch) | |
tree | b76a8d2e64c7aa850c09f8e69f01e7a262ab5cd5 /sdnr/wt/odlux/apps/maintenanceApp/src | |
parent | 49b155ec687cdf58fb51fe8245a2f5f4582b68f0 (diff) |
UX extensions
UX Maintenance client and further changes
Change-Id: I7643661d17db5fc3d3f94b58cb42ed0be558c64f
Issue-ID: SDNC-583
Signed-off-by: Herbert Eiselt <herbert.eiselt@highstreet-technologies.com>
Diffstat (limited to 'sdnr/wt/odlux/apps/maintenanceApp/src')
10 files changed, 724 insertions, 0 deletions
diff --git a/sdnr/wt/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts b/sdnr/wt/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts new file mode 100644 index 000000000..753c1183f --- /dev/null +++ b/sdnr/wt/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts @@ -0,0 +1,95 @@ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; +import { MaintenenceEntry, spoofSymbol } from '../models/maintenenceEntryType'; + +import { AddSnackbarNotification } from '../../../../framework/src/actions/snackbarActions'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { maintenenceService } from '../services/maintenenceService'; + +export class BaseAction extends Action { } + +export class LoadAllMainteneceEntriesAction extends BaseAction { } + +export class AllMainteneceEntriesLoadedAction extends BaseAction { + + constructor (public maintenenceEntries: MaintenenceEntry[] | null, error?:string) { + super(); + + } +} + + +export const loadAllMainteneceEntriesAsyncAction = (dispatch: Dispatch , getState: () => IApplicationStoreState) => { + dispatch(new LoadAllMainteneceEntriesAction()); + // frist get all entries from the maintenance index + maintenenceService.getAllMaintenenceEntries().then(entries => { + if (!entries) { + dispatch(new AddSnackbarNotification({ message: `Error reading maintenece sessings.`, options: { variant: 'error' } })); + return; + } + + // combine ell entries from the maintenance index with all networkelements + const networkElements = getState().connectApp.mountedNetworkElements; + const maintenenceEntries = networkElements.elements.reduce((acc, cur) => { + const entry = entries.find(e => e.mountId === cur.mountId); + acc.push(entry || { + [spoofSymbol]: true, + _id: cur.mountId, + mountId: cur.mountId, + description : "", + start: "", + end: "", + active: false + }); + return acc; + }, [] as MaintenenceEntry[]); + + // all all entries from the maintenance index which are not in all networkelements + entries.forEach(entry => { + if (maintenenceEntries.findIndex(e => e.mountId === entry.mountId) < 0) { + maintenenceEntries.push(entry); + }; + }); + + dispatch(new AllMainteneceEntriesLoadedAction(maintenenceEntries)); + }).catch(error => dispatch(new AllMainteneceEntriesLoadedAction(null, error))); +} + +export class UpdateMaintenanceEntry extends BaseAction { + constructor(public maintenenceEntry: MaintenenceEntry) { + super(); + } +} + +/** Represents an async thunk action creator to add an element to the maintenence entries. */ +export const addOrUpdateMaintenenceEntryAsyncActionCreator = (entry: MaintenenceEntry) => (dispatch: Dispatch) => { + maintenenceService.writeMaintenenceEntry(entry).then(result => { + result && window.setTimeout(() => { + // dispatch(loadAllMountedNetworkElementsAsync); + dispatch(new UpdateMaintenanceEntry(entry)); + dispatch(new AddSnackbarNotification({ message: `Successfully ${result && result.created ? "created" : "updated"} maintenece sessings for [${entry.mountId}]`, options: { variant: 'success' } })); + }, 900); + }); +}; + +/** Represents an async thunk action creator to delete an element from the maintenence entries. */ +export const removeFromMaintenenceEntrysAsyncActionCreator = (entry: MaintenenceEntry) => (dispatch: Dispatch) => { + maintenenceService.deleteMaintenenceEntry(entry).then(result => { + result && window.setTimeout(() => { + //dispatch(loadAllMountedNetworkElementsAsync); + dispatch(new UpdateMaintenanceEntry({ + [spoofSymbol]: true, + _id: entry._id, + mountId: entry.mountId, + description : "", + start: "", + end: "", + active: false + })); + dispatch(new AddSnackbarNotification({ message: `Successfully removed [${entry.mountId}]`, options: { variant: 'success' } })); + }, 900); + }); +}; + +// Hint: since there is no notification of changed required network elements, this code is not aware of changes caused outiside of this browser.
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx b/sdnr/wt/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx new file mode 100644 index 000000000..04f6d2d41 --- /dev/null +++ b/sdnr/wt/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx @@ -0,0 +1,177 @@ +import * as React from 'react'; + +import Button from '@material-ui/core/Button'; +import TextField from '@material-ui/core/TextField'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; + +import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect'; + +import { + addOrUpdateMaintenenceEntryAsyncActionCreator, + removeFromMaintenenceEntrysAsyncActionCreator +} from '../actions/maintenenceActions'; + +import { MaintenenceEntry } from '../models/maintenenceEntryType'; +import { FormControl, InputLabel, Select, MenuItem } from '@material-ui/core'; + +export enum EditMaintenenceEntryDialogMode { + None = "none", + AddMaintenenceEntry = "addMaintenenceEntry", + EditMaintenenceEntry = "editMaintenenceEntry", + RemoveMaintenenceEntry = "removeMaintenenceEntry" +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + addOrUpdateMaintenenceEntry: (entry: MaintenenceEntry) => { + dispatcher.dispatch(addOrUpdateMaintenenceEntryAsyncActionCreator(entry)); + }, + removeMaintenenceEntry: (entry: MaintenenceEntry) => { + dispatcher.dispatch(removeFromMaintenenceEntrysAsyncActionCreator(entry)); + }, +}); + +type DialogSettings = { + dialogTitle: string, + dialogDescription: string, + applyButtonText: string, + cancelButtonText: string, + enableMountIdEditor: boolean, + enableTimeEditor: boolean, +} + +const settings: { [key: string]: DialogSettings } = { + [EditMaintenenceEntryDialogMode.None]: { + dialogTitle: "", + dialogDescription: "", + applyButtonText: "", + cancelButtonText: "", + enableMountIdEditor: false, + enableTimeEditor: false, + }, + [EditMaintenenceEntryDialogMode.AddMaintenenceEntry]: { + dialogTitle: "Add new maintenence entry", + dialogDescription: "", + applyButtonText: "Add", + cancelButtonText: "Cancel", + enableMountIdEditor: true, + enableTimeEditor: true, + }, + [EditMaintenenceEntryDialogMode.EditMaintenenceEntry]: { + dialogTitle: "Edit maintenence entry", + dialogDescription: "", + applyButtonText: "Save", + cancelButtonText: "Cancel", + enableMountIdEditor: false, + enableTimeEditor: true, + }, + [EditMaintenenceEntryDialogMode.RemoveMaintenenceEntry]: { + dialogTitle: "Remove maintenence entry", + dialogDescription: "", + applyButtonText: "Remove", + cancelButtonText: "Cancel", + enableMountIdEditor: false, + enableTimeEditor: false, + }, +} + +type EditMaintenenceEntryDIalogComponentProps = Connect<undefined, typeof mapDispatch> & { + mode: EditMaintenenceEntryDialogMode; + initialMaintenenceEntry: MaintenenceEntry; + onClose: () => void; +}; + +type EditMaintenenceEntryDIalogComponentState = MaintenenceEntry; + +class EditMaintenenceEntryDIalogComponent extends React.Component<EditMaintenenceEntryDIalogComponentProps, EditMaintenenceEntryDIalogComponentState> { + constructor (props: EditMaintenenceEntryDIalogComponentProps) { + super(props); + + this.state = { + ...this.props.initialMaintenenceEntry + }; + } + + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + <Dialog open={this.props.mode !== EditMaintenenceEntryDialogMode.None}> + <DialogTitle id="form-dialog-title">{setting.dialogTitle}</DialogTitle> + <DialogContent> + <DialogContentText> + {setting.dialogDescription} + </DialogContentText> + <TextField disabled={!setting.enableMountIdEditor} spellCheck={false} autoFocus margin="dense" id="name" label="Name" type="text" fullWidth value={this.state.mountId} onChange={(event) => { this.setState({ mountId: event.target.value }); }} /> + <TextField disabled={!setting.enableTimeEditor} spellCheck={false} autoFocus margin="dense" id="start" label="Start" type="datetime-local" fullWidth value={this.state.start} onChange={(event) => { this.setState({ start: event.target.value }); }} /> + <TextField disabled={!setting.enableTimeEditor} spellCheck={false} autoFocus margin="dense" id="end" label="End" type="datetime-local" fullWidth value={this.state.end} onChange={(event) => { this.setState({ end: event.target.value }); }} /> + <FormControl fullWidth disabled={!setting.enableTimeEditor}> + <InputLabel htmlFor="active">Active</InputLabel> + <Select value={ this.state.active || false } onChange={(event) => { + this.setState({ active: event.target.value as any as boolean }); + }} inputProps={{ name: 'active', id: 'active' }} fullWidth > + <MenuItem value={true as any as string}>active</MenuItem> + <MenuItem value={false as any as string}>not active</MenuItem> + </Select> + </FormControl> + </DialogContent> + <DialogActions> + <Button onClick={(event) => { + this.onApply({ + _id: this.state._id || this.state.mountId, + mountId: this.state.mountId, + description: this.state.description, + start: this.state.start, + end: this.state.end, + active: this.state.active + }); + event.preventDefault(); + event.stopPropagation(); + }} > {setting.applyButtonText} </Button> + <Button onClick={(event) => { + this.onCancel(); + event.preventDefault(); + event.stopPropagation(); + }} color="secondary"> {setting.cancelButtonText} </Button> + </DialogActions> + </Dialog> + ) + } + + private onApply = (entry: MaintenenceEntry) => { + this.props.onClose && this.props.onClose(); + switch (this.props.mode) { + case EditMaintenenceEntryDialogMode.AddMaintenenceEntry: + entry && this.props.addOrUpdateMaintenenceEntry(entry); + break; + case EditMaintenenceEntryDialogMode.EditMaintenenceEntry: + entry && this.props.addOrUpdateMaintenenceEntry(entry); + break; + case EditMaintenenceEntryDialogMode.RemoveMaintenenceEntry: + entry && this.props.removeMaintenenceEntry(entry); + break; + } + }; + + + private onCancel = () => { + this.props.onClose && this.props.onClose(); + } + + static getDerivedStateFromProps(props: EditMaintenenceEntryDIalogComponentProps, state: EditMaintenenceEntryDIalogComponentState & { _initialMaintenenceEntry: MaintenenceEntry }): EditMaintenenceEntryDIalogComponentState & { _initialMaintenenceEntry: MaintenenceEntry } { + if (props.initialMaintenenceEntry !== state._initialMaintenenceEntry) { + state = { + ...state, + ...props.initialMaintenenceEntry, + _initialMaintenenceEntry: props.initialMaintenenceEntry, + }; + } + return state; + } + +} + +export const EditMaintenenceEntryDIalog = connect(undefined, mapDispatch)(EditMaintenenceEntryDIalogComponent); +export default EditMaintenenceEntryDIalog;
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts b/sdnr/wt/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts new file mode 100644 index 000000000..956d05843 --- /dev/null +++ b/sdnr/wt/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts @@ -0,0 +1,27 @@ +// main state handler + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { IConnectAppStoreState } from '../../../connectApp/src/handlers/connectAppRootHandler'; + +import { IMaintenenceEntriesState, maintenenceEntriesActionHandler } from './maintenenceEntriesHandler'; + +export interface IMaintenanceAppStoreState { + maintenenceEntries : IMaintenenceEntriesState +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + maintenanceApp: IMaintenanceAppStoreState, + connectApp: IConnectAppStoreState + } +} + +const actionHandlers = { + maintenenceEntries: maintenenceEntriesActionHandler +}; + +export const maintenanceAppRootHandler = combineActionHandler<IMaintenanceAppStoreState>(actionHandlers); +export default maintenanceAppRootHandler; diff --git a/sdnr/wt/odlux/apps/maintenanceApp/src/handlers/maintenenceEntriesHandler.ts b/sdnr/wt/odlux/apps/maintenanceApp/src/handlers/maintenenceEntriesHandler.ts new file mode 100644 index 000000000..13f4a3b6d --- /dev/null +++ b/sdnr/wt/odlux/apps/maintenanceApp/src/handlers/maintenenceEntriesHandler.ts @@ -0,0 +1,40 @@ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { MaintenenceEntry } from '../models/maintenenceEntryType'; +import { LoadAllMainteneceEntriesAction, AllMainteneceEntriesLoadedAction, UpdateMaintenanceEntry } from '../actions/maintenenceActions'; + +export interface IMaintenenceEntriesState { + entries: MaintenenceEntry[]; + busy: boolean; +} + +const maintenanceEntriesStateInit: IMaintenenceEntriesState = { + entries: [], + busy: false +}; + +export const maintenenceEntriesActionHandler: IActionHandler<IMaintenenceEntriesState> = (state = maintenanceEntriesStateInit, action) => { + if (action instanceof LoadAllMainteneceEntriesAction){ + state = { + ...state, + busy: true, + } + } else if (action instanceof AllMainteneceEntriesLoadedAction){ + state = { + ...state, + entries: action.maintenenceEntries || [], + busy: false, + } + } else if (action instanceof UpdateMaintenanceEntry) { + const index = state.entries.findIndex(e => action.maintenenceEntry.mountId === e.mountId); + state = { + ...state, + entries: index > -1 ? [ + ...state.entries.slice(0, index), + action.maintenenceEntry, + ...state.entries.slice(index+1), + ] : [...state.entries, action.maintenenceEntry] + } + } + return state; +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/maintenanceApp/src/index.html b/sdnr/wt/odlux/apps/maintenanceApp/src/index.html new file mode 100644 index 000000000..c84aecee1 --- /dev/null +++ b/sdnr/wt/odlux/apps/maintenanceApp/src/index.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta http-equiv="X-UA-Compatible" content="ie=edge"> + <!-- <link rel="stylesheet" href="./vendor.css"> --> + <title>Minimal App</title> +</head> + +<body> + <div id="app"></div> + <script type="text/javascript" src="./require.js"></script> + <script type="text/javascript" src="./config.js"></script> + <script> + // run the application + require(["app","connectApp", "maintenanceApp"], function (app, connectApp, maintenanceApp) { + connectApp.register(); + maintenanceApp.register(); + app("./app.tsx").runApplication(); + }); + </script> +</body> + +</html>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/maintenanceApp/src/models/maintenenceEntryType.ts b/sdnr/wt/odlux/apps/maintenanceApp/src/models/maintenenceEntryType.ts new file mode 100644 index 000000000..fca70f2af --- /dev/null +++ b/sdnr/wt/odlux/apps/maintenanceApp/src/models/maintenenceEntryType.ts @@ -0,0 +1,28 @@ +/** Represents the elestic search db type for maintenence enrties */ +export type MaintenenceEntryType = { + node: string; + // According to the arrangement from 2019.02.15 there will be currently only one element in the filters array. + filter: [{ + definition: { "object-id-ref": string ,problem: string}, + description: string, + /** The end date for the maintenence mode formated as ISO date string in UTC. */ + end: string, + /** The start date for the maintenence mode formated as ISO date string in UTC. */ + start: string + }], + /** Determines if the filter set is activated or not. */ + active: boolean; +} + +export const spoofSymbol = Symbol("Spoof"); + +/** Represents the type for an maintenence entry. */ +export type MaintenenceEntry = { + [spoofSymbol]?: boolean; + _id: string; + mountId: string; + description: string; + start: string; + end: string; + active: boolean; +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/maintenanceApp/src/plugin.tsx b/sdnr/wt/odlux/apps/maintenanceApp/src/plugin.tsx new file mode 100644 index 000000000..19e188ec2 --- /dev/null +++ b/sdnr/wt/odlux/apps/maintenanceApp/src/plugin.tsx @@ -0,0 +1,27 @@ +// app configuration and main entry point for the app + +import * as React from "react"; + +import { faLock } from '@fortawesome/free-solid-svg-icons'; // select app icon + +import applicationManager from '../../../framework/src/services/applicationManager'; + +import { maintenanceAppRootHandler } from './handlers/maintenanceAppRootHandler'; + +import MaintenenceView from "./views/maintenenceView"; + +const App : React.SFC = (props) => { + return <MaintenenceView /> +}; + +export function register() { + applicationManager.registerApplication({ + name: "maintenanceApp", + icon: faLock, + rootComponent: App, + rootActionHandler: maintenanceAppRootHandler, + menuEntry: "Maintenance App" + }); +} + + diff --git a/sdnr/wt/odlux/apps/maintenanceApp/src/services/maintenenceService.ts b/sdnr/wt/odlux/apps/maintenanceApp/src/services/maintenenceService.ts new file mode 100644 index 000000000..5e15a4c78 --- /dev/null +++ b/sdnr/wt/odlux/apps/maintenanceApp/src/services/maintenenceService.ts @@ -0,0 +1,82 @@ +import { requestRest } from '../../../../framework/src/services/restService'; +import { Result, HitEntry, PostResponse, DeleteResponse } from '../../../../framework/src/models/elasticSearch'; + +import { MaintenenceEntryType, MaintenenceEntry } from '../models/maintenenceEntryType'; +import { convertToLocaleString, convertToGMTString } from '../utils/timeUtils'; + +export const maintenenceEntryDatabasePath = "mwtn/maintenancemode"; + +/** + * Represents a web api accessor service for all maintenence entries related actions. + */ +class MaintenenceService { + /** + * Gets all maintenence entries from the backend. + */ + public async getAllMaintenenceEntries(): Promise<(MaintenenceEntry[]) | null> { + const path = `database/${maintenenceEntryDatabasePath}/_search`; + const query = { "query": { "match_all": {} } }; + + const result = await requestRest<Result<MaintenenceEntryType>>(path, { method: "POST", body: JSON.stringify(query) }); + return result && result.hits && result.hits.hits && result.hits.hits.map(me => me._source && me._source.filter[0] && ({ + _id: me._id, + mountId: me._source.node, + description: me._source.filter[0].description, + start: me._source.filter[0].start && convertToLocaleString(me._source.filter[0].start), + end: me._source.filter[0].end && convertToLocaleString(me._source.filter[0].end), + active: me._source.active + })) || null; + } + + /** + * Gets one maintenence entry by its mountId from the backend. + */ + public async getMaintenenceEntryByMountId(mountId: string): Promise<(MaintenenceEntry) | null> { + const path = `database/${maintenenceEntryDatabasePath}/${mountId}`; + + const result = await requestRest<HitEntry<MaintenenceEntryType> & { found: boolean }>(path, { method: "GET" }); + return result && result.found && result._source && result._source.filter[0] && { + _id: result._id, + mountId: result._source.node, + description: result._source.filter[0].description, + start: result._source.filter[0].start && convertToLocaleString(result._source.filter[0].start), + end: result._source.filter[0].end && convertToLocaleString(result._source.filter[0].end), + active: result._source.active + } || null; + } + + /** + * Adds or updates one maintenence entry to the backend. + */ + public async writeMaintenenceEntry(maintenenceEntry: MaintenenceEntry): Promise<PostResponse | null> { + const path = `database/${maintenenceEntryDatabasePath}/${maintenenceEntry._id}`; + const data: MaintenenceEntryType = { + node: maintenenceEntry.mountId, + filter: [ + { + definition: { "object-id-ref": "", problem: "" }, + description: "", + start: convertToGMTString(maintenenceEntry.start), + end: convertToGMTString(maintenenceEntry.end) + } + ], + active: maintenenceEntry.active + }; + + const result = await requestRest<PostResponse>(path, { method: "POST", body: JSON.stringify(data) }); + return result || null; + } + + /** + * Deletes one maintenence entry by its mountId from the backend. + */ + public async deleteMaintenenceEntry(maintenenceEntry: MaintenenceEntry): Promise<(DeleteResponse) | null> { + const path = `database/${maintenenceEntryDatabasePath}/${maintenenceEntry._id}`; + + const result = await requestRest<DeleteResponse>(path, { method: "DELETE" }); + return result || null; + } +} + +export const maintenenceService = new MaintenenceService(); +export default maintenenceService;
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/maintenanceApp/src/utils/timeUtils.ts b/sdnr/wt/odlux/apps/maintenanceApp/src/utils/timeUtils.ts new file mode 100644 index 000000000..92bc2ba8b --- /dev/null +++ b/sdnr/wt/odlux/apps/maintenanceApp/src/utils/timeUtils.ts @@ -0,0 +1,22 @@ +export function convertToGMTString(dateString: string): string { + const date = new Date(dateString); + const pad = (n: number) => (n < 10) ? '0' + n : n; + + return date.getUTCFullYear() + + '-' + pad(date.getUTCMonth() + 1) + + '-' + pad(date.getUTCDate()) + + 'T' + pad(date.getUTCHours()) + + ':' + pad(date.getUTCMinutes()) + + '+00:00'; +} + +export function convertToLocaleString(rawDate: string| number): string { + const date = new Date(rawDate); + const pad = (n: number) => (n < 10) ? '0' + n : n; + + return date.getFullYear() + + '-' + pad(date.getMonth() + 1) + + '-' + pad(date.getDate()) + + 'T' + pad(date.getHours()) + + ':' + pad(date.getMinutes()); +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/maintenanceApp/src/views/maintenenceView.tsx b/sdnr/wt/odlux/apps/maintenanceApp/src/views/maintenenceView.tsx new file mode 100644 index 000000000..f048d6cc5 --- /dev/null +++ b/sdnr/wt/odlux/apps/maintenanceApp/src/views/maintenenceView.tsx @@ -0,0 +1,200 @@ +import * as React from 'react'; + +import { Theme, createStyles, WithStyles, withStyles, Tooltip } from '@material-ui/core'; + +import AddIcon from '@material-ui/icons/Add'; +import EditIcon from '@material-ui/icons/Edit'; +import RemoveIcon from '@material-ui/icons/RemoveCircleOutline'; + +import Button from '@material-ui/core/Button'; +import IconButton from '@material-ui/core/IconButton'; + +import connect, { IDispatcher, Connect } from '../../../../framework/src/flux/connect'; +import MaterialTable, { MaterialTableCtorType, ColumnType } from '../../../../framework/src/components/material-table'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { loadAllMountedNetworkElementsAsync } from '../../../connectApp/src/actions/mountedNetworkElementsActions'; + +import { loadAllMainteneceEntriesAsyncAction } from '../actions/maintenenceActions'; +import { MaintenenceEntry, spoofSymbol } from '../models/maintenenceEntryType'; + +import EditMaintenenceEntryDialog, { EditMaintenenceEntryDialogMode } from '../components/editMaintenenceEntryDialog'; +import { convertToLocaleString } from '../utils/timeUtils'; + +const styles = (theme: Theme) => createStyles({ + button: { + margin: 0, + padding: "6px 6px", + minWidth: 'unset' + }, + spacer: { + marginLeft: theme.spacing.unit, + marginRight: theme.spacing.unit, + display: "inline" + } +}); + +const MaintenenceEntriesTable = MaterialTable as MaterialTableCtorType<MaintenenceEntry>; + +const mapProps = (state: IApplicationStoreState) => ({ + maintenenceEntries: state.maintenanceApp.maintenenceEntries.entries, + busy: state.maintenanceApp.maintenenceEntries.busy +}); + +const mapDispatcher = (dispatcher: IDispatcher) => ({ + onLoadMenteneceEntries: async () => { + await dispatcher.dispatch(loadAllMountedNetworkElementsAsync) + dispatcher.dispatch(loadAllMainteneceEntriesAsyncAction); + } +}); + +const emptyMaintenenceEntry: MaintenenceEntry = { + _id: '', + mountId: '', + description: '', + start: convertToLocaleString(new Date().valueOf()), + end: convertToLocaleString(new Date().valueOf()), + active: false, +}; + +type MaintenenceViewComponentProps = Connect<typeof mapProps, typeof mapDispatcher> & WithStyles<typeof styles> & { + +} + +type MaintenenceViewComponentState = { + maintenenceEntryToEdit: MaintenenceEntry; + maintenenceEntryEditorMode: EditMaintenenceEntryDialogMode; +} + +class MaintenenceViewComponent extends React.Component<MaintenenceViewComponentProps, MaintenenceViewComponentState> { + + constructor (props: MaintenenceViewComponentProps) { + super(props); + + this.state = { + maintenenceEntryToEdit: emptyMaintenenceEntry, + maintenenceEntryEditorMode: EditMaintenenceEntryDialogMode.None, + }; + + } + + render() { + const { classes } = this.props; + const addMaintenenceEntryAction = { + icon: AddIcon, tooltip: 'Add', onClick: () => { + const startTime = (new Date().valueOf()); + const endTime = startTime; + this.setState({ + maintenenceEntryToEdit: { + ...emptyMaintenenceEntry, + start: convertToLocaleString(startTime), + end: convertToLocaleString(endTime), + }, + maintenenceEntryEditorMode: EditMaintenenceEntryDialogMode.AddMaintenenceEntry + }); + } + }; + return ( + <> + <MaintenenceEntriesTable asynchronus rows={this.props.maintenenceEntries} customActionButtons={[ addMaintenenceEntryAction ]} columns={ + [ + { property: "mountId", title: "Mount Id", type: ColumnType.text }, + { property: "active", title: "Active", type: ColumnType.boolean, labels: { "true": "active", "false": "not active" }, }, + { property: "start", title: "Start Date", type: ColumnType.text }, + { property: "end", title: "End Date", type: ColumnType.text }, + { property: "actions", title: "Actions", type: ColumnType.custom, customControl : ({ rowData })=>( + <> + <div className={classes.spacer}> + <Tooltip title={"1h from now"} ><Button className={classes.button} onClick={ (event) => this.onOpenPlus1hEditMaintenenceEntryDialog(event, rowData)} >+1h</Button></Tooltip> + <Tooltip title={"8h from now"} ><Button className={classes.button} onClick={(event) => this.onOpenPlus8hEditMaintenenceEntryDialog(event, rowData)} >+8h</Button></Tooltip> + </div> + <div className={classes.spacer}> + <Tooltip title={"Edit"} ><IconButton className={classes.button} onClick={(event) => this.onOpenEditMaintenenceEntryDialog(event, rowData)} ><EditIcon /></IconButton></Tooltip> + <Tooltip title={"Remove"} ><IconButton disabled={ !!rowData[spoofSymbol] } className={classes.button} onClick={(event) => this.onOpenRemoveMaintenenceEntryDialog(event, rowData)} ><RemoveIcon /></IconButton></Tooltip> + </div> + </> + ) }, + ] + } idProperty={'_id'}> </MaintenenceEntriesTable> + <EditMaintenenceEntryDialog initialMaintenenceEntry={this.state.maintenenceEntryToEdit} mode={this.state.maintenenceEntryEditorMode} + onClose={ this.onCloseEditMaintenenceEntryDialog } /> + </> + ); + } + + componentDidMount(){ + this.props.onLoadMenteneceEntries(); + } + + private onOpenPlus1hEditMaintenenceEntryDialog = (event: React.MouseEvent<HTMLElement>, entry: MaintenenceEntry) => { + event.preventDefault(); + event.stopPropagation(); + const startTime = (new Date().valueOf()); + const endTime = startTime + (1 * 60 * 60 * 1000); + this.setState({ + maintenenceEntryToEdit: { + ...entry, + start: convertToLocaleString(startTime), + end: convertToLocaleString(endTime), + }, + maintenenceEntryEditorMode: EditMaintenenceEntryDialogMode.EditMaintenenceEntry + }); + } + + private onOpenPlus8hEditMaintenenceEntryDialog = (event: React.MouseEvent<HTMLElement>, entry: MaintenenceEntry) => { + event.preventDefault(); + event.stopPropagation(); + const startTime = (new Date().valueOf()); + const endTime = startTime + (8 * 60 * 60 * 1000); + this.setState({ + maintenenceEntryToEdit: { + ...entry, + start: convertToLocaleString(startTime), + end: convertToLocaleString(endTime), + }, + maintenenceEntryEditorMode: EditMaintenenceEntryDialogMode.EditMaintenenceEntry + }); + } + + private onOpenEditMaintenenceEntryDialog = (event: React.MouseEvent<HTMLElement>, entry: MaintenenceEntry) => { + event.preventDefault(); + event.stopPropagation(); + const startTime = (new Date().valueOf()); + const endTime = startTime ; + this.setState({ + maintenenceEntryToEdit: { + ...entry, + ...(entry.start && endTime) + ? { start: entry.start, end: entry.end } + : { start: convertToLocaleString(startTime), end: convertToLocaleString(endTime)} + }, + maintenenceEntryEditorMode: EditMaintenenceEntryDialogMode.EditMaintenenceEntry + }); + } + + private onOpenRemoveMaintenenceEntryDialog = (event: React.MouseEvent<HTMLElement>, entry: MaintenenceEntry) => { + event.preventDefault(); + event.stopPropagation(); + const startTime = (new Date().valueOf()); + const endTime = startTime; + this.setState({ + maintenenceEntryToEdit: { + ...entry, + ...(entry.start && endTime) + ? { start: entry.start, end: entry.end } + : { start: convertToLocaleString(startTime), end: convertToLocaleString(endTime) } + }, + maintenenceEntryEditorMode: EditMaintenenceEntryDialogMode.RemoveMaintenenceEntry + }); + } + + private onCloseEditMaintenenceEntryDialog = () => { + this.setState({ + maintenenceEntryToEdit: emptyMaintenenceEntry, + maintenenceEntryEditorMode: EditMaintenenceEntryDialogMode.None, + }); + } +} + +export const MaintenenceView = withStyles(styles)(connect(mapProps, mapDispatcher)(MaintenenceViewComponent)); +export default MaintenenceView;
\ No newline at end of file |