import React, {useContext, useState} from 'react';
import "../static/wrolpi.css";
import {
deleteInventory,
deleteItems,
getBrands,
getCategories,
getInventories,
getItems,
saveInventory,
saveItem,
updateInventory,
updateItem
} from "../api";
import {
Checkbox,
Dropdown,
Form,
Grid,
Portal,
TableBody,
TableCell,
TableHeader,
TableHeaderCell,
TableRow
} from "semantic-ui-react";
import {Route, Routes} from "react-router-dom";
import {toast} from 'react-semantic-toasts';
import {arraysEqual, enumerate, isEmpty, PageContainer, replaceNullValues, TabLinks, useTitle} from './Common';
import _ from 'lodash';
import {ThemeContext} from "../contexts/contexts";
import {useInventory} from "../hooks/customHooks";
import {Button, FormGroup, FormInput, Header, Segment, Table} from "./Theme";
function InventorySummary() {
const {t} = useContext(ThemeContext);
const [inventoryId, setInventoryId] = useState();
const {byCategory, bySubcategory, byName} = useInventory(inventoryId);
const setInventory = (inventory) => {
setInventoryId(inventory.id);
}
const categoryTable = () => {
function row([key, i]) {
return
{i.category}
{i.total_size}
{i.unit}
}
if (!isEmpty(byCategory)) {
return
Category
Total Size
Unit
{byCategory.map(row)}
;
}
return No items have been added to this inventory.
;
}
const subcategoryTable = () => {
function row([key, i]) {
return
{i.category}
{i.subcategory}
{i.total_size}
{i.unit}
}
if (!isEmpty(bySubcategory )) {
return
Category
Subcategory
Total Size
Unit
{bySubcategory.map(row)}
;
}
return No items have been added to this inventory.
;
}
const nameTable = () => {
function row([key, i]) {
return
{i.brand}
{i.name}
{i.total_size}
{i.unit}
}
if (!isEmpty(byName)) {
return
Brand
Product Name
Total Size
Unit
{byName.map(row)}
;
}
return No items have been added to this inventory.
;
}
return <>
{categoryTable()}
Categorized by Subcategory
{subcategoryTable()}
{nameTable()}
>
}
class InventoryList extends React.Component {
constructor(props) {
super(props);
this.state = {
checkboxes: [],
editItems: {},
}
}
handleCheckbox = (checkbox, item) => {
// Add or remove a checkbox ID from the checkboxes array.
let checkboxes = this.state.checkboxes;
let checked = checkbox.current.state.checked;
// Add the item id to the list of checked checkboxes.
let value = checkbox.current.props.value;
if (!checked) {
checkboxes = checkboxes.concat([value]);
} else {
const index = checkboxes.indexOf(value);
if (index > -1) {
checkboxes.splice(index, 1);
}
}
// Create or remove the editItem. This is a copy of the item which can then be modified and saved.
let editItems = this.state.editItems;
if (!checked) {
let editItem = {}
Object.assign(editItem, item);
replaceNullValues(editItem);
editItems[item.id] = editItem;
} else {
delete editItems[item.id];
}
this.setState({checkboxes: checkboxes, editItems: editItems});
}
handleRemove = async (e) => {
e.preventDefault();
await deleteItems(this.state.checkboxes);
await this.props.fetchItems();
}
handleInputChange = (e, {name, value}, itemId) => {
let editItems = this.state.editItems;
editItems[itemId][name] = value;
this.setState({editItems});
}
handleSave = async (e) => {
e.preventDefault();
let keys = Object.keys(this.state.editItems);
for (let i = 0; i < keys.length; i++) {
let itemId = keys[i];
let item = this.state.editItems[itemId];
await updateItem(itemId, item);
}
this.setState({editItems: {}, checkboxes: []}, this.props.fetchItems);
}
row = (item) => {
let ref = React.createRef();
let editable = this.state.checkboxes.indexOf(item.id) >= 0;
let checkboxCell =
this.handleCheckbox(ref, item)}
/>
;
if (!editable) {
// Show the user the non-editable version, until they check the checkbox.
return (
{checkboxCell}
{item.brand}
{item.name}
{item.item_size}
{item.unit}
{item.count}
{
item.subcategory}
{item.category}
{item.expiration_date}
)
} else {
// Insert the modified table row so the user can edit this item.
let editItem = this.state.editItems[item.id];
return
{checkboxCell}
this.handleInputChange(i, j, item.id)}
/>
this.handleInputChange(i, j, item.id)}
/>
this.handleInputChange(i, j, item.id)}
/>
this.handleInputChange(i, j, item.id)}
/>
this.handleInputChange(i, j, item.id)}
/>
this.handleInputChange(i, j, item.id)}
subcategory={editItem.subcategory}
category={editItem.category}
/>
this.handleInputChange(i, j, item.id)}
/>
}
}
handleSort = (columns) => {
this.props.handleSort(columns);
}
render() {
if (isEmpty(this.props.items)) {
return
{({t}) => (
Add some items using the form above!
)}
}
const {sortColumns, sortDirection} = this.props;
return <>
this.handleSort(['id'])}
>
Edit
this.handleSort(['brand', 'name'])}
>
Brand
this.handleSort(['name', 'brand'])}
>
Name
this.handleSort(['size'])}
>
Size
this.handleSort(['unit'])}
>
Unit
this.handleSort(['count'])}
>
Count
this.handleSort(['subcategory', 'category'])}
>
Subcategory
this.handleSort(['category', 'subcategory'])}
>
Category
this.handleSort(['expiration_date'])}
>
Expiration Date
{this.props.items.map((i) => this.row(i))}
>
}
}
class InventoryPortal extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
inventory: null,
name: '',
};
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.props !== prevProps) {
this.setState({
inventory: this.props.inventory,
name: this.props.inventory ? this.props.inventory.name : '',
});
}
}
handleClose = () => this.setState({open: false});
handleOpen = () => this.setState({open: true});
handleInputChange = (e, {name, value}) => {
this.setState({[name]: value});
}
handleSubmit = async (e) => {
e.preventDefault();
let inventory = {
id: this.state.inventory ? this.state.inventory.id : null,
name: this.state.name || (this.state.inventory ? this.state.inventory.name : ''),
};
await this.props.handleSubmit(inventory, this.handleClose);
}
handleDelete = async (e) => {
e.preventDefault();
this.props.handleDelete(this.state.inventory, this.handleClose);
}
render() {
return
{({s}) => (
<>
>
)}
}
}
class EditInventory extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
inventory: props.inventory,
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps !== this.props) {
this.setState({inventory: this.props.inventory});
}
}
handleSubmit = async (inventory, closeCallback) => {
let inventoryId = inventory.id;
delete inventory['id'];
let response = await updateInventory(inventoryId, inventory);
if (response.status === 204) {
await this.props.success();
closeCallback();
} else {
toast({
type: 'error',
header: 'Failed to save inventory!',
description: 'Server responded with an error.',
time: 5000,
})
}
}
handleDelete = async (inventory, closeCallback) => {
let response = await deleteInventory(inventory.id);
if (response.status === 204) {
await this.props.success();
closeCallback();
} else {
toast({
type: 'error',
header: 'Failed to delete inventory!',
description: 'Server responded with an error.',
time: 5000,
})
}
}
render() {
return (
)
}
}
class NewInventory extends React.Component {
handleSubmit = async (inventory, closeCallback) => {
delete inventory.id;
let response = await saveInventory(inventory);
if (response.status === 201) {
await this.props.success();
closeCallback();
} else {
toast({
type: 'error',
header: 'Failed to save inventory!',
description: 'Server responded with an error.',
time: 5000,
})
}
}
render() {
return (
)
}
}
class InventorySelector extends React.Component {
constructor(props) {
super(props);
this.state = {
options: [],
inventories: [],
selected: null,
newInventoryPortal: false,
editInventoryPortal: false,
name: '',
}
}
async componentDidMount() {
await this.fetchInventories();
}
async fetchInventories() {
try {
let inventories = await getInventories();
let options = [];
for (let i = 0; i < inventories.length; i++) {
let inventory = inventories[i];
options = options.concat([{
key: inventory.id,
text: inventory.name,
value: inventory.id,
id: inventory.id,
}])
}
let selected = this.state.selected || inventories[0];
this.setState({inventories, options, selected},
() => this.props.setInventory(selected));
} catch (e) {
console.error(e);
}
}
setInventory = (e, {value}) => {
let selected = null;
for (let i = 0; i < this.state.inventories.length; i++) {
if (value === this.state.inventories[i].id) {
selected = this.state.inventories[i];
}
}
this.setState({selected: selected},
() => this.props.setInventory(this.state.selected));
}
render() {
return <>
this.fetchInventories()}/>
this.fetchInventories()}
/>
>
}
}
class SuggestionInput extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
}
}
componentDidMount() {
this.setState({value: this.props.value !== undefined ? this.props.value : ''})
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps !== this.props && this.props.value !== undefined) {
this.setState({value: this.props.value});
}
}
buildOptions() {
return this.props.options.map((i) => )
}
handleInputChange = (e, option) => {
e.preventDefault();
let [key, value,] = option.value.split(',');
if (!isNaN(key)) {
// User chose from the suggestions.
this.setState({value: value});
this.props.handleInputChange(e, {name: option.name, key: key, value: value})
} else {
// User typed in something new.
value = key === undefined ? '' : key;
this.setState({value: value});
this.props.handleInputChange(e, {name: option.name, value: value});
}
}
clearInput() {
this.setState({value: ''});
}
render() {
return
{({s}) => (
<>
>
)}
}
}
class CategoryInputs extends React.Component {
constructor(props) {
super(props);
this.state = {
categories: [],
subcategory: '',
category: '',
}
}
componentDidMount = async () => {
await this.fetchCategories();
this.setState({
subcategory: this.props.subcategory !== undefined ? this.props.subcategory : '',
category: this.props.category !== undefined ? this.props.category : '',
});
}
fetchCategories = async () => {
try {
let categories = await getCategories();
categories = enumerate(categories);
let newCategories = [];
for (let i = 0; i < categories.length; i++) {
let [key, sc] = categories[i];
let [subcategory, category] = sc;
newCategories = newCategories.concat([[key, subcategory, `${subcategory}/${category}`]]);
}
this.setState({categories: newCategories});
} catch (e) {
console.error(e);
}
}
clearInputs = () => {
this.subcategoryRef.current.clearInput();
this.setState({
subcategory: '',
category: '',
});
}
handleInputChange = (e, {name, value, key}) => {
this.setState({[name]: value});
if (key) {
// User chose from suggestions. Set the associated category.
let [subcategory, category] = this.state.categories[key][2].split('/');
this.setState({subcategory: subcategory, category: category});
this.props.handleInputChange(e, {'name': 'subcategory', 'value': subcategory});
this.props.handleInputChange(e, {'name': 'category', 'value': category});
} else {
this.props.handleInputChange(e, {name, value});
}
}
render() {
this.subcategoryRef = React.createRef();
return
{({s}) => (
)}
}
}
class TableCategoryInputs extends CategoryInputs {
render() {
return (
<>
>
)
}
}
class BrandInput extends React.Component {
constructor(props) {
super(props);
this.state = {
brand: '',
options: [],
}
}
async componentDidMount() {
await this.fetchBrands();
this.setState({brand: this.props.value !== undefined ? this.props.value : ''});
}
fetchBrands = async () => {
try {
let brands = await getBrands();
brands = enumerate(brands);
let newBrands = [];
for (let i = 0; i < brands.length; i++) {
let [key, brand] = brands[i];
newBrands = newBrands.concat([[key, brand, brand]]);
}
this.setState({options: newBrands});
} catch (e) {
console.error(e);
}
}
handleInputChange = (e, {name, value}) => {
this.setState({[name]: value});
this.props.handleInputChange(e, {name, value});
}
clearInput() {
this.setState({brand: ''});
}
render() {
return (
)
}
}
class InventoryAddList extends React.Component {
constructor(props) {
super(props);
this.state = {
inventory: null,
categories: [],
sort: {columns: null, direction: 'ascending'},
items: [],
brand: '',
name: '',
item_size: '',
unit: '',
count: '',
category: '',
subcategory: '',
expiration_date: '',
}
}
componentDidMount = async () => {
await this.fetchItems();
}
fetchItems = async () => {
if (this.state.inventory) {
this.setState({items: []});
let response = await getItems(this.state.inventory.id);
let items = response.items;
this.setState({items: items});
}
}
handleInputChange = (e, {name, value}) => {
this.setState({[name]: value});
}
clearInputs = () => {
this.setState({
brand: '',
name: '',
item_size: '',
unit: '',
count: '',
category: '',
subcategory: '',
expiration_date: '',
});
this.categoriesRef.current.clearInputs();
this.brandRef.current.clearInput();
}
handleSubmit = async (e) => {
e.preventDefault();
let item = {
brand: this.state.brand,
name: this.state.name,
item_size: this.state.item_size,
unit: this.state.unit,
count: this.state.count,
category: this.state.category,
subcategory: this.state.subcategory,
expiration_date: this.state.expiration_date,
};
let response = await saveItem(this.state.inventory.id, item);
if (response.status === 204) {
await this.clearInputs();
await this.fetchItems();
await this.fetchCategories();
await this.fetchBrands();
} else {
toast({
type: 'error',
header: 'Failed to save item!',
description: 'Server responded with an error.',
time: 5000,
})
}
}
setInventory = async (inventory) => {
this.setState({inventory}, this.fetchItems);
}
fetchCategories = async () => {
await this.categoriesRef.current.fetchCategories();
}
fetchBrands = async () => {
await this.brandRef.current.fetchBrands();
}
handleSort = (newColumns) => {
let sorted = _.sortBy(this.state.items, newColumns);
let {columns, direction} = this.state.sort;
if (arraysEqual(columns, newColumns)) {
direction = direction === 'ascending' ? 'descending' : 'ascending';
}
if (direction === 'descending') {
sorted = sorted.reverse();
}
this.setState({items: sorted, sort: {columns: newColumns, direction: direction}});
}
render() {
this.categoriesRef = React.createRef();
this.brandRef = React.createRef();
return
{({s}) => (
<>
Items in: {this.state.inventory ? this.state.inventory.name : ''}
>
)}
}
}
export function InventoryRoute(props) {
useTitle('Inventory');
const links = [
{text: 'List', to: '/inventory', key: 'inventory', end: true},
{text: 'Summary', to: '/inventory/summary', key: 'summary'},
];
return (
}/>
}/>
)
}