aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVidhu Kant Sharma <vidhukant@vidhukant.xyz>2022-10-05 22:16:53 +0530
committerVidhu Kant Sharma <vidhukant@vidhukant.xyz>2022-10-05 22:16:53 +0530
commita1534066073f3197623565d597e5126a5c01bff9 (patch)
tree01eef2bee4a4b9f01ac60b36949aa7624dbfd54c
parent94b86f4c333f1991c2d09c8ce02beaedf5abc721 (diff)
added client picker
-rw-r--r--public/index.html29
-rw-r--r--src/App.js4
-rw-r--r--src/classes/client.js7
-rw-r--r--src/components/pickers/client-picker.js145
-rw-r--r--src/components/pickers/item-picker.js22
-rw-r--r--src/components/pickers/scss/_colors.scss28
-rw-r--r--src/components/pickers/scss/_picker.scss105
-rw-r--r--src/components/pickers/scss/client-picker.scss47
-rw-r--r--src/components/tables/client-table.js4
-rw-r--r--src/views/homepage.js4
-rw-r--r--src/views/invoice/new.js45
11 files changed, 410 insertions, 30 deletions
diff --git a/public/index.html b/public/index.html
index aa069f2..1777176 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4,40 +4,17 @@
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
- <meta name="theme-color" content="#000000" />
+ <meta name="theme-color" content="#232627" />
<meta
name="description"
- content="Web site created using create-react-app"
+ content="WebUI for OpenBills - Libre Billing Software"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
- <!--
- manifest.json provides metadata used when your web app is installed on a
- user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
- -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
- <!--
- Notice the use of %PUBLIC_URL% in the tags above.
- It will be replaced with the URL of the `public` folder during the build.
- Only files inside the `public` folder can be referenced from the HTML.
-
- Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
- work correctly both with client-side routing and a non-root public URL.
- Learn how to configure a non-root public URL by running `npm run build`.
- -->
- <title>React App</title>
+ <title>OpenBills</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
- <!--
- This HTML file is a template.
- If you open it directly in the browser, you will see an empty page.
-
- You can add webfonts, meta tags, or analytics to this file.
- The build step will place the bundled scripts into the <body> tag.
-
- To begin the development, run `npm start` or `yarn start`.
- To create a production bundle, use `npm run build` or `yarn build`.
- -->
</body>
</html>
diff --git a/src/App.js b/src/App.js
index ffd2344..1938304 100644
--- a/src/App.js
+++ b/src/App.js
@@ -19,6 +19,7 @@ import { BrowserRouter, Route, Routes } from "react-router-dom";
import './App.scss';
import Navbar from './components/navbar/navbar';
import HomePage from './views/homepage';
+import NewInvoicePage from './views/invoice/new';
import ManagementPage from './views/manage/manage';
import ManageItemsPage from './views/manage/items';
import ManageClientsPage from './views/manage/clients';
@@ -31,10 +32,11 @@ const App = () => {
<main>
<Routes>
<Route exact path="/" element={<HomePage/>}/>
- <Route exact path="/manage" element={<ManagementPage/>}/>
+ <Route exact path="/invoice/new" element={<NewInvoicePage/>}/>
<Route exact path="/manage/items" element={<ManageItemsPage/>}/>
<Route exact path="/manage/clients" element={<ManageClientsPage/>}/>
<Route exact path="/manage/brands" element={<ManageBrandsPage/>}/>
+ <Route exact path="/manage" element={<ManagementPage/>}/>
<Route path="*" element={<h1>404</h1>}/>
</Routes>
</main>
diff --git a/src/classes/client.js b/src/classes/client.js
index 842c5e5..d05655d 100644
--- a/src/classes/client.js
+++ b/src/classes/client.js
@@ -47,6 +47,13 @@ export class Client {
}
}
+export class InvoiceClient extends Client {
+ constructor() {
+ super();
+ this.ShipTo = new Address();
+ }
+}
+
export const saveClient = (item, ok, fail) => {
axios.post("/client/new", item)
.then(res => ok(res))
diff --git a/src/components/pickers/client-picker.js b/src/components/pickers/client-picker.js
new file mode 100644
index 0000000..bca8566
--- /dev/null
+++ b/src/components/pickers/client-picker.js
@@ -0,0 +1,145 @@
+/* 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 { Client, InvoiceClient, getAllClients, Address } from '../../classes/client';
+import './scss/client-picker.scss';
+
+import { useState, useEffect } from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faPhone, faEnvelope, faGlobe } from '@fortawesome/free-solid-svg-icons'
+
+const ClientPicker = ({ client, setClient, shippingAddressId, setShippingAddressId }) => {
+ const [clients, setClients] = useState([new Client()]);
+
+ useEffect(() => refreshClients, []);
+
+ const refreshClients = () =>
+ getAllClients(setClients, () => {});
+
+ const handleClientSelect = (e) => {
+ const c = clients.filter(i => i.Id === e.target.value )[0];
+ setClient(c ? c : new InvoiceClient());
+ }
+
+ const shouldShowAddressPicker = () => {
+ if (client.Id !== null) {
+ if (client.ShippingAddresses.length > 0) {
+ // if the only address is same as billing address, dont show
+ if (client.ShippingAddresses.length === 1) {
+ return JSON.stringify(client.ShippingAddresses[0]) !==
+ JSON.stringify(client.BillingAddress);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ const formatAddress = (addr) =>
+ `${addr.Text.length > 30 ? addr.Text.substring(0, 30) + "..." : addr.Text} | ${addr.City}`;
+
+ const formatClientName = (client) =>
+ `${client.Name.length > 30 ? client.Name.substring(0, 30) + "..." : client.Name} | ${client.BillingAddress.City}`;
+
+ return (
+ <div className={"picker-wrapper"}>
+ <p className={"heading"}>Invoice Recipient</p>
+ <div className={"client-picker"}>
+ <div className={"options"}>
+ {clients && clients.length > 0 &&
+ <>
+ <label>
+ Client Name:
+ <select value={client.Id ? client.Id : ""} onChange={handleClientSelect}>
+ <option key="placeholder" value={""} disabled>Select A Client</option>
+ {clients.map(i => <option key={i.Id} value={i.Id}>{formatClientName(i)}</option>)}
+ </select>
+ </label>
+
+ {shouldShowAddressPicker() &&
+ <label>
+ Ship To:
+ <select value={shippingAddressId} onChange={(e) => setShippingAddressId(parseInt(e.target.value))} >
+ <option key="placeholder" value={-1} disabled>Select A Shipping Address</option>
+ {client.ShippingAddresses.map((i, id) => <option key={i.Text} value={id}>{formatAddress(i)}</option>)}
+ </select>
+ </label>
+ }
+ <p>GSTIN: {client.GSTIN === "" ? "URP" : client.GSTIN}</p>
+ </>
+ }
+ </div>
+
+ <div className={"contact-info"}>
+ <p>
+ Name: {client.Contact.Name}<br/>
+ {client.Contact.Phones.length > 0 &&
+ <>
+ <FontAwesomeIcon icon={faPhone} className={"icon"}/>
+ {client.Contact.Phones.map((i, id) => <a key={`${i}-${id}`} href={`tel:${i}`}>{` ${i}${id + 1 === client.Contact.Phones.length ? '' : ','}`}</a>)}
+ <br/>
+ </>
+ }
+ {client.Contact.Emails.length > 0 &&
+ <>
+ <FontAwesomeIcon icon={faEnvelope} className={"icon"}/>
+ {client.Contact.Emails.map((i, id) => <a key={`${i}-${id}`} href={`mailto:${i}`}>{` ${i}${id + 1 === client.Contact.Emails.length ? '' : ','}`}</a>)}
+ <br/>
+ </>
+ }
+ {client.Contact.Website.length > 0 &&
+ <>
+ <FontAwesomeIcon icon={faGlobe} className={"icon"}/> <a
+ href={`${(client.Contact.Website.startsWith("https://")
+ || client.Contact.Website.startsWith("http://"))
+ ? client.Contact.Website : 'https://' + client.Contact.Website}`}
+ target="noreferrer noopener"
+ >
+ {client.Contact.Website}
+ </a>
+ </>
+ }
+ </p>
+ </div>
+
+ {client.Id !== null && // if client is selected
+ <>
+ <div className={"billing-address"}>
+ <p className={"multiline"}>
+ <strong>Billing Address: </strong><br/>
+ {client.BillingAddress.Text} <br/>
+ {client.BillingAddress.City}, {client.BillingAddress.State} - {client.BillingAddress.PostalCode} ({client.BillingAddress.Country})
+ </p>
+ </div>
+ {shouldShowAddressPicker() && shippingAddressId >= 0 &&
+ <div className={"shipping-address"}>
+ <p className={"multiline"}>
+ <strong>Shipping Address: </strong><br/>
+ {client.ShippingAddresses[shippingAddressId].Text}<br/>
+ {client.ShippingAddresses[shippingAddressId].City}, {client.ShippingAddresses[shippingAddressId].State} - {client.ShippingAddresses[shippingAddressId].PostalCode} ({client.ShippingAddresses[shippingAddressId].Country})
+ </p>
+ </div>
+ }
+ </>
+ }
+ </div>
+ <hr/>
+ </div>
+ );
+}
+
+export default ClientPicker;
diff --git a/src/components/pickers/item-picker.js b/src/components/pickers/item-picker.js
new file mode 100644
index 0000000..8353dbb
--- /dev/null
+++ b/src/components/pickers/item-picker.js
@@ -0,0 +1,22 @@
+/* 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/>.
+ */
+
+const ItemPicker = () => {
+
+}
+
+export default ItemPicker;
diff --git a/src/components/pickers/scss/_colors.scss b/src/components/pickers/scss/_colors.scss
new file mode 100644
index 0000000..bf47f75
--- /dev/null
+++ b/src/components/pickers/scss/_colors.scss
@@ -0,0 +1,28 @@
+/* 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 "../../../colors";
+
+$primaryAccentColor: $primaryAccentColor;
+$secondaryAccentColor: $secondaryAccentColor;
+
+$fgColor: $fgColor;
+$fgColorAlt: $black;
+
+$inputBackgroundColor: $backgroundColor;
+
+$warningColor: $warningColor;
diff --git a/src/components/pickers/scss/_picker.scss b/src/components/pickers/scss/_picker.scss
new file mode 100644
index 0000000..625a1e3
--- /dev/null
+++ b/src/components/pickers/scss/_picker.scss
@@ -0,0 +1,105 @@
+/* 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 "colors";
+
+@mixin label {
+ label {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: center;
+ max-width: 24rem;
+ width: 90%;
+ padding: 0.3rem 0.1rem;
+ border-bottom: 1px dotted $secondaryAccentColor;
+
+ select,input {
+ padding: 0.2rem;
+ max-width: 13rem;
+ width: 100%;
+ box-sizing: border-box;
+ background-color: $inputBackgroundColor;
+ border: 1px solid $primaryAccentColor;
+ color: $fgColor;
+ border-radius: 4px;
+ outline: 0;
+ font-size: 0.8rem;
+ }
+ textarea {
+ background-color: $inputBackgroundColor;
+ color: $fgColor;
+ outline: 0;
+ border: 1px solid $primaryAccentColor;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 0.8rem;
+ }
+ }
+}
+
+@mixin picker-wrapper {
+ width: 100%;
+ margin: auto;
+ padding: 0;
+ margin: 0;
+ position: relative;
+ p.heading {
+ text-align: center;
+ font-weight: bold;
+ font-size: 1.1rem;
+ 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/client-picker.scss b/src/components/pickers/scss/client-picker.scss
new file mode 100644
index 0000000..e1d3b13
--- /dev/null
+++ b/src/components/pickers/scss/client-picker.scss
@@ -0,0 +1,47 @@
+/* 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 "picker";
+
+
+.picker-wrapper {
+ @include picker-wrapper;
+ @include label;
+}
+
+.client-picker {
+ display: flex;
+ justify-content: space-evenly;
+ .options {
+ width: 27rem;
+ }
+ .billing-address,
+ .shipping-address,
+ .contact-info {
+ p { margin: 0 }
+ max-width: 24rem;
+ margin: 0 1rem;
+ }
+ .contact-info {
+ .icon {
+ font-size: 0.9rem;
+ }
+ }
+ .multiline {
+ white-space: pre-line;
+ }
+}
diff --git a/src/components/tables/client-table.js b/src/components/tables/client-table.js
index eb13308..cea612d 100644
--- a/src/components/tables/client-table.js
+++ b/src/components/tables/client-table.js
@@ -70,7 +70,7 @@ const ClientTable = (props) => {
</div>
<div className={"billing-address"}>
- <p className={"heading"}><strong>Billing Address: </strong></p>
+ <p><strong>Billing Address: </strong></p>
<p className={"multiline"}>{i.BillingAddress.Text}</p>
<p>{i.BillingAddress.City}, {i.BillingAddress.State} - {i.BillingAddress.PostalCode} ({i.BillingAddress.Country})</p>
</div>
@@ -79,7 +79,7 @@ const ClientTable = (props) => {
<div className={"shipping-addresses"}>
{i.ShippingAddresses.map((j, id) =>
<div key={id} className={"shipping-address"}>
- <p><strong>{`Shipping Address ${i.ShippingAddresses.length === 1 ? '' : id + 1}`}</strong></p>
+ <p><strong>{`Shipping Address ${i.ShippingAddresses.length === 1 ? '' : id + 1}:`}</strong></p>
<p className={"multiline"}>{j.Text}</p>
<p>{j.City}, {j.State} - {j.PostalCode} ({j.Country})</p>
</div>
diff --git a/src/views/homepage.js b/src/views/homepage.js
index fe32136..7ed89a0 100644
--- a/src/views/homepage.js
+++ b/src/views/homepage.js
@@ -15,11 +15,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import { Link } from 'react-router-dom';
+
const HomePage = () => {
return (
<>
<h1>Welcome to OpenBills</h1>
- <p>Check out <a href="/manage">/manage</a></p>
+ <p>Check out <Link to="/manage">/manage</Link></p>
</>
);
}
diff --git a/src/views/invoice/new.js b/src/views/invoice/new.js
new file mode 100644
index 0000000..57f3094
--- /dev/null
+++ b/src/views/invoice/new.js
@@ -0,0 +1,45 @@
+/* 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 ClientPicker from '../../components/pickers/client-picker';
+import ItemPicker from '../../components/pickers/item-picker';
+
+import { InvoiceClient } from '../../classes/client';
+
+import { useState, useEffect } from 'react';
+
+const NewInvoicePage = () => {
+ const [client, setClient] = useState(new InvoiceClient());
+ const [shippingAddressId, setShippingAddressId] = useState(-1);
+
+ useEffect(() => {
+ setShippingAddressId(-1);
+ }, [client]);
+
+ return (
+ <>
+ <ClientPicker
+ client={client}
+ setClient={setClient}
+ shippingAddressId={shippingAddressId}
+ setShippingAddressId={setShippingAddressId}/>
+ <ItemPicker/>
+ </>
+ );
+}
+
+export default NewInvoicePage;