diff options
-rw-r--r-- | src/components/pickers/item-picker.js | 56 | ||||
-rw-r--r-- | src/components/pickers/scss/_picker.scss | 38 | ||||
-rw-r--r-- | src/components/pickers/scss/item-picker.scss | 35 | ||||
-rw-r--r-- | src/components/tables/invoice-item-table.js | 85 | ||||
-rw-r--r-- | src/views/invoice/new.js | 9 |
5 files changed, 159 insertions, 64 deletions
diff --git a/src/components/pickers/item-picker.js b/src/components/pickers/item-picker.js index 46310db..339319f 100644 --- a/src/components/pickers/item-picker.js +++ b/src/components/pickers/item-picker.js @@ -10,8 +10,7 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License +* You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ @@ -19,10 +18,11 @@ import { Item, InvoiceItem, getAllItems } from '../../classes/item'; import './scss/item-picker.scss'; import { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; //import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' //import { faPhone, faEnvelope, faGlobe } from '@fortawesome/free-solid-svg-icons' -const ItemPicker = () => { +const ItemPicker = ({invoiceItems, addInvoiceItem}) => { const [items, setItems] = useState([new Item()]); const [item, setItem] = useState(new InvoiceItem()); @@ -44,30 +44,33 @@ const ItemPicker = () => { })); } + // add item to the invoice items list + const addItem = (e) => { + e.preventDefault(); + addInvoiceItem(item); + setItem(new InvoiceItem()); + } + + // input elements are sorted on the basis of + // how likely they are going to be edited return ( <div className={"picker-wrapper"}> <p className={"heading"}>Add an Item</p> - <div className={"item-picker"}> - {items && items.length > 0 && + <form className={"item-picker"} onSubmit={addItem}> + {items.length > 0 ? <> <label> Product/Service: <select value={item.Id ? item.Id : ""} onChange={handleItemSelect}> <option key="placeholder" value={""} disabled>Select an Item</option> {items.map(i => - <option key={i.Id} value={i.Id}>{i.Name}{i.Brand.Id === null ? "" : " - " + i.Brand.Name}</option> + <option key={i.Id} value={i.Id} disabled={invoiceItems.some(j => j.Id === i.Id)}> + {i.Name}{i.Brand.Id === null ? "" : " - " + i.Brand.Name} + </option> )} </select> </label> - <label> - Description: - <input - type="text" - value={item.Description} - name="Description" - onChange={handleInput} /> - </label> - <label> + <label className={"narrow"}> Quantity: <input type="number" @@ -77,7 +80,7 @@ const ItemPicker = () => { max={item.MaxQuantity} onChange={handleInput} /> </label> - <label> + <label className={"narrow"}> Price: <input type="number" @@ -85,7 +88,7 @@ const ItemPicker = () => { name="UnitPrice" onChange={handleInput} /> </label> - <label> + <label className={"narrow"}> Discount %: <input type="number" @@ -94,15 +97,22 @@ const ItemPicker = () => { onChange={handleInput} /> </label> <label> + Description: + <input + type="text" + value={item.Description} + name="Description" + onChange={handleInput} /> + </label> + <label className={"narrow"}> HSN: <input - className={"narrow"} type="text" value={item.HSN} name="HSN" onChange={handleInput} /> </label> - <label> + <label className={"narrow"}> GST %: <input type="number" @@ -110,9 +120,13 @@ const ItemPicker = () => { name="GSTPercentage" onChange={handleInput} /> </label> - </> + <input type="submit" value="Add"/> + </> : + <Link to="/manage/items"> + <input type="button" value="Add Items"/> + </Link> } - </div> + </form> <hr/> </div> ); diff --git a/src/components/pickers/scss/_picker.scss b/src/components/pickers/scss/_picker.scss index 625a1e3..4d8d1b1 100644 --- a/src/components/pickers/scss/_picker.scss +++ b/src/components/pickers/scss/_picker.scss @@ -65,41 +65,3 @@ color: $primaryAccentColor; } } - -@mixin picker { - .picker { - padding-bottom: 2.5rem; - margin: auto; - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: space-evenly; - align-items: center; - min-width: 90%; - @include label; - - .buttons { - position: absolute; - display: flex; - justify-content: space-between; - width: 13.5rem; - input { - padding: 0.2rem 0; - width: 4rem; - background-color: $inputBackgroundColor; - border: 1px solid $primaryAccentColor; - color: $fgColor; - border-radius: 4px; - transition: background-color 0.4s, color 0.4s; - } - input:hover { - background-color: $primaryAccentColor; - color: $fgColorAlt; - } - bottom: 0; - left: 0; - right: 0; - margin: auto; - } - } -} diff --git a/src/components/pickers/scss/item-picker.scss b/src/components/pickers/scss/item-picker.scss index b04fb54..093b0e9 100644 --- a/src/components/pickers/scss/item-picker.scss +++ b/src/components/pickers/scss/item-picker.scss @@ -23,8 +23,13 @@ } .item-picker { - display: flex; - justify-content: space-evenly; + //display: flex; + //flex-wrap: wrap; + //justify-content: space-between; + //align-items: center; + display: grid; + grid-template-columns: auto auto auto auto; + .options { width: 27rem; flex-grow: 1; @@ -46,14 +51,36 @@ white-space: pre-line; } - input[type=number], input.narrow { + label.narrow { + max-width: 16rem; + input { width: 7rem; } + } + + input[type=number] { -moz-appearance: textfield; width: 7rem; } - input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } + + input[type=button], input[type=submit] { + padding: 0.2rem 0; + width: 4rem; + height: 1.7rem; + margin-top: 0.3rem; + margin-left: auto; + background-color: $inputBackgroundColor; + border: 1px solid $primaryAccentColor; + color: $fgColor; + border-radius: 4px; + transition: background-color 0.4s, color 0.4s; + } + + input[type=button]:hover, input[type=submit]:hover { + background-color: $primaryAccentColor; + color: $fgColorAlt; + } } diff --git a/src/components/tables/invoice-item-table.js b/src/components/tables/invoice-item-table.js new file mode 100644 index 0000000..6b0a1a5 --- /dev/null +++ b/src/components/tables/invoice-item-table.js @@ -0,0 +1,85 @@ +/* OpenBills-web - Web based libre billing software + * Copyright (C) 2022 Vidhu Kant Sharma <vidhukant@vidhukant.xyz> + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import './scss/table.scss'; +import { deleteItem } from './../../classes/item'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faPencil, faTrashCan } from '@fortawesome/free-solid-svg-icons' + +const ItemTable = ({items, setItems}) => { + const handleEdit = (i) => { + alert("coming soon; please delete and add item again"); + } + + const handleDelete = (item) => { + setItems(items.filter(i => i.Id !== item.Id)); + } + + /* TODO: all the math should be done here + * i.e CGST, IGST, total price (i.e price x quantity) + * discount/gst value (i.e number instead of percentage) + * + * the total cost and the like may be handled by + * parent component since they are needed + * by other things (probably) + * + * all the values will be calculated on runtime. + * the database will only store the unit price + * and gst/discount *percentages* and everything else + * will be calculated on runtime. i.e the + * database only store info required to re-produce + * those same values shown to the user while creating the invoice + */ + return ( + <table> + <thead> + <tr> + <th>S. No</th> + <th>Name</th> + <th>Description</th> + <th>Brand Name</th> + <th>UOM</th> + <th>HSN</th> + <th>Unit Price</th> + <th>GST %</th> + {/* TODO: CGST, IGST, etc */} + <th></th> + </tr> + </thead> + <tbody> + {items && items.map((i, id=id+1) => ( + <tr key={id}> + <td>{id+1}</td> + <td className={i.Name === "" ? "empty" : ""}>{i.Name}</td> + <td className={i.Description === "" ? "empty" : ""}>{i.Description}</td> + <td className={i.Brand.Name === "" ? "empty" : ""}>{i.Brand.Name}</td> + <td className={i.UnitOfMeasure === "" ? "empty" : ""}>{i.UnitOfMeasure}</td> + <td className={i.HSN === "" ? "empty" : ""}>{i.HSN}</td> + <td className={i.UnitPrice === 0.0 ? "empty" : ""}>{i.UnitPrice}</td> + <td className={i.GSTPercentage === 0.0 ? "empty" : ""}>{i.GSTPercentage}</td> + <td className={"buttons"}> + <FontAwesomeIcon icon={faPencil} onClick={() => handleEdit(i)}/> + <FontAwesomeIcon icon={faTrashCan} onClick={() => handleDelete(i)}/> + </td> + </tr> + ))} + </tbody> + </table> + ); +} + +export default ItemTable; diff --git a/src/views/invoice/new.js b/src/views/invoice/new.js index 57f3094..70bc545 100644 --- a/src/views/invoice/new.js +++ b/src/views/invoice/new.js @@ -17,6 +17,7 @@ import ClientPicker from '../../components/pickers/client-picker'; import ItemPicker from '../../components/pickers/item-picker'; +import ItemTable from '../../components/tables/invoice-item-table'; import { InvoiceClient } from '../../classes/client'; @@ -25,6 +26,7 @@ import { useState, useEffect } from 'react'; const NewInvoicePage = () => { const [client, setClient] = useState(new InvoiceClient()); const [shippingAddressId, setShippingAddressId] = useState(-1); + const [items, setItems] = useState([]); useEffect(() => { setShippingAddressId(-1); @@ -37,7 +39,12 @@ const NewInvoicePage = () => { setClient={setClient} shippingAddressId={shippingAddressId} setShippingAddressId={setShippingAddressId}/> - <ItemPicker/> + <ItemPicker + invoiceItems={items} + addInvoiceItem={(item) => setItems(prev => [...prev, item])} /> + <ItemTable + items={items} + setItems={setItems} /> </> ); } |