113 lines
3.3 KiB
TypeScript
113 lines
3.3 KiB
TypeScript
import {
|
|
Box,
|
|
Button,
|
|
Container,
|
|
Flex,
|
|
Flow,
|
|
Screen,
|
|
ScrollableList,
|
|
Text,
|
|
} from "wretched";
|
|
import {
|
|
ClientResponse,
|
|
InvoiceResponse,
|
|
getAllClients,
|
|
getAllInvoices,
|
|
getAllProjects,
|
|
} from "../services/pancakeApi.js";
|
|
import { compact, find, reverse, sortBy } from "lodash-es";
|
|
import Table from "cli-table3";
|
|
import { differenceInBusinessDays, format, parse } from "date-fns";
|
|
import { tableCellBox } from "./helpers/tableCellBox.js";
|
|
import { Theme } from "wretched/dist/Theme.js";
|
|
import {
|
|
distanceInBizDays,
|
|
parseServiceDateString,
|
|
simpleFormat,
|
|
} from "./helpers/dates.js";
|
|
|
|
export const renderInvoiceList = async (
|
|
clientId: string,
|
|
onSelect: (selectedInvoiceId: string | number) => void,
|
|
) => {
|
|
if (clientId === undefined) {
|
|
console.warn("No Client Id");
|
|
return new Text({ text: " --" });
|
|
}
|
|
const invResp = await getAllInvoices({ client_id: clientId });
|
|
const invoices: Array<InvoiceResponse> = invResp.invoices
|
|
.sort((a, b) => Number(a.invoice_number) - Number(b.invoice_number))
|
|
.reverse();
|
|
|
|
if (!invoices?.length) {
|
|
console.warn("No invoices");
|
|
return new Text({ text: " --" });
|
|
}
|
|
|
|
type FormattedInvoice = InvoiceResponse & { dueDistance: string };
|
|
const transformInvoice = (invoice: InvoiceResponse): FormattedInvoice => {
|
|
const ret: FormattedInvoice = { ...invoice, dueDistance: "" };
|
|
try {
|
|
if (invoice.due_date?.length > 0) {
|
|
const due = parseServiceDateString(invoice.due_date);
|
|
ret.due_date = simpleFormat(due);
|
|
ret.dueDistance = `Due in ${distanceInBizDays(due)} biz days.`;
|
|
}
|
|
if (invoice.payment_date?.length > 0) {
|
|
ret.payment_date = simpleFormat(
|
|
parseServiceDateString(invoice.payment_date),
|
|
);
|
|
}
|
|
ret.amount = "$" + Number(invoice.amount).toFixed(2).padEnd(2, "0");
|
|
} catch (error) {
|
|
console.error("Error transforming date", error);
|
|
} finally {
|
|
return ret;
|
|
}
|
|
};
|
|
// const detailsTable = new Table({
|
|
// head: ["Invoice#", "Amount", "Due", "Paid?", "Overdue?"],
|
|
// });
|
|
// const detailsTable: string[] | undefined = new Array();
|
|
// detailsTable.concat(
|
|
// ...invoices.map((x) => [
|
|
// x.invoice_number,
|
|
// Number(x.amount).toFixed(2),
|
|
// x.due_date?.substring(0, 10),
|
|
// x.is_paid ? "💰" : "⏳",
|
|
// x.overdue ? "🔥" : "✅",
|
|
// ]),
|
|
// );
|
|
|
|
return new ScrollableList({
|
|
cellForItem: (item, row) => {
|
|
return Flex.right({
|
|
children: [
|
|
new Button({
|
|
onClick: () => onSelect(item.id),
|
|
border: "none",
|
|
width: 6,
|
|
text: item.invoice_number,
|
|
}),
|
|
tableCellBox(item.amount, {
|
|
theme: item.is_paid ? Theme.green : Theme.secondary,
|
|
padding: { left: 1, right: 1, top: 0, bottom: 0 },
|
|
borders: { right: false },
|
|
width: 15,
|
|
alignment: "right",
|
|
}),
|
|
tableCellBox(item.dueDistance, {
|
|
theme: !item.is_paid && item.overdue ? Theme.red : Theme.secondary,
|
|
borders: { right: false },
|
|
padding: { left: 1, right: 1, top: 0, bottom: 0 },
|
|
width: 20,
|
|
alignment: "center",
|
|
}),
|
|
],
|
|
});
|
|
},
|
|
items: invoices.map(transformInvoice),
|
|
showScrollbars: undefined,
|
|
});
|
|
};
|