+
+
+ }
+ />
+ ) : (
+ ""
+ );
+
+ const hidden =
+ this.props.user && this.props.item.hidden ? (
+ }
+ />
+ ) : (
+ ""
+ );
+
+ return (
+
+ {itemImage(this.props.item.imageUrl, this.props.item.qtyUnreserved <= 0)}
+
+
+ {editBtn} {hidden} {this.props.item.name || (this.props.preview && "SMItem Name")}
+
+ {maxPerRequest}
+
+ {this.props.item.description || (this.props.preview && "Description")}
+
+ {qtyRequest}
+
+
+ );
+ }
}
function mapStateToProps(state: AppState) {
- return {
- user: state.account
- };
+ return {
+ user: state.account,
+ };
}
export default withToastManager(connect(mapStateToProps)(HardwareItem));
-
-
-
diff --git a/client/src/components/inventory/HardwareLocation.tsx b/client/src/components/inventory/HardwareLocation.tsx
index 0c290b6..d5a60bc 100644
--- a/client/src/components/inventory/HardwareLocation.tsx
+++ b/client/src/components/inventory/HardwareLocation.tsx
@@ -1,14 +1,14 @@
-import React from 'react';
-import {Header, Icon} from "semantic-ui-react";
+import React from "react";
+import { Header, Icon } from "semantic-ui-react";
-const HardwareLocation = ({location_name}: any) => {
- return (
-
-
-
These items are available from {location_name}. Pick them up and return them
- there.
-
- );
-};
+const HardwareLocation = ({ name }: any) => (
+
+
+
These items are available from {name}. Pick them up and return them there.
+
+);
export default HardwareLocation;
diff --git a/client/src/components/inventory/HardwareLocationContents.tsx b/client/src/components/inventory/HardwareLocationContents.tsx
index 04fd127..8455f72 100644
--- a/client/src/components/inventory/HardwareLocationContents.tsx
+++ b/client/src/components/inventory/HardwareLocationContents.tsx
@@ -1,86 +1,110 @@
-import React, {useState} from 'react';
+import React, { useState } from "react";
+import { Accordion, Divider, Header, Icon } from "semantic-ui-react";
+import { connect } from "react-redux";
+
import HardwareLocation from "./HardwareLocation";
-import {Accordion, Divider, Header, Icon} from "semantic-ui-react";
-import {HwItem, ItemByCat, ItemByLocation} from "../../types/Hardware";
+import { Item, ItemByCat, ItemByLocation } from "../../types/Hardware";
import HardwareCategory from "./HardwareCategory";
import NoItemsFound from "./NoItemsFound";
-import {AppState} from "../../state/Store";
-import {connect} from "react-redux";
+import { AppState } from "../../state/Store";
function handleClick(e: any, titleProps: any, accordionState: any, setAccordionState: any): void {
- const {index} = titleProps;
- const accordionStateClone = accordionState.slice(0);
-
- if (accordionStateClone.indexOf(index) > -1) {
- accordionStateClone.splice(accordionStateClone.indexOf(index), 1);
- } else {
- accordionStateClone.push(index);
- }
+ const { index } = titleProps;
+ const accordionStateClone = accordionState.slice(0);
- setAccordionState(accordionStateClone);
+ if (accordionStateClone.indexOf(index) > -1) {
+ accordionStateClone.splice(accordionStateClone.indexOf(index), 1);
+ } else {
+ accordionStateClone.push(index);
+ }
+ setAccordionState(accordionStateClone);
}
-function filteredItems(items: HwItem[], searchQuery: string): HwItem[] {
- return items.filter((item: HwItem) => item.item_name.toLowerCase().includes(searchQuery));
+function filteredItems(items: Item[], searchQuery: string): Item[] {
+ return items.filter((item: Item) => item.name.toLowerCase().includes(searchQuery));
}
-function combinedAndFilteredItemsByCategory(categories: any, searchQuery: string): HwItem[] {
- return filteredItems(categories.reduce((acc: any, val: any) => acc.concat(val.items), []), searchQuery);
+function combinedAndFilteredItemsByCategory(categories: any, searchQuery: string): Item[] {
+ return filteredItems(
+ categories.reduce((acc: any, val: any) => acc.concat(val.items), []),
+ searchQuery
+ );
}
interface HardwareListProps {
- itemsByLocation: ItemByLocation,
- searchQuery: string,
- requestsEnabled: boolean
+ itemsByLocation: ItemByLocation;
+ searchQuery: string;
+ requestsEnabled: boolean;
}
-const HardwareLocationContents = ({itemsByLocation, searchQuery, requestsEnabled}: HardwareListProps) => {
- const [accordionState, setAccordionState] = useState([0]);
+const HardwareLocationContents = ({
+ itemsByLocation,
+ searchQuery,
+ requestsEnabled,
+}: HardwareListProps) => {
+ const [accordionState, setAccordionState] = useState([0]);
- return (
-
-
-
- {itemsByLocation.categories.map((itemByCat: ItemByCat, index: number) => <>
- {filteredItems(itemByCat.items, searchQuery).length ? <>
- = 3}
- index={index}
- onClick={(e: any, titleProps: any) => {
- handleClick(e, titleProps, accordionState, setAccordionState);
- }}>
-
-
- {itemByCat.category.category_name}
-
-
- = 3}
- index={index}>
-
- > : ""}
- >)}
- {(!itemsByLocation.categories.length
- || !combinedAndFilteredItemsByCategory(itemsByLocation.categories, searchQuery).length) &&
- }
-
-
-
- );
+ return (
+
+
+
+ {itemsByLocation.categories.map((itemByCat: ItemByCat, index: number) => (
+ <>
+ {filteredItems(itemByCat.items, searchQuery).length ? (
+ <>
+ = 3}
+ index={index}
+ onClick={(e: any, titleProps: any) => {
+ handleClick(e, titleProps, accordionState, setAccordionState);
+ }}
+ >
+
+
+ {itemByCat.category.name}
+
+
+ = 3}
+ index={index}
+ >
+
+
+ >
+ ) : (
+ ""
+ )}
+ >
+ ))}
+ {(!itemsByLocation.categories.length ||
+ !combinedAndFilteredItemsByCategory(itemsByLocation.categories, searchQuery).length) && (
+
+ )}
+
+
+
+ );
};
function mapStateToProps(state: AppState) {
- return {
- user: state.account
- };
+ return {
+ user: state.account,
+ };
}
export default connect(mapStateToProps)(HardwareLocationContents);
diff --git a/client/src/components/inventory/NewHardwareList.tsx b/client/src/components/inventory/NewHardwareList.tsx
index ada161f..3e219db 100644
--- a/client/src/components/inventory/NewHardwareList.tsx
+++ b/client/src/components/inventory/NewHardwareList.tsx
@@ -1,128 +1,127 @@
-import React, {useState} from 'react';
-import {useQuery} from "@apollo/client";
-import {ALL_ITEMS, GET_SETTING} from "../util/graphql/Queries";
-import {
- Button,
- Grid,
- Header,
- Icon,
- Input,
- Loader,
- Message
-} from "semantic-ui-react";
-import {ItemByLocation} from "../../types/Hardware";
-import HardwareLocationContents from "./HardwareLocationContents";
-import {connect} from "react-redux";
-import {AppState} from "../../state/Store";
-import {User} from "../../types/User";
-import {Link} from "react-router-dom";
+import React, { useState } from "react";
+import { useQuery } from "@apollo/client";
+import { Button, Grid, Header, Icon, Input, Loader, Message } from "semantic-ui-react";
+import { connect } from "react-redux";
+import { Link } from "react-router-dom";
-const NewHardwareList = ({user}: { user: User | null }) => {
- const {data, loading, error} = useQuery(ALL_ITEMS);
- const [searchQuery, setSearchQuery] = useState("");
+import { ALL_ITEMS, GET_SETTING } from "../util/graphql/Queries";
+import { ItemByLocation } from "../../types/Hardware";
+import HardwareLocationContents from "./HardwareLocationContents";
+import { AppState } from "../../state/Store";
+import { User } from "../../types/User";
- const setting = useQuery(GET_SETTING, {
- variables: {settingName: "requests_allowed"}
- });
+const NewHardwareList = ({ user }: { user: User | null }) => {
+ const { data, loading, error } = useQuery(ALL_ITEMS);
+ const [searchQuery, setSearchQuery] = useState("");
- if (loading || setting.loading) {
- return (
- <>
-
-
- >);
- }
+ const setting = useQuery(GET_SETTING, {
+ variables: { settingName: "requests_allowed" },
+ });
- if (error) {
- return <>
-
-
- Error displaying hardware
- inventory
- Try refreshing the page. If that doesn't work,
- contact a member of the HackGT Team for
- assistance.
-
- >;
- }
+ if (loading || setting.loading) {
+ return (
+ <>
+
+
+ >
+ );
+ }
- let requestsEnabled = true;
- if(!setting.error && setting.data.setting !== undefined) {
- requestsEnabled = (setting.data.setting.value === "true");
- }
+ if (error) {
+ return (
+ <>
+
+
+ Error displaying hardware inventory
+
+ Try refreshing the page. If that doesn't work, contact a member of the HackGT Team for
+ assistance.
+
+
+ >
+ );
+ }
- let noRequestsMessageText = "";
- if (!requestsEnabled) {
- noRequestsMessageText = "Hardware checkout requests can't be made at this time.";
- } else if (requestsEnabled && !user) {
- noRequestsMessageText = "Sign in to request hardware.";
- }
+ let requestsEnabled = true;
+ if (!setting.error && setting.data.setting !== undefined) {
+ requestsEnabled = setting.data.setting.value === "true";
+ }
- const noRequestsMessage = !requestsEnabled || !user ? (
-
-
-
- Look, but do not touch
- {noRequestsMessageText}
-
-
- ) : "";
+ let noRequestsMessageText = "";
+ if (!requestsEnabled) {
+ noRequestsMessageText = "Hardware checkout requests can't be made at this time.";
+ } else if (requestsEnabled && !user) {
+ noRequestsMessageText = "Sign in to request hardware.";
+ }
- return (
-
-
-
-
-
-
- {user && user.admin ?
-
-
- Create item
- : ""}
-
-
-
- {noRequestsMessage}
-
-
- {
- if (value.length >= 3) {
- setSearchQuery(value.trim().toLowerCase());
- } else {
- setSearchQuery("");
- }
- }
- }
- />
-
-
-
- {data.allItems.map((itemsByLocation: ItemByLocation) =>
-
- )
- }
-
+ const noRequestsMessage =
+ !requestsEnabled || !user ? (
+
+
+
+ Look, but do not touch
+ {noRequestsMessageText}
+
+
+
+ ) : (
+ ""
);
+
+ return (
+
+
+
+
+
+
+ {user && user.admin ? (
+
+
+ Create item
+
+ ) : (
+ ""
+ )}
+
+
+
+ {noRequestsMessage}
+
+
+ {
+ if (value.length >= 3) {
+ setSearchQuery(value.trim().toLowerCase());
+ } else {
+ setSearchQuery("");
+ }
+ }}
+ />
+
+
+
+ {data.allItems.map((itemsByLocation: ItemByLocation) => (
+
+ ))}
+
+ );
};
function mapStateToProps(state: AppState) {
- return {
- user: state.account
- };
+ return {
+ user: state.account,
+ };
}
export default connect(mapStateToProps)(NewHardwareList);
diff --git a/client/src/components/inventory/NoItemsFound.tsx b/client/src/components/inventory/NoItemsFound.tsx
index 38595d0..136446e 100644
--- a/client/src/components/inventory/NoItemsFound.tsx
+++ b/client/src/components/inventory/NoItemsFound.tsx
@@ -1,39 +1,49 @@
-import React from 'react';
-import {Button, Header, Icon, Message, Segment} from "semantic-ui-react";
-import {Link} from "react-router-dom";
-import {AppState} from "../../state/Store";
-import {connect} from "react-redux";
+import React from "react";
+import { Button, Header, Icon, Message, Segment } from "semantic-ui-react";
+import { Link } from "react-router-dom";
+import { connect } from "react-redux";
-const NoItemsFound = ({searchQuery, user}: any) => {
- console.log("user", user);
- if (!searchQuery) {
- return (
- No items here!
- This location doesn't have any items you can see. Try again later, or contact a HackGT staff member for
- further assistance.
- {user && user.admin &&
- <>
- Create item
- Import items
- >}
- );
- }
+import { AppState } from "../../state/Store";
- return (
-
-
- No matching items were found
-
- If you're trying to find something specific, you can ask a staff member at the HackGT
- hardware desk staff for help!
-
+const NoItemsFound = ({ searchQuery, user }: any) => {
+ if (!searchQuery) {
+ return (
+
+ No items here!
+
+ This location doesn't have any items you can see. Try again later, or contact a HackGT
+ staff member for further assistance.
+
+ {user && user.admin && (
+ <>
+
+ Create item
+
+
+ Import items
+
+ >
+ )}
+
);
+ }
+
+ return (
+
+
+
+ No matching items were found
+
+ If you're trying to find something specific, you can ask a staff member at the HackGT hardware
+ desk staff for help!
+
+ );
};
function mapStateToProps(state: AppState) {
- return {
- user: state.account
- };
+ return {
+ user: state.account,
+ };
}
export default connect(mapStateToProps)(NoItemsFound);
diff --git a/client/src/components/inventory/RequestButton.tsx b/client/src/components/inventory/RequestButton.tsx
index fec0722..14fcfa1 100644
--- a/client/src/components/inventory/RequestButton.tsx
+++ b/client/src/components/inventory/RequestButton.tsx
@@ -1,86 +1,101 @@
import React from "react";
-import {Button, Icon, Loader, Message} from "semantic-ui-react";
-import {useMutation} from "@apollo/client";
-import {Query} from "@apollo/client/react/components";
-import {CREATE_REQUEST} from "../util/graphql/Mutations";
-import {RequestedItem} from "../../types/Hardware";
-import {withToastManager} from "react-toast-notifications";
-import {GET_SETTING, USER_REQUESTS} from "../util/graphql/Queries";
-import {User} from "../../types/User";
+import { Button, Icon, Loader, Message } from "semantic-ui-react";
+import { useMutation } from "@apollo/client";
+import { Query } from "@apollo/client/react/components";
+import { withToastManager } from "react-toast-notifications";
-interface RequestButtonProps {
- requestedItem: RequestedItem,
- user: User,
- toastManager: any
+import { CREATE_REQUEST } from "../util/graphql/Mutations";
+import { RequestedItem } from "../../types/Hardware";
+import { GET_SETTING, USER_REQUESTS } from "../util/graphql/Queries";
+import { User } from "../../types/User";
+
+interface Props {
+ requestedItem: RequestedItem;
+ user: User;
+ toastManager: any;
}
-function RequestButton({requestedItem, user, toastManager}: RequestButtonProps) {
- const [createRequest, {loading, error}] = useMutation(CREATE_REQUEST, {
- refetchQueries: [
- {
- query: USER_REQUESTS,
- variables: {
- uuid: user.uuid
- },
- },
- ],
- });
+const RequestButton: React.FC = ({ requestedItem, user, toastManager }) => {
+ const [createRequest, { loading, error }] = useMutation(CREATE_REQUEST, {
+ refetchQueries: [
+ {
+ query: USER_REQUESTS,
+ variables: {
+ uuid: user.uuid,
+ },
+ },
+ ],
+ });
- if (loading) {
- return ;
- }
- if (error) {
- return ;
- }
- let requests_allowed = "true";
+ if (loading) {
+ return ;
+ }
+ if (error) {
return (
-
- {
- ({loading, error, data}: any) => {
- if (loading) {
- return ;
- }
- if (!error && data.setting !== undefined) {
- requests_allowed = data.setting.value;
- }
- return createRequest({
- variables: {
- newRequest: {
- user_id: requestedItem.user,
- request_item_id: requestedItem.id,
- quantity: requestedItem.qtyRequested
- }
- }
- }).then(toastManager.add(`Successfully requested ${requestedItem.qtyRequested}x ${requestedItem.name}`, {
- appearance: "success",
- autoDismiss: true,
- placement: "top-center"
- })).catch((err: Error) => {
- toastManager.add(`Successfully requested ${requestedItem.qtyRequested}x ${requestedItem.name}`, {
- appearance: "error",
- autoDismiss: true,
- placement: "top-center"
- })
- })
- }
- labelPosition="right">
- Request {requestedItem.qtyRequested}
-
-
- }
- }
-
+
);
-}
+ }
+ let requestsAllowed = "true";
+ return (
+
+ {({ loading: queryLoading, error: queryError, data }: any) => {
+ if (queryLoading) {
+ return ;
+ }
+ if (!queryError && data.setting !== undefined) {
+ requestsAllowed = data.setting.value;
+ }
+ return (
+
+ createRequest({
+ variables: {
+ newRequest: {
+ userId: requestedItem.user,
+ itemId: requestedItem.id,
+ quantity: requestedItem.qtyRequested,
+ },
+ },
+ })
+ .then(
+ toastManager.add(
+ `Successfully requested ${requestedItem.qtyRequested}x ${requestedItem.name}`,
+ {
+ appearance: "success",
+ autoDismiss: true,
+ placement: "top-center",
+ }
+ )
+ )
+ .catch((err: Error) => {
+ toastManager.add(
+ `Successfully requested ${requestedItem.qtyRequested}x ${requestedItem.name}`,
+ {
+ appearance: "error",
+ autoDismiss: true,
+ placement: "top-center",
+ }
+ );
+ })
+ }
+ labelPosition="right"
+ >
+ Request {requestedItem.qtyRequested}
+
+
+ );
+ }}
+
+ );
+};
export default withToastManager(RequestButton);
diff --git a/client/src/components/item/CreateItemWrapper.tsx b/client/src/components/item/CreateItemWrapper.tsx
index 4508be8..c5bbd26 100644
--- a/client/src/components/item/CreateItemWrapper.tsx
+++ b/client/src/components/item/CreateItemWrapper.tsx
@@ -1,44 +1,41 @@
-import React, {Component} from "react";
-import {connect} from "react-redux";
-import {AppState} from "../../state/Store";
+import React, { Component } from "react";
+import { connect } from "react-redux";
+import { match } from "react-router";
+import { Header } from "semantic-ui-react";
+
+import { AppState } from "../../state/Store";
import ItemEditForm from "./ItemEditForm";
-import {match} from "react-router";
-import {Header} from "semantic-ui-react";
interface CreateItemProps {
- match: match & CreateItemParams;
+ match: match & CreateItemParams;
}
interface CreateItemParams {
- params: { itemId: string };
+ params: { itemId: string };
}
-interface CreateItemState {
-}
+interface CreateItemState {}
class CreateItemWrapper extends Component {
- constructor(props: CreateItemProps) {
- super(props);
- this.state = {};
-
- }
-
- public render() {
- return (
-
-
-
-
- );
- }
+ constructor(props: CreateItemProps) {
+ super(props);
+ this.state = {};
+ }
+
+ public render() {
+ return (
+
+
+
+
+ );
+ }
}
function mapStateToProps(state: AppState) {
- return {
- user: state.account
- };
+ return {
+ user: state.account,
+ };
}
-export default connect(
- mapStateToProps
-)(CreateItemWrapper);
+export default connect(mapStateToProps)(CreateItemWrapper);
diff --git a/client/src/components/item/EditItemWrapper.tsx b/client/src/components/item/EditItemWrapper.tsx
index 4adc634..72211e1 100644
--- a/client/src/components/item/EditItemWrapper.tsx
+++ b/client/src/components/item/EditItemWrapper.tsx
@@ -1,61 +1,38 @@
-import React, {Component} from "react";
-import {match} from "react-router";
-import {Query} from "@apollo/client/react/components";
+import React from "react";
+import { match } from "react-router";
+import { Query } from "@apollo/client/react/components";
+import { Header, Loader, Message } from "semantic-ui-react";
+
import ItemEditForm from "./ItemEditForm";
-import {Header, Loader, Message} from "semantic-ui-react";
-import {ITEM_EDIT_GET_ITEM} from "../util/graphql/Queries";
+import { ITEM_EDIT_GET_ITEM } from "../util/graphql/Queries";
interface EditItemProps {
- match: match & EditItemParams;
+ match: match & EditItemParams;
}
interface EditItemParams {
- params: { itemId: string };
-}
-
-interface EditItemState {
- item_name: string;
+ params: { itemId: string };
}
-class EditItemWrapper extends Component {
- constructor(props: EditItemProps) {
- super(props);
- this.state = {
- item_name: ""
- };
- }
-
-
- public render() {
- const itemId: number = parseInt(this.props.match.params.itemId, 10);
- return (
-
-
-
- {
- ({loading, error, data}: any) => {
- if (loading) {
- return ;
- } else if (error) {
- return ;
- }
- return ;
-
- }
- }
-
-
- );
- }
-}
+const EditItemWrapper: React.FC = props => {
+ const itemId: number = parseInt(props.match.params.itemId);
+
+ return (
+
+
+
+ {({ loading, error, data }: any) => {
+ if (loading) {
+ return ;
+ }
+ if (error) {
+ return ;
+ }
+ return ;
+ }}
+
+
+ );
+};
export default EditItemWrapper;
diff --git a/client/src/components/item/ItemEditForm.tsx b/client/src/components/item/ItemEditForm.tsx
index 92c6d46..77bd1bc 100644
--- a/client/src/components/item/ItemEditForm.tsx
+++ b/client/src/components/item/ItemEditForm.tsx
@@ -1,434 +1,545 @@
-import React, {ChangeEvent, Component, FormEvent} from "react";
-import HardwareItem from "../inventory/HardwareItem";
-import {Button, CheckboxProps, DropdownProps, Form, Grid, Header, Item, Label, Message, Popup} from "semantic-ui-react";
-import AddOptionDropdown from "../util/AddOptionDropdown";
-import {withToastManager} from "react-toast-notifications";
-import {Redirect} from "react-router";
-import {Mutation, Query} from "@apollo/client/react/components";
-import {CREATE_ITEM, UPDATE_ITEM} from "../util/graphql/Mutations";
-import {ALL_CATEGORIES, ALL_ITEMS, ALL_LOCATIONS} from "../util/graphql/Queries";
-import {ItemNoId, Location} from "../../types/Hardware";
+import React, { ChangeEvent, Component, FormEvent } from "react";
+import {
+ Button,
+ CheckboxProps,
+ DropdownProps,
+ Form,
+ Grid,
+ Header,
+ Item as SMItem,
+ Label,
+ Message,
+ Popup,
+} from "semantic-ui-react";
+import { withToastManager } from "react-toast-notifications";
+import { Redirect } from "react-router";
+import { Mutation, Query } from "@apollo/client/react/components";
-interface ItemDetails {
- approvalRequired: boolean;
- returnRequired: boolean;
- price: number;
- hidden: boolean;
- owner: string;
-}
+import AddOptionDropdown from "../util/AddOptionDropdown";
+import HardwareItem from "../inventory/HardwareItem";
+import { CREATE_ITEM, UPDATE_ITEM } from "../util/graphql/Mutations";
+import { ALL_CATEGORIES, ALL_ITEMS, ALL_LOCATIONS } from "../util/graphql/Queries";
+import { Item, Location, Category } from "../../types/Hardware";
interface ItemEditProps {
- preloadItemId: number;
- preloadItem: ItemComplete;
- createItem: boolean;
- toastManager: any;
- loading?: boolean;
+ preloadItemId: number;
+ preloadItem: Item;
+ createItem: boolean;
+ toastManager: any;
+ loading?: boolean;
}
-export type ItemComplete = ItemNoId & ItemDetails & {
- [name: string]: any | any[];
-};
-
-export type Category = {
- category_id: number;
- category_name: string;
+export type FormItem = {
+ name: string;
+ description: string;
+ imageUrl: string;
+ category: string;
+ totalAvailable: number;
+ maxRequestQty: number;
+ price: number;
+ hidden: boolean;
+ returnRequired: boolean;
+ approvalRequired: boolean;
+ owner: string;
+ location: string;
+ qtyUnreserved: number;
+ qtyInStock: number;
+ qtyAvailableForApproval: number;
};
interface ItemEditState {
- loading: boolean;
- item: ItemComplete;
- itemOwnerChoices: string[];
- itemPreviewKey: number;
- categoryError: boolean;
- ownerError: boolean;
- locationError: boolean;
- qtyPerRequestTooLargeError: boolean;
+ loading: boolean;
+ item: FormItem;
+ itemPreviewKey: number;
+ categoryError: boolean;
+ ownerError: boolean;
+ locationError: boolean;
+ qtyPerRequestTooLargeError: boolean;
}
class ItemEditForm extends Component {
- constructor(props: ItemEditProps) {
- super(props);
- this.state = {
- categoryError: false,
- ownerError: false,
- locationError: false,
- qtyPerRequestTooLargeError: false,
- loading: false,
- item: this.props.createItem ? {
- item_name: "",
- description: "",
- imageUrl: "",
- category: "",
- location: "",
- totalAvailable: 0,
- maxRequestQty: 0,
- qtyAvailableForApproval: 0,
- qtyUnreserved: 0,
- qtyInStock: 0,
- price: 0,
- approvalRequired: true,
- returnRequired: true,
- hidden: false,
- owner: "HackGT"
- } : this.props.preloadItem,
- itemOwnerChoices: ["HackGT", "The Hive", "Invention Studio"],
- itemPreviewKey: 0
- };
-
+ constructor(props: ItemEditProps) {
+ super(props);
+ this.state = {
+ categoryError: false,
+ ownerError: false,
+ locationError: false,
+ qtyPerRequestTooLargeError: false,
+ loading: false,
+ item: this.props.createItem
+ ? {
+ name: "",
+ description: "",
+ imageUrl: "",
+ category: "",
+ location: "",
+ totalAvailable: 0,
+ maxRequestQty: 0,
+ qtyAvailableForApproval: 0,
+ qtyUnreserved: 0,
+ qtyInStock: 0,
+ price: 0,
+ approvalRequired: true,
+ returnRequired: true,
+ hidden: false,
+ owner: "HackGT",
+ }
+ : {
+ name: this.props.preloadItem.name,
+ description: this.props.preloadItem.description,
+ imageUrl: this.props.preloadItem.imageUrl,
+ category: this.props.preloadItem.category.name,
+ location: this.props.preloadItem.location.name,
+ totalAvailable: this.props.preloadItem.totalAvailable,
+ maxRequestQty: this.props.preloadItem.maxRequestQty,
+ qtyAvailableForApproval: this.props.preloadItem.qtyAvailableForApproval,
+ qtyUnreserved: this.props.preloadItem.qtyUnreserved,
+ qtyInStock: this.props.preloadItem.qtyInStock,
+ price: this.props.preloadItem.price,
+ approvalRequired: this.props.preloadItem.approvalRequired,
+ returnRequired: this.props.preloadItem.returnRequired,
+ hidden: this.props.preloadItem.hidden,
+ owner: this.props.preloadItem.owner,
+ },
+ itemPreviewKey: 0,
+ };
+ }
+
+ public handleInputChangeCheckbox = (
+ event: FormEvent,
+ checkboxProps: CheckboxProps
+ ): void => {
+ if (checkboxProps && checkboxProps.name && typeof checkboxProps.checked !== "undefined") {
+ const value: boolean = checkboxProps.checked;
+ const { name } = checkboxProps;
+
+ this.setState(prevState => ({
+ item: {
+ ...prevState.item,
+ [name]: value,
+ },
+ }));
}
-
- public handleInputChangeCheckbox = (event: FormEvent, checkboxProps: CheckboxProps): void => {
- if (checkboxProps && checkboxProps.name && typeof checkboxProps.checked !== "undefined") {
- const value: boolean = checkboxProps.checked;
- const name: string = checkboxProps.name;
-
- // @ts-ignore
- this.setState({
- item: {
- ...this.state.item,
- [name]: value
- }
- });
- }
+ };
+
+ public handleInputChangeDropdown = (
+ event: React.SyntheticEvent,
+ data: DropdownProps,
+ inputName: string
+ ): void => {
+ const { value } = data;
+ this.setState(prevState => ({
+ item: {
+ ...prevState.item,
+ [inputName]: value,
+ },
+ }));
+ };
+
+ public handleInputChange = (event: ChangeEvent): void => {
+ const { target } = event;
+ let { value }: { value: any } = target;
+ const { name } = target;
+ const inputType = target.type;
+
+ // Convert number input values to numbers
+ if (inputType === "number") {
+ value = Number.parseFloat(value);
}
- public handleInputChangeDropdown = (event: React.SyntheticEvent, data: DropdownProps, inputName: string): void => {
- const value = data.value;
- // @ts-ignore
- this.setState({
- item: {
- ...this.state.item,
- [inputName]: value
- }
- });
+ if (name === "totalAvailable") {
+ this.setState(prevState => ({
+ itemPreviewKey: prevState.itemPreviewKey + 1,
+ }));
}
- public handleInputChange = (event: ChangeEvent): void => {
- const target = event.target;
- let value: string | number = target.value;
- const name = target.name;
- const inputType = target.type;
-
- // Convert number input values to numbers
- if (inputType === "number") {
- value = Number.parseFloat(value);
- }
-
- if (name === "totalAvailable") {
- this.setState({
- itemPreviewKey: this.state.itemPreviewKey + 1
- });
- }
-
- // @ts-ignore
- this.setState({
- item: {
- ...this.state.item,
- [name]: value
- }
- });
- }
-
- public render() {
- const itemOwnerChoices = ["HackGT", "The Hive", "Invention Studio"];
-
- const qtyPerRequestTooBigErrorMessage = ;
-
-
- return (
-
-
-
-
- {(submitForm: any, {loading, error, data}: any) => (
-
-
-
-
-
-
-
-
-
- {(result: any) => {
- const queryLoading = result.loading;
- const queryError = result.error;
- const queryData = result.data;
- let categoriesList: string[] = [];
- let dataLoadedKey = 0; // this allows us to "reset" the AddOptionDropdown when we
- // have the list of existing categories it should show.
- // See https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
- // for more information on why this is necessary and why it works.
- if (queryData && queryData.categories) {
- categoriesList = queryData.categories.map((item: Category) => item.category_name);
- dataLoadedKey = 1;
- }
- const queryErrorMsg = ;
- return (
- {queryError ? queryErrorMsg : ""}
-
- );
- }}
-
-
-
-
- $
-
-
-
-
-
-
-
-
-
-
-
- {(result: any) => {
- const queryLoading = result.loading;
- const queryError = result.error;
- const queryData = result.data;
- let locationsList: string[] = [];
- let dataLoadedKey = 0; // this allows us to "reset" the AddOptionDropdown when we
- // have the list of existing locations it should show.
- // See https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
- // for more information on why this is necessary and why it works.
- if (queryData && queryData.locations) {
- locationsList = queryData.locations.map((item: Location) => item.location_name);
- dataLoadedKey = 1;
- }
- const queryErrorMsg = ;
-
- return (
- {queryError ? queryErrorMsg : ""}
-
- );
- }}
-
-
-
-
-
- Stock
- {this.state.qtyPerRequestTooLargeError ? qtyPerRequestTooBigErrorMessage : ""}
-
-
-
-
-
-
-
-
- Calculated Quantities
-
-
- Unreserved}
- content="The number of an item that is not reserved"/>
- {this.state.item.qtyUnreserved}
-
-
- In stock}
- content="The number of an item that should be physically at the hardware desk"/>
- {this.state.item.qtyInStock}
-
-
- Available for approval}
- content="The number of an item that is available to be allocated to requests waiting to be approved"/>
- {this.state.item.qtyAvailableForApproval}
-
-
- {/* */}
-
- Checkout Controls
-
- }
- content="Whether users who check out this item are expected to return it"/>
- }
- content="Whether hardware checkout staff must approve requests for this item"/>
- }
- content="Whether to hide this item on the public list of hardware"/>
-
- {this.props.createItem ? "Create item" : "Edit item"}
-
-
- )}
-
-
-
-
-
- ({
+ item: {
+ ...prevState.item,
+ [name]: value,
+ },
+ }));
+ };
+
+ public render() {
+ const itemOwnerChoices = ["HackGT", "The Hive", "Invention Studio"];
+
+ const qtyPerRequestTooBigErrorMessage = (
+
+ );
+
+ return (
+
+
+
+
+ {(submitForm: any, { loading, error, data }: any) => (
+
+
+
+
+
+
+
+
+ {(result: any) => {
+ const queryLoading = result.loading;
+ const queryError = result.error;
+ const queryData = result.data;
+ let categoriesList: string[] = [];
+ let dataLoadedKey = 0; // this allows us to "reset" the AddOptionDropdown when we
+ // have the list of existing categories it should show.
+ // See https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
+ // for more information on why this is necessary and why it works.
+ if (queryData && queryData.categories) {
+ categoriesList = queryData.categories.map(
+ (item: Category) => item.name
+ );
+ dataLoadedKey = 1;
+ }
+ const queryErrorMsg = (
+
-
-
-
-
- );
- }
+ );
+ return (
+
+ {queryError ? queryErrorMsg : ""}
+
+
+ );
+ }}
+
+
+
+ $
+
+
+
+
+
+
+
+
+
+
+ {(result: any) => {
+ const queryLoading = result.loading;
+ const queryError = result.error;
+ const queryData = result.data;
+ let locationsList: string[] = [];
+ let dataLoadedKey = 0; // this allows us to "reset" the AddOptionDropdown when we
+ // have the list of existing locations it should show.
+ // See https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
+ // for more information on why this is necessary and why it works.
+ if (queryData && queryData.locations) {
+ locationsList = queryData.locations.map((item: Location) => item.name);
+ dataLoadedKey = 1;
+ }
+ const queryErrorMsg = (
+
+ );
+
+ return (
+
+ {queryError ? queryErrorMsg : ""}
+
+
+ );
+ }}
+
+
+
+
+ Stock
+ {this.state.qtyPerRequestTooLargeError ? qtyPerRequestTooBigErrorMessage : ""}
+
+
+
+
+
+
+
+
+ Calculated Quantities
+
+
+ Unreserved}
+ content="The number of an item that is not reserved"
+ />
+ {this.state.item.qtyUnreserved}
+
+
+ In stock}
+ content="The number of an item that should be physically at the hardware desk"
+ />
+ {this.state.item.qtyInStock}
+
+
+ Available for approval}
+ content="The number of an item that is available to be allocated to requests waiting to be approved"
+ />
+ {this.state.item.qtyAvailableForApproval}
+
+
+ {/* */}
+
+ Checkout Controls
+
+
+ }
+ content="Whether users who check out this item are expected to return it"
+ />
+
+ }
+ content="Whether hardware checkout staff must approve requests for this item"
+ />
+
+ }
+ content="Whether to hide this item on the public list of hardware"
+ />
+
+
+ {this.props.createItem ? "Create item" : "Edit item"}
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+ }
}
export default withToastManager(ItemEditForm);
diff --git a/client/src/components/item/ItemWrapper.tsx b/client/src/components/item/ItemWrapper.tsx
index 0030ff1..a6b6588 100644
--- a/client/src/components/item/ItemWrapper.tsx
+++ b/client/src/components/item/ItemWrapper.tsx
@@ -1,20 +1,21 @@
-import React, {Component} from "react";
-import {match, Switch} from "react-router";
+import React from "react";
+import { match, Switch } from "react-router";
+
import CreateItemWrapper from "./CreateItemWrapper";
import PrivateRoute from "../util/PrivateRoute";
import EditItemWrapper from "./EditItemWrapper";
-class ItemWrapper extends Component<{ match: match }> {
- public render() {
- return (
-
- );
- }
+interface ItemWrapperProps {
+ match: match;
}
+const ItemWrapper: React.FC = props => (
+
+);
+
export default ItemWrapper;
diff --git a/client/src/components/reports/demand/ItemDemandReport.tsx b/client/src/components/reports/demand/ItemDemandReport.tsx
index 58249eb..2216b78 100644
--- a/client/src/components/reports/demand/ItemDemandReport.tsx
+++ b/client/src/components/reports/demand/ItemDemandReport.tsx
@@ -1,192 +1,231 @@
-import React, {useMemo} from 'react';
-import {useQuery} from "@apollo/client";
-import {DETAILED_ITEM_STATISTICS} from "../../util/graphql/Queries";
-import {Header, List, Message, Table} from "semantic-ui-react";
+import React, { useMemo } from "react";
+import { useQuery } from "@apollo/client";
+import { Header, List, Message, Table } from "semantic-ui-react";
import DataTable from "react-data-table-component";
+
+import { DETAILED_ITEM_STATISTICS } from "../../util/graphql/Queries";
import LoadingSpinner from "../../util/LoadingSpinner";
import {
- ABANDONED,
- APPROVED,
- CANCELLED,
- DAMAGED,
- DENIED,
- FULFILLED,
- ItemWithStatistics,
- LOST,
- RETURNED
+ ABANDONED,
+ APPROVED,
+ CANCELLED,
+ DAMAGED,
+ DENIED,
+ FULFILLED,
+ ItemWithStatistics,
+ LOST,
+ RETURNED,
} from "../../../types/Hardware";
function ColumnDef(column: string, def: string) {
- return {column, def};
+ return { column, def };
}
function customSort(rows: any[], field: string, direction: string) {
- console.log(rows, field, direction);
- // const splitField = field.split(".")
-
+ console.log(rows, field, direction);
+ // const splitField = field.split(".")
- return rows;
+ return rows;
}
-
-function ItemDemandReport(props: {}) {
- const {data, loading, error} = useQuery(DETAILED_ITEM_STATISTICS, {
- partialRefetch: true
- });
-
- const columns = useMemo(() => [
- {
- name: "Location",
- selector: "item.location.location_name",
- sortable: true,
- grow: 2
-
- },
- {
- name: "Item",
- selector: "item.item_name",
- sortable: true,
- grow: 2
- },
- {
- name: "Total Available",
- selector: "item.totalAvailable",
- sortable: true,
- center: true
- },
- {
- name: "Demand",
- selector: "totalDemand",
- sortable: true,
- center: true
- },
- {
- name: "Qty Approved",
- selector: "totalApproved",
- sortable: true,
- center: true
- },
- {
- name: "Qty Fulfilled",
- selector: "totalFulfilled",
- sortable: true,
- center: true
- },
- {
- name: "Qty Not Fulfilled",
- selector: "totalNotFulfilled",
- sortable: true,
- center: true
- },
- {
- name: "Qty Returned",
- selector: "totalReturned",
- sortable: true,
- center: true
- },
- {
- name: "Qty Lost/Damaged",
- selector: "totalLostDamaged",
- sortable: true,
- center: true
- }
- ], []);
-
- const totalRequests = loading ? 0 : data.itemStatistics.reduce((prev: number, item: ItemWithStatistics) =>
- prev + item.detailedQuantities.total, 0);
-
- function sumQtys(itemData: ItemWithStatistics, statuses: string[]) {
- return statuses.reduce((accumulator: number, status: string) => itemData.detailedQuantities[status] + accumulator, 0);
- }
-
- function calculatePartial(itemData: ItemWithStatistics, statuses: string[], totalAvailable: number) {
- const itemTotal = sumQtys(itemData, statuses);
- const percent = (itemTotal / totalAvailable * 100).toPrecision(3);
-
- return `${itemTotal} (${percent}%)`
- }
-
- const calculatedData = loading || totalRequests === 0 ? [] : data.itemStatistics.map((itemData: ItemWithStatistics) => {
- const totalAvailable = itemData.item.totalAvailable;
- return {
- ...itemData,
- percentAllRequests: `${itemData.detailedQuantities.total / totalRequests * 100}%`,
- totalDemand: `${itemData.detailedQuantities.total} (${(itemData.detailedQuantities.total / itemData.item.totalAvailable * 100).toPrecision(3)}%)`,
- totalApproved: calculatePartial(itemData, [APPROVED, FULFILLED, RETURNED], totalAvailable),
- totalFulfilled: calculatePartial(itemData, [FULFILLED, RETURNED], totalAvailable),
- totalNotFulfilled: sumQtys(itemData, [ABANDONED, CANCELLED, DENIED]),
- totalReturned: sumQtys(itemData, [RETURNED]),
- totalLostDamaged: calculatePartial(itemData, [LOST, DAMAGED], totalAvailable)
- }
- });
-
- if (error) {
- return <>
-