aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/_colors.scss7
-rw-r--r--src/_styles.scss2
-rw-r--r--src/classes/client.js6
-rw-r--r--src/components/editors/address-editor.js41
-rw-r--r--src/components/editors/client-editor.js82
-rw-r--r--src/components/editors/contact-editor.js41
-rw-r--r--src/components/editors/item-editor.js20
-rw-r--r--src/components/editors/multi-address-editor.js62
-rw-r--r--src/components/editors/scss/_colors.scss14
-rw-r--r--src/components/editors/scss/client-editor.scss12
-rw-r--r--src/components/tables/client-table.js4
-rw-r--r--src/components/tables/scss/_colors.scss16
-rw-r--r--src/views/manage/clients.js25
13 files changed, 212 insertions, 120 deletions
diff --git a/src/_colors.scss b/src/_colors.scss
index 12e4b92..ced67aa 100644
--- a/src/_colors.scss
+++ b/src/_colors.scss
@@ -16,10 +16,17 @@
*/
$primaryAccentColor: #bd93f9;
+$secondaryAccentColor: #d0afff;
+
$backgroundColor: #282a36;
$linkColor: $primaryAccentColor;
$white: #f8f8f2;
+$gray: lightgray;
$black: black;
+$warningColor: #ed4683;
+
$fgColor: $white;
+$darkFgColor: $black;
+$disabledColor: $gray;
diff --git a/src/_styles.scss b/src/_styles.scss
index 3faed12..064b9c6 100644
--- a/src/_styles.scss
+++ b/src/_styles.scss
@@ -33,7 +33,7 @@
backdrop-filter: blur(2px);
.floating-window {
width: 90%;
- max-width: 1000px;
+ max-width: 1200px;
z-index: 6;
}
}
diff --git a/src/classes/client.js b/src/classes/client.js
index 20e0524..6785c90 100644
--- a/src/classes/client.js
+++ b/src/classes/client.js
@@ -64,3 +64,9 @@ export const getAllClients = (ok, fail) => {
.then(res => ok(res.data))
.catch(err => fail(err))
}
+
+export const editClient = (client, ok, fail) => {
+ axios.put(`/client/${client.Id}`, client)
+ .then(res => ok())
+ .catch(err => fail())
+}
diff --git a/src/components/editors/address-editor.js b/src/components/editors/address-editor.js
index bd81b6f..5fb8ff6 100644
--- a/src/components/editors/address-editor.js
+++ b/src/components/editors/address-editor.js
@@ -18,29 +18,19 @@
import { Address } from './../../classes/client';
import './scss/address-editor.scss';
+import { useState, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faXmark } from '@fortawesome/free-solid-svg-icons';
-const AddressEditor = (props) => {
- const handleInput = (field, event) => {
- const a = new Address();
- const val = event.target.value;
- a.Country = field === "country" ? val : props.address.Country;
- a.State = field === "state" ? val : props.address.State;
- a.City = field === "city" ? val : props.address.City;
- a.Text = field === "address" ? val : props.address.Text;
- a.PostalCode = field === "postal" ? val : props.address.PostalCode;
- props.setAddress(a);
- }
-
+const AddressEditor = ({ heading, address, setAddress, isBillingAddress, billingAddressIsShipping, setShipToBillingAddress, deleteAddress}) => {
return (
<div className={"address-editor"}>
- <p className={"heading"}>{props.heading}</p>
- {props.isBillingAddress || // cross button
+ <p className={"heading"}>{heading}</p>
+ {isBillingAddress || // cross button
<FontAwesomeIcon
icon={faXmark}
className={"remove-button"}
- onClick={props.deleteAddress}/>
+ onClick={deleteAddress}/>
}
<div className={"labels-wrapper"}>
@@ -49,21 +39,24 @@ const AddressEditor = (props) => {
Country:
<input
type="text" name="name"
- value={props.address.Country} onChange={(e) => handleInput("country", e)} />
+ value={address.Country}
+ onChange={(e) => setAddress({Country: e.target.value})} />
</label>
<label>
State:
<input
type="text" name="name"
- value={props.address.State} onChange={(e) => handleInput("state", e)} />
+ value={address.State}
+ onChange={(e) => setAddress({State: e.target.value})} />
</label>
<label>
City:
<input
type="text" name="name"
- value={props.address.City} onChange={(e) => handleInput("city", e)} />
+ value={address.City}
+ onChange={(e) => setAddress({City: e.target.value})} />
</label>
</div>
@@ -72,23 +65,25 @@ const AddressEditor = (props) => {
Address:
<textarea
type="text" name="name"
- value={props.address.Text} onChange={(e) => handleInput("address", e)} />
+ value={address.Text}
+ onChange={(e) => setAddress({Text: e.target.value})} />
</label>
<label>
Postal Code:
<input
type="text" name="name"
- value={props.address.PostalCode} onChange={(e) => handleInput("postal", e)} />
+ value={address.PostalCode}
+ onChange={(e) => setAddress({PostalCode: e.target.value})} />
</label>
</div>
</div>
- {props.isBillingAddress &&
+ {isBillingAddress &&
<label className={"checkbox-label"}>
<input
type="checkbox"
- checked={props.billingAddressIsShipping}
- onChange={() => props.setShipToBillingAddress(!props.billingAddressIsShipping)} />
+ checked={billingAddressIsShipping}
+ onChange={() => setShipToBillingAddress(!billingAddressIsShipping)} />
Shipping address same as billing address
</label>
}
diff --git a/src/components/editors/client-editor.js b/src/components/editors/client-editor.js
index bd018f4..e65ab8f 100644
--- a/src/components/editors/client-editor.js
+++ b/src/components/editors/client-editor.js
@@ -15,7 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { Client, saveClient, Contact, Address } from './../../classes/client';
+import { Client, saveClient, editClient, Contact, Address } from './../../classes/client';
+import MultiAddressEditor from './multi-address-editor';
import AddressEditor from './address-editor';
import ContactEditor from './contact-editor';
import './scss/client-editor.scss';
@@ -23,12 +24,16 @@ import './scss/client-editor.scss';
import { useState, useEffect } from 'react';
const ClientEditor = (props) => {
- const [name, setName] = useState("");
- const [GSTIN, setGSTIN] = useState([]);
- const [contact, setContact] = useState(new Contact());
- const [billingAddress, setBillingAddress] = useState(new Address());
- const [shippingAddresses, setShippingAddresses] = useState([]);
- const [shipToBillingAddress, setShipToBillingAddress] = useState(true);
+ const [name, setName] = useState(props.client.Name);
+ const [GSTIN, setGSTIN] = useState(props.client.GSTIN);
+ const [contact, setContact] = useState(props.client.Contact);
+ const [billingAddress, setBillingAddress] = useState(props.client.BillingAddress);
+ const [shippingAddresses, setShippingAddresses] = useState(props.client.ShippingAddresses);
+ const [shipToBillingAddress, setShipToBillingAddress] = useState(
+ props.client.ShippingAddresses.length === 1
+ ? JSON.stringify(props.client.ShippingAddresses[0]) === JSON.stringify(props.client.BillingAddress)
+ : props.client.ShippingAddresses.length === 0
+ );
useEffect(() => {
// will delete existing shipping addresses if false
@@ -47,13 +52,19 @@ const ClientEditor = (props) => {
? [billingAddress]
: shippingAddresses
- // TODO: Save is for new items. implement modification too
- saveClient(client, handleSuccess, handleFail);
+ // remove blank phone numbers/email addresses
+ client.Contact.Phones = client.Contact.Phones.filter(i => i !== "");
+ client.Contact.Emails = client.Contact.Emails.filter(i => i !== "");
+
+ props.editing
+ ? editClient(client, handleSuccess, handleFail)
+ : saveClient(client, handleSuccess, handleFail);
}
const handleSuccess = () => {
clearAll();
props.successCallback();
+ props.editing && props.hide();
}
const handleFail = (err) => {
@@ -71,33 +82,12 @@ const ClientEditor = (props) => {
}
const handleCancel = () => {
- // TODO: hide this component or something
clearAll();
- }
-
- const handleShippingAddressUpdate = (id, data) => {
- setShippingAddresses([
- ...shippingAddresses.slice(0, id),
- data,
- ...shippingAddresses.slice(id+1)
- ]);
- }
-
- const handleShippingAddressDelete = (id) => {
- // deleting the last address sets
- // shipToBillingAddress to true
- if (shippingAddresses.length === 1) {
- setShipToBillingAddress(true);
- } else {
- setShippingAddresses([
- ...shippingAddresses.slice(0, id),
- ...shippingAddresses.slice(id+1)
- ]);
- }
+ props.editing && props.hide();
}
return (
- <div className={"editor-wrapper"}>
+ <div className={`editor-wrapper ${props.className ? props.className : ''}`}>
<p>{props.heading}</p>
<form onSubmit={handleSubmit} className={"editor client-editor"}>
<div className={"top"}>
@@ -122,23 +112,21 @@ const ClientEditor = (props) => {
setContact={setContact} />
<AddressEditor
heading={"Billing Address"}
+ address={billingAddress}
+ setAddress={(data) => setBillingAddress(prev => ({...prev, ...data}))}
isBillingAddress={true}
billingAddressIsShipping={shipToBillingAddress}
- setShipToBillingAddress={setShipToBillingAddress}
- address={billingAddress}
- setAddress={setBillingAddress} />
-
- {shippingAddresses.length > 0 && shippingAddresses.map((i, id) =>
- <AddressEditor
- key={id}
- heading={`Shipping Address ${shippingAddresses.length === 1 ? '' : id + 1}`}
- address={i}
- deleteAddress={() => handleShippingAddressDelete(id)}
- setAddress={(data) => handleShippingAddressUpdate(id, data)} />
- )}
-
- <span className={`buttons ${shippingAddresses.length > 0 ? 'wide' : ''}`}>
- {shippingAddresses.length > 0 &&
+ setShipToBillingAddress={setShipToBillingAddress} />
+
+ {shipToBillingAddress ||
+ <MultiAddressEditor
+ addresses={shippingAddresses}
+ setAddresses={setShippingAddresses}
+ setShipToBillingAddress={setShipToBillingAddress} />
+ }
+
+ <span className={`buttons ${shipToBillingAddress ? '' : 'wide'}`}>
+ {shipToBillingAddress ||
<input
className={"wide-button"}
type="button"
diff --git a/src/components/editors/contact-editor.js b/src/components/editors/contact-editor.js
index f689b8c..99baeca 100644
--- a/src/components/editors/contact-editor.js
+++ b/src/components/editors/contact-editor.js
@@ -16,62 +16,49 @@
*/
import { Contact } from './../../classes/client';
+import { useState, useEffect } from 'react';
import './scss/contact-editor.scss';
-const ContactEditor = (props) => {
- const handleInput = (field, event) => {
- const c = new Contact();
- const val = event.target.value;
- c.Name = field === "name" ? val : props.contact.Name;
- c.Website = field === "website" ? val : props.contact.Website;
- c.Phones = field === "phones"
- ? (val.length === 0 ? [] : val.split("\n"))
- : props.contact.Phones;
- c.Emails = field === "emails"
- ? (val.length === 0 ? [] : val.split("\n"))
- : props.contact.Emails;
- props.setContact(c);
- }
+const ContactEditor = ({ heading, contact, setContact }) => {
+ const splitMultiline = (value) => value.split("\n")
+ .filter((i, id, arr) => id + 1 === arr.length || i !== "");
+
+ const handleInput = (data) =>
+ setContact(prev => ({...prev, ...data}));
return (
<div className={"contact-editor"}>
- <p className={"heading"}>{props.heading}</p>
+ <p className={"heading"}>{heading}</p>
<div className={"labels-wrapper"}>
<label>
Contact Name:
<input
type="text" name="name"
- value={props.contact.Name} onChange={(e) => handleInput("name", e)} />
+ value={contact.Name} onChange={(e) => handleInput({Name: e.target.value})} />
</label>
<label>
Website:
<input
type="text" name="name"
- value={props.contact.Website} onChange={(e) => handleInput("website", e)} />
+ value={contact.Website} onChange={(e) => handleInput({Website: e.target.value})} />
</label>
<label>
Phone:
<textarea
type="text" name="name"
- value={props.contact.Phones.length > 0
- ? props.contact.Phones.forEach(i => i)
- : ""
- }
- onChange={(e) => handleInput("phones", e)} />
+ value={contact.Phones.join('\n')}
+ onChange={(e) => handleInput({Phones: splitMultiline(e.target.value)})} />
</label>
<label>
E-mail:
<textarea
type="text" name="name"
- value={props.contact.Emails.length > 0
- ? props.contact.Emails.forEach(i => i)
- : ""
- }
- onChange={(e) => handleInput("emails", e)} />
+ value={contact.Emails.join('\n')}
+ onChange={(e) => handleInput({Emails: splitMultiline(e.target.value)})} />
</label>
</div>
</div>
diff --git a/src/components/editors/item-editor.js b/src/components/editors/item-editor.js
index 94c6137..eb6e8c1 100644
--- a/src/components/editors/item-editor.js
+++ b/src/components/editors/item-editor.js
@@ -43,6 +43,18 @@ const ItemEditor = (props) => {
const handleSubmit = (e) => {
e.preventDefault();
+ const minQuantity = parseFloat(minQty)
+ const maxQuantity = parseFloat(maxQty)
+
+ // show error if minQty > maxQty
+ if (maxQuantity > 0) {
+ if (minQuantity > maxQuantity) {
+ // TODO: handle error
+ console.log("shiat")
+ return
+ }
+ }
+
const item = new Item();
item.Id = props.item.Id;
item.Name = name;
@@ -51,8 +63,8 @@ const ItemEditor = (props) => {
item.UnitOfMeasure = unit;
item.UnitPrice = parseFloat(unitPrice);
item.GSTPercentage = parseFloat(gstP);
- item.MinQuantity = parseFloat(minQty);
- item.MaxQuantity = parseFloat(maxQty);
+ item.MinQuantity = minQuantity;
+ item.MaxQuantity = maxQuantity;
item.Brand = brand;
// TODO: Save is for new items. implement modification too
@@ -151,7 +163,7 @@ const ItemEditor = (props) => {
Minimum Quantity:
<input
type="number"
- value={minQty}
+ value={minQty == "0" ? "" : minQty}
min="0"
onChange={(e) => setMinQty(e.target.value)} />
</label>
@@ -160,7 +172,7 @@ const ItemEditor = (props) => {
Maximum Quantity:
<input
type="number"
- value={maxQty}
+ value={maxQty == "0" ? "" : maxQty}
min="0"
onChange={(e) => setMaxQty(e.target.value)} />
</label>
diff --git a/src/components/editors/multi-address-editor.js b/src/components/editors/multi-address-editor.js
new file mode 100644
index 0000000..a0182f2
--- /dev/null
+++ b/src/components/editors/multi-address-editor.js
@@ -0,0 +1,62 @@
+/* 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 { Address } from './../../classes/client';
+import AddressEditor from './address-editor';
+
+import { useState, useEffect } from 'react';
+
+const MultiAddressEditor = ({addresses, setAddresses, setShipToBillingAddress}) => {
+ const handleChange = (id, data) => {
+ console.log(id, data)
+
+ const newAddresses = [...addresses];
+ newAddresses[id] = {
+ ...newAddresses[id], ...data
+ }
+
+ setAddresses(newAddresses)
+ }
+
+ const handleDelete = (id) => {
+ // deleting the last address sets
+ // shipToBillingAddress to true
+ if (addresses.length === 1) {
+ setShipToBillingAddress(true);
+ } else {
+ setAddresses([
+ ...addresses.slice(0, id),
+ ...addresses.slice(id+1)
+ ]);
+ }
+ }
+
+ return (
+ <>
+ {addresses.map((i, id) =>
+ <AddressEditor
+ key={id}
+ heading={`Shipping Address ${addresses.length === 1 ? '' : id + 1}`}
+ address={i}
+ deleteAddress={() => handleDelete(id)}
+ setAddress={(data) => {handleChange(id, data)}} />
+ )}
+ </>
+ );
+}
+
+export default MultiAddressEditor;
diff --git a/src/components/editors/scss/_colors.scss b/src/components/editors/scss/_colors.scss
index 994dcf7..bf47f75 100644
--- a/src/components/editors/scss/_colors.scss
+++ b/src/components/editors/scss/_colors.scss
@@ -15,12 +15,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-$primaryAccentColor: #bd93f9;
-$secondaryAccentColor: #d0afff;
+@import "../../../colors";
-$fgColor: white;
-$fgColorAlt: black;
+$primaryAccentColor: $primaryAccentColor;
+$secondaryAccentColor: $secondaryAccentColor;
-$inputBackgroundColor: #00000000;
+$fgColor: $fgColor;
+$fgColorAlt: $black;
-$warningColor: #ed4683;
+$inputBackgroundColor: $backgroundColor;
+
+$warningColor: $warningColor;
diff --git a/src/components/editors/scss/client-editor.scss b/src/components/editors/scss/client-editor.scss
index 30919fd..2a2bfef 100644
--- a/src/components/editors/scss/client-editor.scss
+++ b/src/components/editors/scss/client-editor.scss
@@ -16,6 +16,7 @@
*/
@import "editor";
+@import "../../../colors";
.client-editor {
max-width: 90%;
@@ -33,3 +34,14 @@
}
}
}
+
+.floating-window {
+ overflow: scroll;
+ max-height: 100%;
+ .buttons {
+ position: fixed;
+ bottom: 1rem;
+ background-color: $backgroundColor;
+ padding: 1rem auto;
+ }
+}
diff --git a/src/components/tables/client-table.js b/src/components/tables/client-table.js
index ffbf5e8..d3b5c25 100644
--- a/src/components/tables/client-table.js
+++ b/src/components/tables/client-table.js
@@ -19,8 +19,8 @@ import './scss/client-table.scss';
import { deleteClient } from './../../classes/client';
const ClientTable = (props) => {
- const handleEdit = (i) => {
- props.setItemToEdit(i)
+ const handleEdit = (c) => {
+ props.setClientToEdit(c)
}
const handleDelete = (c) => {
diff --git a/src/components/tables/scss/_colors.scss b/src/components/tables/scss/_colors.scss
index a4a8ba4..2a3725c 100644
--- a/src/components/tables/scss/_colors.scss
+++ b/src/components/tables/scss/_colors.scss
@@ -15,18 +15,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-$primaryAccentColor: #bd93f9;
-$secondaryAccentColor: #d0afff;
+@import "../../../colors";
+
+$primaryAccentColor: $primaryAccentColor;
+$secondaryAccentColor: $secondaryAccentColor;
$linkColor: $primaryAccentColor;
-$fgColor: white;
-$fgColorAlt: black;
-$fgColorDisabled: lightgray;
+$fgColor: $fgColor;
+$fgColorAlt: $darkFgColor;
+$fgColorDisabled: $disabledColor;
-$inputBackgroundColor: #00000000;
+$inputBackgroundColor: $backgroundColor;
-$warningColor: #ed4683;
+$warningColor: $warningColor;
@mixin button {
input[type=button] {
diff --git a/src/views/manage/clients.js b/src/views/manage/clients.js
index 9d2209b..8445d80 100644
--- a/src/views/manage/clients.js
+++ b/src/views/manage/clients.js
@@ -22,11 +22,12 @@
import { useState, useEffect } from 'react';
import './scss/management-page.scss';
-import { getAllClients } from '../../classes/client';
+import { Client, getAllClients } from '../../classes/client';
import ClientEditor from './../../components/editors/client-editor';
import ClientTable from './../../components/tables/client-table';
const ManageClientsPage = () => {
+ const [clientToEdit, setClientToEdit] = useState(new Client());
const [allClients, setAllClients] = useState([]);
// TODO: handle error
const updateList = () =>
@@ -38,9 +39,27 @@ const ManageClientsPage = () => {
return (
<>
- <ClientEditor heading={"Add New Client"} successCallback={updateList}/>
+ <ClientEditor
+ heading={"Add New Client"}
+ client={new Client()}
+ successCallback={updateList}/>
<hr/>
- <ClientTable refresh={updateList} clients={allClients}/>
+ <ClientTable
+ refresh={updateList}
+ clients={allClients}
+ setClientToEdit={setClientToEdit}/>
+
+ {JSON.stringify(clientToEdit) !== JSON.stringify(new Client()) &&
+ <div className={"floating-wrapper"}>
+ <ClientEditor
+ className={"floating-window"}
+ heading={`Edit ${clientToEdit.Name ? clientToEdit.Name : 'Client'}:`}
+ client={clientToEdit}
+ hide={() => setClientToEdit(new Client())}
+ editing={true}
+ callback={updateList}/>
+ </div>
+ }
</>
);
}