aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVidhu Kant Sharma <vidhukant@vidhukant.xyz>2022-10-10 22:45:28 +0530
committerVidhu Kant Sharma <vidhukant@vidhukant.xyz>2022-10-10 22:45:28 +0530
commit0b4343bed2cace86552929f25202680c0d99c541 (patch)
treef5eb25902a401ff98827aed68377c7f221097f75
parent6dd449f6f38e8afdee7638b56f24e61953854eda (diff)
added ItemTable for the invoice page
-rw-r--r--src/components/pickers/item-picker.js56
-rw-r--r--src/components/pickers/scss/_picker.scss38
-rw-r--r--src/components/pickers/scss/item-picker.scss35
-rw-r--r--src/components/tables/invoice-item-table.js85
-rw-r--r--src/views/invoice/new.js9
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} />
</>
);
}