Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
.history
16,918 changes: 11,115 additions & 5,803 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"axios": "^1.7.2",
"bootstrap": "^5.1.0",
"file-saver": "^2.0.5",
"html2canvas": "^1.4.1",
Expand Down Expand Up @@ -46,6 +47,6 @@
]
},
"engines": {
"node": "14.15.0"
"node": "20.x"
}
}
76 changes: 61 additions & 15 deletions src/components/EditableField.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import React from "react";
import React, { useState } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import Form from "react-bootstrap/Form";
import InputGroup from "react-bootstrap/InputGroup";

const EditableField = (props) => {
const [inputValue, setInputValue] = useState(props.cellData.value);

const handleInputChange = (e) => {
setInputValue(e.target.value);
props.onItemizedItemEdit(e);
};

const handleSelectChange = (e) => {
const selectedValue = e.target.value;
setInputValue(selectedValue);
props.onItemizedItemEdit({
target: {
name: props.cellData.name,
value: selectedValue,
id: props.cellData.id,
},
});
};

return (
<InputGroup className="my-1 flex-nowrap">
{props.cellData.leading != null && (
Expand All @@ -16,20 +35,47 @@ const EditableField = (props) => {
</span>
</InputGroup.Text>
)}
<Form.Control
className={props.cellData.textAlign}
type={props.cellData.type}
placeholder={props.cellData.placeholder}
min={props.cellData.min}
name={props.cellData.name}
id={props.cellData.id}
value={props.cellData.value}
step={props.cellData.step}
precision={props.cellData.precision}
aria-label={props.cellData.name}
onChange={props.onItemizedItemEdit}
required
/>
{props.cellData.name === "itemName" && props.options ? (
<>
<Form.Control
as="select"
value=""
onChange={handleSelectChange}
className="me-1"
>
<option value="">Select an item</option>
{props.options.map((option, index) => (
<option key={index} value={option.itemName}>
{option.itemName}
</option>
))}
</Form.Control>
<Form.Control
type="text"
placeholder={props.cellData.placeholder}
name={props.cellData.name}
id={props.cellData.id}
value={inputValue}
onChange={handleInputChange}
required
/>
</>
) : (
<Form.Control
className={props.cellData.textAlign}
type={props.cellData.type}
placeholder={props.cellData.placeholder}
min={props.cellData.min}
name={props.cellData.name}
id={props.cellData.id}
value={props.cellData.value}
step={props.cellData.step}
precision={props.cellData.precision}
aria-label={props.cellData.name}
onChange={props.onItemizedItemEdit}
required
/>
)}
</InputGroup>
);
};
Expand Down
152 changes: 102 additions & 50 deletions src/components/InvoiceForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import InvoiceItem from "./InvoiceItem";
import InvoiceModal from "./InvoiceModal";
import { BiArrowBack } from "react-icons/bi";
import InputGroup from "react-bootstrap/InputGroup";
import { useDispatch } from "react-redux";
import { addInvoice, updateInvoice } from "../redux/invoicesSlice";
import { useDispatch,useSelector} from "react-redux";
import { addInvoice,updateInvoice } from "../redux/invoicesSlice";
import { Link, useParams, useLocation, useNavigate } from "react-router-dom";
import generateRandomId from "../utils/generateRandomId";
import { useInvoiceListData } from "../redux/hooks";
import ProductsTab from "./ProductsTab";
import { fetchRates, selectRates, selectSelectedCurrency, setCurrency } from "../redux/currencySlice";

const InvoiceForm = () => {
const dispatch = useDispatch();
Expand All @@ -22,49 +24,69 @@ const InvoiceForm = () => {
const navigate = useNavigate();
const isCopy = location.pathname.includes("create");
const isEdit = location.pathname.includes("edit");

const [isOpen, setIsOpen] = useState(false);
const [copyId, setCopyId] = useState("");
const { getOneInvoice, listSize } = useInvoiceListData();
const [oldCurrency, setOldCurrency] = useState("");
const [newCurrency, setNewCurrency] = useState("");
const [formData, setFormData] = useState(
isEdit
? getOneInvoice(params.id)
: isCopy && params.id
? {
...getOneInvoice(params.id),
id: generateRandomId(),
invoiceNumber: listSize + 1,
}
: {
id: generateRandomId(),
currentDate: new Date().toLocaleDateString(),
invoiceNumber: listSize + 1,
dateOfIssue: "",
billTo: "",
billToEmail: "",
billToAddress: "",
billFrom: "",
billFromEmail: "",
billFromAddress: "",
notes: "",
total: "0.00",
subTotal: "0.00",
taxRate: "",
taxAmount: "0.00",
discountRate: "",
discountAmount: "0.00",
currency: "$",
items: [
{
itemId: 0,
itemName: "",
itemDescription: "",
itemPrice: "1.00",
itemQuantity: 1,
},
],
}
);
isEdit
? getOneInvoice(params.id)
: isCopy && params.id
? {
...getOneInvoice(params.id),
id: generateRandomId(),
invoiceNumber: listSize + 1,
}
: {
id: generateRandomId(),
currentDate: new Date().toLocaleDateString(),
invoiceNumber: listSize + 1,
dateOfIssue: "",
billTo: "",
billToEmail: "",
billToAddress: "",
billFrom: "",
billFromEmail: "",
billFromAddress: "",
notes: "",
total: "0.00",
subTotal: "0.00",
taxRate: "",
taxAmount: "0.00",
discountRate: "",
discountAmount: "0.00",
currency: "$",
items: [
{
itemId: 0,
itemName: "",
itemDescription: "",
itemPrice: "1.00",
itemQuantity: 1,
},
],
}
);

/* TODO: OnCLick + icon => add item to invoice item list */
/* const itemsfromProdTab = useSelector(addItemToForm);
useEffect(() => {
setFormData(prevFormData => ({
...prevFormData,
items: [
...prevFormData.items,
...itemsfromProdTab.payload.invoices.map(item => ({
itemId: item.itemId,
itemName: item.itemName,
itemDescription: item.itemDescription,
itemPrice: item.itemPrice,
itemQuantity: item.itemQuantity,
}))
]
}));
}, [itemsfromProdTab]); */


useEffect(() => {
handleCalculateTotal();
Expand Down Expand Up @@ -142,8 +164,32 @@ const InvoiceForm = () => {
handleCalculateTotal();
};

const rates = useSelector(selectRates);

useEffect(() => {
dispatch(fetchRates());
}, [dispatch]);

const onCurrencyChange = (selectedOption) => {
const oldCurr = formData.currency;
setOldCurrency(oldCurr);
console.log({ name: "old curr", value: oldCurr });

setFormData({ ...formData, currency: selectedOption.currency });
dispatch(setCurrency(selectedOption.currency));
const newCurr = selectedOption.currency;
setNewCurrency(newCurr);
console.log({ name: "new curr", value: newCurr });
console.log(rates);
};


const convertCurrency = (amount, fromCurrency, toCurrency) => {
if (fromCurrency === toCurrency) {
return amount;
}
const rate = rates[toCurrency] / rates[fromCurrency];
return (amount * rate).toFixed(2);
};

const openModal = (event) => {
Expand All @@ -158,14 +204,16 @@ const InvoiceForm = () => {

const handleAddInvoice = () => {
if (isEdit) {

dispatch(updateInvoice({ id: params.id, updatedInvoice: formData }));
console.log("updateInvoice is called with:", { id: params.id, updatedInvoice: formData });
alert("Invoice updated successfuly 🥳");
} else if (isCopy) {
dispatch(addInvoice({ id: generateRandomId(), ...formData }));
alert("Invoice added successfuly 🥳");
} else {
dispatch(addInvoice(formData));
alert("Invoice added successfuly 🥳");
alert("Invoice added successfuly 🥳");
}
navigate("/");
};
Expand Down Expand Up @@ -307,6 +355,9 @@ const InvoiceForm = () => {
onRowDel={handleRowDel}
currency={formData.currency}
items={formData.items}
convertCurrency={convertCurrency}
oldCurrency={oldCurrency}
newCurrency={newCurrency}
/>
<Row className="mt-4 justify-content-end">
<Col lg={6}>
Expand Down Expand Up @@ -413,14 +464,14 @@ const InvoiceForm = () => {
className="btn btn-light my-1"
aria-label="Change Currency"
>
<option value="$">USD (United States Dollar)</option>
<option value="£">GBP (British Pound Sterling)</option>
<option value="¥">JPY (Japanese Yen)</option>
<option value="$">CAD (Canadian Dollar)</option>
<option value="$">AUD (Australian Dollar)</option>
<option value="$">SGD (Singapore Dollar)</option>
<option value="¥">CNY (Chinese Renminbi)</option>
<option value="">BTC (Bitcoin)</option>
<option value="USD">USD (United States Dollar)</option>
<option value="GBP">GBP (British Pound Sterling)</option>
<option value="JPY">JPY (Japanese Yen)</option>
<option value="CAD">CAD (Canadian Dollar)</option>
<option value="AUD">AUD (Australian Dollar)</option>
<option value="SGD">SGD (Singapore Dollar)</option>
<option value="CNY">CNY (Chinese Renminbi)</option>
<option value="BTC">BTC (Bitcoin)</option>
</Form.Select>
</Form.Group>
<Form.Group className="my-3">
Expand Down Expand Up @@ -477,6 +528,7 @@ const InvoiceForm = () => {
>
Copy Old Invoice
</Button>
<ProductsTab/>
</div>
</Col>
</Row>
Expand Down
30 changes: 25 additions & 5 deletions src/components/InvoiceItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { BiTrash } from "react-icons/bi";
import EditableField from "./EditableField";

const InvoiceItem = (props) => {
const { onItemizedItemEdit, currency, onRowDel, items, onRowAdd } = props;
const { onItemizedItemEdit, currency, onRowDel, items, onRowAdd,convertCurrency,oldCurrency,newCurrency } = props;

const itemTable = items.map((item) => (
<ItemRow
Expand All @@ -15,6 +15,10 @@ const InvoiceItem = (props) => {
onDelEvent={onRowDel}
onItemizedItemEdit={onItemizedItemEdit}
currency={currency}
allItems={items}
convertCurrency={convertCurrency}
oldCurrency={oldCurrency}
newCurrency={newCurrency}
/>
));

Expand Down Expand Up @@ -42,6 +46,23 @@ const ItemRow = (props) => {
const onDelEvent = () => {
props.onDelEvent(props.item);
};

const handlePriceChange = (event) => {

const convertedPrice = props.convertCurrency(
event.target.value,
props.oldCurrency,
props.newCurrency
);
console.log({name:"converted price",value:convertedPrice})
props.onItemizedItemEdit(
{
target: { name: "itemPrice", value: convertedPrice }
},
props.item.itemId
);
};

return (
<tr>
<td style={{ width: "100%" }}>
Expand All @@ -56,6 +77,7 @@ const ItemRow = (props) => {
value: props.item.itemName,
id: props.item.itemId,
}}
options={props.allItems}
/>
<EditableField
onItemizedItemEdit={(evt) =>
Expand Down Expand Up @@ -87,16 +109,14 @@ const ItemRow = (props) => {
</td>
<td style={{ minWidth: "130px" }}>
<EditableField
onItemizedItemEdit={(evt) =>
props.onItemizedItemEdit(evt, props.item.itemId)
}
onItemizedItemEdit={handlePriceChange}
cellData={{
leading: props.currency,
type: "number",
name: "itemPrice",
min: 1,
step: "0.01",
presicion: 2,
precision: 2,
textAlign: "text-end",
value: props.item.itemPrice,
id: props.item.itemId,
Expand Down
Loading