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 <>
Categorized
{categoryTable()}
Categorized by Subcategory
{subcategoryTable()}
By Name
{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}) => ( <> { this.props.deleteButton && } )} } } 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 <>
Inventory
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}) => ( <> {this.buildOptions()} )} } } 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}) => ( <>