wip
This commit is contained in:
82
src/components/detailsInvoice.ts
Normal file
82
src/components/detailsInvoice.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import * as w from "wretched";
|
||||||
|
import Table from "cli-table3";
|
||||||
|
import {
|
||||||
|
getOneInvoice,
|
||||||
|
markInvoicePaid,
|
||||||
|
updateInvoice,
|
||||||
|
} from "../services/pancakeApi.js";
|
||||||
|
import { doubleBoxWithTitle } from "./helpers/boxBorders.js";
|
||||||
|
import { isNaN, omit, values } from "lodash-es";
|
||||||
|
import { fromUnixTime, format } from "date-fns";
|
||||||
|
import { InvoiceDetailsResponse } from "../services/invoiceResponse.js";
|
||||||
|
|
||||||
|
export const renderInvoiceDetails = async (invoiceId: string | number) => {
|
||||||
|
const { invoice } = await getOneInvoice(invoiceId.toString());
|
||||||
|
if (process.env.DEBUG) {
|
||||||
|
console.log("🚀 ~ renderInvoiceDetails ~ details:", invoice);
|
||||||
|
}
|
||||||
|
const mainWindow = new w.Scrollable({
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
|
const mainTable = new Table();
|
||||||
|
for (const key in omit(invoice, "items", "partial_payments")) {
|
||||||
|
const rawValue = invoice[key as keyof InvoiceDetailsResponse];
|
||||||
|
if (typeof rawValue !== "object") {
|
||||||
|
let value: Table.Cell = rawValue;
|
||||||
|
if (typeof rawValue !== "boolean") {
|
||||||
|
if (key.indexOf("date") >= 0) {
|
||||||
|
value = Number(rawValue);
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
value = format(fromUnixTime(value), "y-M-d");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainTable.push({
|
||||||
|
[key]: value,
|
||||||
|
} as Table.VerticalTableRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainWindow.addAll([
|
||||||
|
new w.Flow({
|
||||||
|
direction: "topToBottom",
|
||||||
|
children: [
|
||||||
|
new w.Text({ text: `Unpaid: $${invoice.unpaid_amount}` }),
|
||||||
|
new w.Button({
|
||||||
|
text: `Mark as ${invoice.unpaid_amount > 0 ? "Paid" : "Unpaid"}`,
|
||||||
|
onClick: async () => {
|
||||||
|
const isPaid = invoice.unpaid_amount > 0;
|
||||||
|
if (process.env.DEBUG) {
|
||||||
|
console.log("Switching paid status from", {
|
||||||
|
isPaid,
|
||||||
|
invoiceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isPaid) {
|
||||||
|
mainWindow.removeAllChildren();
|
||||||
|
mainWindow.addAll([
|
||||||
|
new w.Flow({
|
||||||
|
direction: "leftToRight",
|
||||||
|
children: [
|
||||||
|
new w.Text({ text: "Paid Amount" }),
|
||||||
|
new w.Input({ text: "Paid Amount" }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
if (!process.env.DEBUG) {
|
||||||
|
await markInvoicePaid(invoiceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
new w.Text({ text: mainTable.toString() }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
return new w.Box({
|
||||||
|
border: doubleBoxWithTitle(
|
||||||
|
`${invoice.client_name} 💸 Invoice: ${invoice.id}`,
|
||||||
|
),
|
||||||
|
child: mainWindow,
|
||||||
|
});
|
||||||
|
};
|
||||||
42
src/components/helpers/animatedText.ts
Normal file
42
src/components/helpers/animatedText.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { Size, Text, View, Viewport } from "wretched";
|
||||||
|
|
||||||
|
export class AnimatedText extends View {
|
||||||
|
#frameTime = 0;
|
||||||
|
#frame = 0;
|
||||||
|
#frames: string[];
|
||||||
|
|
||||||
|
static FRAME = 32;
|
||||||
|
|
||||||
|
constructor({ frames }: { frames: string[] }) {
|
||||||
|
super({
|
||||||
|
x: 10,
|
||||||
|
y: 4,
|
||||||
|
width: 14,
|
||||||
|
height: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#frames = frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
naturalSize() {
|
||||||
|
return new Size(14, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
receiveTick(dt: number): boolean {
|
||||||
|
this.#frameTime += dt;
|
||||||
|
if (this.#frameTime > AnimatedText.FRAME) {
|
||||||
|
this.#frameTime %= AnimatedText.FRAME;
|
||||||
|
this.#frame = (this.#frame + 1) % this.#frames.length;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(viewport: Viewport) {
|
||||||
|
viewport.registerTick();
|
||||||
|
|
||||||
|
const t = new Text({ text: this.#frames[this.#frame] });
|
||||||
|
t.render(viewport);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/components/helpers/boxBorders.ts
Normal file
5
src/components/helpers/boxBorders.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export const doubleBoxWithTitle = (
|
||||||
|
title: string,
|
||||||
|
): [string, string, string, string, string, string, string, string] => {
|
||||||
|
return ["═", "║", `╔═ ${title} `, "╗", "╚", "╝", "═", "║"];
|
||||||
|
};
|
||||||
21
src/components/helpers/dates.ts
Normal file
21
src/components/helpers/dates.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { differenceInBusinessDays, format, parse, startOfDay } from "date-fns";
|
||||||
|
|
||||||
|
const now = format(new Date(), "yyyy-MM-dd");
|
||||||
|
const customTime = "18:00:00";
|
||||||
|
const customDate = new Date(`${now} ${customTime}`);
|
||||||
|
export const simpleFormat = (date: Date) => {
|
||||||
|
return format(startOfDay(date), "MMM d, yyyy");
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("🚀 ~ customDate:", simpleFormat(customDate));
|
||||||
|
|
||||||
|
export const distanceInBizDays = (date: Date) =>
|
||||||
|
differenceInBusinessDays(date, customDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param dateString Ex. 2017-07-16 23:00:00
|
||||||
|
* @returns Date
|
||||||
|
*/
|
||||||
|
export const parseServiceDateString = (dateString: string) =>
|
||||||
|
parse(dateString, "yyyy-MM-dd hh:mm:ss", customDate);
|
||||||
53
src/components/helpers/tableCellBox.ts
Normal file
53
src/components/helpers/tableCellBox.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { Box, Text } from "wretched";
|
||||||
|
import { InvoiceResponse } from "../../services/pancakeApi.js";
|
||||||
|
import { Theme } from "wretched/dist/Theme.js";
|
||||||
|
import { Style } from "wretched/dist/Style.js";
|
||||||
|
|
||||||
|
export type CellProps = {
|
||||||
|
width?: number;
|
||||||
|
padding?: { left: number; right: number; top: number; bottom: number };
|
||||||
|
borders?: { left?: boolean; right?: boolean };
|
||||||
|
alignment?: "left" | "right" | "center";
|
||||||
|
theme?: Theme;
|
||||||
|
};
|
||||||
|
export const tableCellBox = (text: string, opts: CellProps) => {
|
||||||
|
opts = Object.assign(
|
||||||
|
{
|
||||||
|
padding: { left: 1, right: 1, top: 0, bottom: 0 },
|
||||||
|
borders: { left: true, right: true },
|
||||||
|
alignment: "left",
|
||||||
|
theme: Theme.plain,
|
||||||
|
},
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
const innerWidth = opts.width
|
||||||
|
? opts.padding
|
||||||
|
? opts.width + opts.padding.left + opts.padding.right
|
||||||
|
: "natural"
|
||||||
|
: "natural";
|
||||||
|
return new Box({
|
||||||
|
width: innerWidth,
|
||||||
|
padding: opts.padding,
|
||||||
|
theme: opts.theme,
|
||||||
|
child: new Text({
|
||||||
|
wrap: false,
|
||||||
|
style: new Style({
|
||||||
|
background: opts.theme?.background,
|
||||||
|
foreground: opts.theme?.textColor,
|
||||||
|
}),
|
||||||
|
text,
|
||||||
|
width: opts.width,
|
||||||
|
alignment: opts.alignment,
|
||||||
|
}),
|
||||||
|
border: [
|
||||||
|
"",
|
||||||
|
opts.borders?.left ? "|" : "",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
opts.borders?.right ? "|" : "",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
|
|
||||||
import { Box, Button, Container, Flex, Screen, ScrollableList, Text, } from "wretched";
|
|
||||||
import { ClientResponse, InvoiceResponse, getAllClients, getAllInvoices, getAllProjects } from "../services/pancakeApi.js";
|
|
||||||
import { compact, find, reverse, sortBy } from "lodash-es";
|
|
||||||
|
|
||||||
const clientsResp = await getAllClients({ limit: 100, sort_by: "created", sort_dir: "desc" });
|
|
||||||
console.log("🚀 ~ clientsResp:", clientsResp)
|
|
||||||
const sorted = reverse(sortBy(clientsResp.clients, ["unpaid_total"]))
|
|
||||||
console.log("🚀 ~ sorted:", sorted)
|
|
||||||
|
|
||||||
let selectedClientId: string | undefined;
|
|
||||||
const currentClientName = () => {
|
|
||||||
const curr = find(sorted, { id: selectedClientId });
|
|
||||||
return `🏙️ ${curr?.company} | ${curr?.first_name} ${curr?.last_name}`
|
|
||||||
}
|
|
||||||
let invoiceList = async (clientId?: string) => {
|
|
||||||
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;
|
|
||||||
if (!invoices?.length) {
|
|
||||||
console.warn("No invoices")
|
|
||||||
return new Text({ text: " --" });
|
|
||||||
}
|
|
||||||
return new ScrollableList({ cellForItem: (item) => new Button({ text: item.invoice_number }), items: invoices })
|
|
||||||
}
|
|
||||||
|
|
||||||
let Invoices = new Box({ width: "fill", height: "fill", child: await invoiceList(selectedClientId), border: "rounded" })
|
|
||||||
|
|
||||||
let Clients = new ScrollableList({
|
|
||||||
minWidth: 20,
|
|
||||||
width: "natural",
|
|
||||||
cellForItem: (item: ClientResponse) => {
|
|
||||||
if (!item || !item.company) {
|
|
||||||
console.log(item)
|
|
||||||
return new Text({ text: "Nothing Here" })
|
|
||||||
}
|
|
||||||
return new Button({
|
|
||||||
text: item.company || item.id, onClick: async () => {
|
|
||||||
console.log("🚀 ~ clientInvoicesView ~ clicked company:", item);
|
|
||||||
selectedClientId = item.id;
|
|
||||||
//TODO: Need a spinner for loading indicator
|
|
||||||
Invoices.removeAllChildren();
|
|
||||||
Invoices.add(await invoiceList(selectedClientId))
|
|
||||||
ClientTitle.text = currentClientName();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, items: sorted
|
|
||||||
})
|
|
||||||
|
|
||||||
const ClientTitle = new Text({
|
|
||||||
text: "No Client Selected Yet",
|
|
||||||
})
|
|
||||||
|
|
||||||
export const clientInvoicesView = Flex.right({
|
|
||||||
children: [
|
|
||||||
Clients,
|
|
||||||
Flex.down({
|
|
||||||
width: "fill",
|
|
||||||
height: "fill",
|
|
||||||
padding: 1,
|
|
||||||
|
|
||||||
children: [
|
|
||||||
ClientTitle,
|
|
||||||
Invoices
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
})
|
|
||||||
104
src/components/listClients.ts
Normal file
104
src/components/listClients.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Container,
|
||||||
|
Flex,
|
||||||
|
Screen,
|
||||||
|
ScrollableList,
|
||||||
|
Text,
|
||||||
|
} from "wretched";
|
||||||
|
import {
|
||||||
|
ClientResponse,
|
||||||
|
InvoiceResponse,
|
||||||
|
getAllClients,
|
||||||
|
getAllInvoices,
|
||||||
|
getAllProjects,
|
||||||
|
} from "../services/pancakeApi.js";
|
||||||
|
import spinners from "cli-spinners";
|
||||||
|
import { compact, find, isNil, isString, reverse, sortBy } from "lodash-es";
|
||||||
|
import { renderInvoiceList } from "./listInvoices.js";
|
||||||
|
import { renderInvoiceDetails } from "./detailsInvoice.js";
|
||||||
|
import { doubleBoxWithTitle } from "./helpers/boxBorders.js";
|
||||||
|
import { AnimatedText } from "./helpers/animatedText.js";
|
||||||
|
|
||||||
|
const clientsResp = await getAllClients({
|
||||||
|
limit: 100,
|
||||||
|
sort_by: "created",
|
||||||
|
sort_dir: "desc",
|
||||||
|
});
|
||||||
|
// console.log("🚀 ~ clientsResp:", clientsResp);
|
||||||
|
const sorted = reverse(sortBy(clientsResp.clients, ["unpaid_total"]));
|
||||||
|
// console.log("🚀 ~ sorted:", sorted);
|
||||||
|
|
||||||
|
// let selectedClientId: string | undefined;
|
||||||
|
// let selectedInvoiceId: string | undefined;
|
||||||
|
|
||||||
|
const reDrawMain = async (
|
||||||
|
selectedClientId?: string,
|
||||||
|
selectedInvoiceId?: string,
|
||||||
|
) => {
|
||||||
|
clientInvoicesView.removeChild(1);
|
||||||
|
clientInvoicesView.add(
|
||||||
|
new AnimatedText({ frames: spinners.aesthetic.frames }),
|
||||||
|
);
|
||||||
|
const newBox = await renderInvoicesBox(selectedClientId, selectedInvoiceId);
|
||||||
|
clientInvoicesView.removeChild(1);
|
||||||
|
clientInvoicesView.add(renderRightPanel(newBox));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderInvoicesBox = async (
|
||||||
|
selectedClientId?: string,
|
||||||
|
selectedInvoiceId?: string,
|
||||||
|
) => {
|
||||||
|
let InvoicesBox = new Box({ child: new Text({ text: "« Select a Client" }) });
|
||||||
|
|
||||||
|
if (selectedInvoiceId && selectedClientId) {
|
||||||
|
InvoicesBox = await renderInvoiceDetails(selectedInvoiceId);
|
||||||
|
} else if (selectedClientId) {
|
||||||
|
const currClient = find(sorted, { id: selectedClientId });
|
||||||
|
const currentClientName = `${currClient?.company} | ${currClient?.first_name} ${currClient?.last_name}`;
|
||||||
|
InvoicesBox = new Box({
|
||||||
|
width: "fill",
|
||||||
|
height: "fill",
|
||||||
|
child: await renderInvoiceList(selectedClientId, (id) => {
|
||||||
|
console.log("clicked invoice", id);
|
||||||
|
reDrawMain(selectedClientId, id.toString());
|
||||||
|
}),
|
||||||
|
border: doubleBoxWithTitle(currentClientName),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return InvoicesBox;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Clients = new ScrollableList({
|
||||||
|
minWidth: 20,
|
||||||
|
width: "natural",
|
||||||
|
cellForItem: (client: ClientResponse) => {
|
||||||
|
if (!client) {
|
||||||
|
// console.log("empty client company name", item);
|
||||||
|
return new Text({ text: `Undefined Client` });
|
||||||
|
}
|
||||||
|
return new Button({
|
||||||
|
border: "none",
|
||||||
|
width: "natural",
|
||||||
|
text: `${client.company}`,
|
||||||
|
onClick: async () => {
|
||||||
|
console.log("🚀 ~ clientInvoicesView ~ clicked client:", client);
|
||||||
|
reDrawMain(client.id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
items: sorted,
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderRightPanel = (box: Box) =>
|
||||||
|
Flex.down({
|
||||||
|
width: "fill",
|
||||||
|
height: "fill",
|
||||||
|
padding: { left: 1, right: 1 },
|
||||||
|
children: [box],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const clientInvoicesView = Flex.right({
|
||||||
|
children: [Clients, renderRightPanel(await renderInvoicesBox())],
|
||||||
|
});
|
||||||
112
src/components/listInvoices.ts
Normal file
112
src/components/listInvoices.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
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,
|
||||||
|
});
|
||||||
|
};
|
||||||
38
src/components/mainNav.ts
Normal file
38
src/components/mainNav.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { Box, Button, Flex, Text } from "wretched";
|
||||||
|
import { Style } from "wretched/dist/Style.js";
|
||||||
|
const tabs = {
|
||||||
|
Home: "/",
|
||||||
|
Clients: "/",
|
||||||
|
Invoices: "/",
|
||||||
|
Projects: "/",
|
||||||
|
} as const;
|
||||||
|
export const mainNav = (props: { activeTabName: keyof typeof tabs }) => {
|
||||||
|
return new Flex({
|
||||||
|
direction: "leftToRight",
|
||||||
|
children: Object.keys(tabs).map(
|
||||||
|
(x) =>
|
||||||
|
new Box({
|
||||||
|
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
||||||
|
// child: new Text({ text: x, style: new Style({ bold: true }) }),
|
||||||
|
child: new Button({
|
||||||
|
text: x,
|
||||||
|
border: "none",
|
||||||
|
theme: props.activeTabName === x ? "plain" : "selected",
|
||||||
|
onClick: () => {
|
||||||
|
console.log({ clicked: x });
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
border: [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
props.activeTabName === x ? "━" : "─",
|
||||||
|
"",
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
59
src/index.ts
59
src/index.ts
@@ -1,38 +1,47 @@
|
|||||||
import 'dotenv/config'
|
import "dotenv/config";
|
||||||
import { Screen, Box, Flow, Text, Button, interceptConsoleLog, ConsoleLog, iTerm2, Window, Flex } from 'wretched'
|
import {
|
||||||
|
Screen,
|
||||||
|
Box,
|
||||||
|
Flow,
|
||||||
|
Text,
|
||||||
|
Button,
|
||||||
|
interceptConsoleLog,
|
||||||
|
ConsoleLog,
|
||||||
|
iTerm2,
|
||||||
|
Window,
|
||||||
|
Flex,
|
||||||
|
} from "wretched";
|
||||||
import * as utility from "wretched/dist/components/utility";
|
import * as utility from "wretched/dist/components/utility";
|
||||||
import { clientInvoicesView } from "./components/listClientInvoices.js";
|
import { clientInvoicesView } from "./components/listClients.js";
|
||||||
|
import { mainNav } from "./components/mainNav.js";
|
||||||
|
import { inspect } from "node:util";
|
||||||
|
|
||||||
interceptConsoleLog();
|
interceptConsoleLog();
|
||||||
process.title = 'Wretched';
|
process.title = "Wretched";
|
||||||
|
|
||||||
const consoleLog = new ConsoleLog({
|
const consoleLog = new ConsoleLog({
|
||||||
height: 12,
|
height: 12,
|
||||||
})
|
});
|
||||||
const [screen, program] = await Screen.start(
|
const [screen, program] = await Screen.start(async (program) => {
|
||||||
async (program) => {
|
await iTerm2.setBackground(program, [23, 23, 23]);
|
||||||
await iTerm2.setBackground(program, [23, 23, 23])
|
|
||||||
|
|
||||||
return new Window({
|
return new Window({
|
||||||
child: new utility.TrackMouse({
|
child: Flex.down({
|
||||||
content: Flex.down({
|
padding: { top: 1 },
|
||||||
children: [
|
children: [
|
||||||
['flex1', clientInvoicesView],
|
["natural", mainNav({ activeTabName: "Clients" })],
|
||||||
['natural', consoleLog],
|
["flex1", clientInvoicesView],
|
||||||
|
["natural", consoleLog],
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
}),
|
});
|
||||||
})
|
});
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
program.key('escape', function () {
|
program.key("escape", function () {
|
||||||
consoleLog.clear()
|
consoleLog.clear();
|
||||||
screen.render()
|
screen.render();
|
||||||
})
|
});
|
||||||
|
|
||||||
process.on("beforeExit", () => {
|
process.on("uncaughtException", (error) => {
|
||||||
|
console.error("\r\nUncaught Exception was Caught!", inspect(error));
|
||||||
})
|
});
|
||||||
|
|||||||
146
src/services/invoiceResponse.ts
Normal file
146
src/services/invoiceResponse.ts
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
export interface Item {
|
||||||
|
id: string;
|
||||||
|
unique_id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
qty: string;
|
||||||
|
rate: string;
|
||||||
|
period: string;
|
||||||
|
total: string;
|
||||||
|
sort: string;
|
||||||
|
type: string;
|
||||||
|
item_type_id: string;
|
||||||
|
discount: string;
|
||||||
|
discount_is_percentage: string;
|
||||||
|
item_type_table: string;
|
||||||
|
currency_code: null;
|
||||||
|
taxes: any[];
|
||||||
|
tax_ids: any[];
|
||||||
|
is_taxable: boolean;
|
||||||
|
tax_total: number;
|
||||||
|
billable_total: number;
|
||||||
|
tax_label: string;
|
||||||
|
taxes_buffer: any[];
|
||||||
|
total_pre_tax_post_discounts: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PartialPayment {
|
||||||
|
id: string;
|
||||||
|
unique_invoice_id: string;
|
||||||
|
amount: string;
|
||||||
|
gateway_surcharge: string;
|
||||||
|
is_percentage: string;
|
||||||
|
due_date: string;
|
||||||
|
notes: string;
|
||||||
|
txn_id: string;
|
||||||
|
payment_gross: string;
|
||||||
|
item_name: string;
|
||||||
|
is_paid: string;
|
||||||
|
payment_date: string;
|
||||||
|
payment_type: string;
|
||||||
|
payer_status: string;
|
||||||
|
payment_status: string;
|
||||||
|
unique_id: string;
|
||||||
|
payment_method: string;
|
||||||
|
key: string;
|
||||||
|
improved: string;
|
||||||
|
transaction_fee: string;
|
||||||
|
due_date_input: string;
|
||||||
|
payment_url: string;
|
||||||
|
over_due: boolean;
|
||||||
|
billableAmount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PartialPayments {
|
||||||
|
[key: string]: PartialPayment;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Items {
|
||||||
|
[key: string]: Item;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InvoiceDetailsResponse {
|
||||||
|
date_to_automatically_notify: string;
|
||||||
|
computed_due_date: string;
|
||||||
|
real_invoice_id: string;
|
||||||
|
real_invoice_unique_id: string;
|
||||||
|
paid: string;
|
||||||
|
overdue: string;
|
||||||
|
id: string;
|
||||||
|
unique_id: string;
|
||||||
|
client_id: string;
|
||||||
|
amount: string;
|
||||||
|
due_date: string;
|
||||||
|
invoice_number: string;
|
||||||
|
notes: null;
|
||||||
|
description: string;
|
||||||
|
txn_id: string;
|
||||||
|
payment_gross: string;
|
||||||
|
item_name: string;
|
||||||
|
payment_hash: string;
|
||||||
|
payment_status: string;
|
||||||
|
payment_type: string;
|
||||||
|
payment_date: string;
|
||||||
|
payer_status: string;
|
||||||
|
type: string;
|
||||||
|
date_entered: string;
|
||||||
|
is_paid: string;
|
||||||
|
is_recurring: string;
|
||||||
|
frequency: string;
|
||||||
|
auto_send: string;
|
||||||
|
recur_id: string;
|
||||||
|
currency_id: string;
|
||||||
|
exchange_rate: string;
|
||||||
|
proposal_id: string;
|
||||||
|
send_x_days_before: string;
|
||||||
|
has_sent_notification: string;
|
||||||
|
last_sent: string;
|
||||||
|
next_recur_date: string;
|
||||||
|
last_viewed: string;
|
||||||
|
is_viewable: string;
|
||||||
|
is_archived: string;
|
||||||
|
owner_id: string;
|
||||||
|
last_status_change: string;
|
||||||
|
status: string;
|
||||||
|
project_id: string;
|
||||||
|
auto_charge: string;
|
||||||
|
address: string;
|
||||||
|
language: string;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
email: string;
|
||||||
|
company: string;
|
||||||
|
phone: string;
|
||||||
|
client_unique_id: string;
|
||||||
|
currency_code: string;
|
||||||
|
has_files: string;
|
||||||
|
total_comments: string;
|
||||||
|
tax_total: number;
|
||||||
|
billable_amount: number;
|
||||||
|
client_name: string;
|
||||||
|
formatted_is_paid: string;
|
||||||
|
days_overdue: number;
|
||||||
|
url: string;
|
||||||
|
currency_symbol: string;
|
||||||
|
part_count: string;
|
||||||
|
paid_part_count: string;
|
||||||
|
unpaid_part_count: string;
|
||||||
|
proposal_num: string;
|
||||||
|
list_invoice_belongs_to: string;
|
||||||
|
items: Items;
|
||||||
|
taxes: any[];
|
||||||
|
sub_total: number;
|
||||||
|
has_discount: boolean;
|
||||||
|
discounts: any[];
|
||||||
|
sub_total_after_discounts: number;
|
||||||
|
total: number;
|
||||||
|
receipts: any[];
|
||||||
|
paid_amount: number;
|
||||||
|
unpaid_amount: number;
|
||||||
|
collected_taxes: any[];
|
||||||
|
has_tax_reg: boolean;
|
||||||
|
tax_collected: number;
|
||||||
|
total_transaction_fees: number;
|
||||||
|
partial_payments: PartialPayments;
|
||||||
|
next_part_to_pay: number;
|
||||||
|
}
|
||||||
@@ -1,25 +1,34 @@
|
|||||||
|
import ky from "ky";
|
||||||
import ky from 'ky';
|
import { InvoiceDetailsResponse } from "./invoiceResponse.js";
|
||||||
|
|
||||||
|
|
||||||
const API_KEY = process.env.PANCAKE_API_KEY;
|
const API_KEY = process.env.PANCAKE_API_KEY;
|
||||||
const API_URL = process.env.PANCAKE_API_URL;
|
const API_URL = process.env.PANCAKE_API_URL;
|
||||||
console.log("🚀 ~ API_URL:", API_URL)
|
console.log("🚀 ~ API_URL:", API_URL);
|
||||||
|
|
||||||
const api = ky.create({ prefixUrl: API_URL, headers: { "x-api-key": `${API_KEY}` } });
|
const api = ky.create({
|
||||||
|
prefixUrl: API_URL,
|
||||||
|
headers: { "x-api-key": `${API_KEY}` },
|
||||||
|
});
|
||||||
|
|
||||||
type PaginationParams = { limit?: number, start?: number, sort_by?: string, sort_dir?: 'asc' | 'desc' }
|
type PaginationParams = {
|
||||||
|
limit?: number;
|
||||||
|
start?: number;
|
||||||
|
sort_by?: string;
|
||||||
|
sort_dir?: "asc" | "desc";
|
||||||
|
};
|
||||||
export type ClientResponse = {
|
export type ClientResponse = {
|
||||||
id: string,
|
id: string;
|
||||||
first_name: string,
|
first_name: string;
|
||||||
last_name: string,
|
last_name: string;
|
||||||
company: string,
|
company: string;
|
||||||
total: number,
|
total: number;
|
||||||
unpaid_total: number
|
unpaid_total: number;
|
||||||
}
|
};
|
||||||
// Clients
|
// Clients
|
||||||
async function getAllClients(params: PaginationParams): Promise<ListResponse<"clients", ClientResponse>> {
|
async function getAllClients(
|
||||||
const { limit = 100, start = 0, sort_by = 'id', sort_dir = 'desc' } = params;
|
params: PaginationParams,
|
||||||
|
): Promise<ListResponse<"clients", ClientResponse>> {
|
||||||
|
const { limit = 100, start = 0, sort_by = "id", sort_dir = "desc" } = params;
|
||||||
const url = `clients?limit=${limit}&start=${start}&sort_by=${sort_by}&sort_dir=${sort_dir}`;
|
const url = `clients?limit=${limit}&start=${start}&sort_by=${sort_by}&sort_dir=${sort_dir}`;
|
||||||
const response = api.get(url);
|
const response = api.get(url);
|
||||||
return response.json();
|
return response.json();
|
||||||
@@ -34,7 +43,7 @@ async function getOneClient(id: string) {
|
|||||||
async function createNewClient(data) {
|
async function createNewClient(data) {
|
||||||
const url = `clients/new`;
|
const url = `clients/new`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: data
|
json: data,
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -42,7 +51,7 @@ async function createNewClient(data) {
|
|||||||
async function updateClient(data) {
|
async function updateClient(data) {
|
||||||
const url = `clients/edit`;
|
const url = `clients/edit`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: data
|
json: data,
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -50,18 +59,18 @@ async function updateClient(data) {
|
|||||||
async function deleteClient(id: string) {
|
async function deleteClient(id: string) {
|
||||||
const url = `clients/delete`;
|
const url = `clients/delete`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: { id }
|
json: { id },
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
type ListResponse<A extends string, T> = {
|
type ListResponse<A extends string, T> = {
|
||||||
status: boolean,
|
status: boolean;
|
||||||
message: string,
|
message: string;
|
||||||
count: number,
|
count: number;
|
||||||
} & Record<A, Array<T>>
|
} & Record<A, Array<T>>;
|
||||||
type ProjectResponse = {
|
type ProjectResponse = {
|
||||||
name: string
|
name: string;
|
||||||
}
|
};
|
||||||
// Projects
|
// Projects
|
||||||
async function getOneProject(id: string): Promise<ProjectResponse> {
|
async function getOneProject(id: string): Promise<ProjectResponse> {
|
||||||
const url = `projects/show?id=${id}`;
|
const url = `projects/show?id=${id}`;
|
||||||
@@ -69,20 +78,21 @@ async function getOneProject(id: string): Promise<ProjectResponse> {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAllProjects(params: PaginationParams): Promise<ListResponse<"projects", ProjectResponse>> {
|
async function getAllProjects(
|
||||||
const { limit = 5, start = 0, sort_by = 'id', sort_dir = 'asc' } = params;
|
params: PaginationParams,
|
||||||
|
): Promise<ListResponse<"projects", ProjectResponse>> {
|
||||||
|
const { limit = 5, start = 0, sort_by = "id", sort_dir = "asc" } = params;
|
||||||
const url = `projects?limit=${limit}&start=${start}&sort_by=${sort_by}&sort_dir=${sort_dir}`;
|
const url = `projects?limit=${limit}&start=${start}&sort_by=${sort_by}&sort_dir=${sort_dir}`;
|
||||||
const response = await api.get(url);
|
const response = await api.get(url);
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Projects (continued)
|
// Projects (continued)
|
||||||
async function createNewProject(data) {
|
async function createNewProject(data) {
|
||||||
const url = `projects/new`;
|
const url = `projects/new`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: data
|
json: data,
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -90,7 +100,7 @@ async function createNewProject(data) {
|
|||||||
async function updateProject(data) {
|
async function updateProject(data) {
|
||||||
const url = `projects/edit`;
|
const url = `projects/edit`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: data
|
json: data,
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -105,7 +115,7 @@ async function getTasksByProject(projectId: string) {
|
|||||||
async function createTask(data) {
|
async function createTask(data) {
|
||||||
const url = `projects/tasks/new`;
|
const url = `projects/tasks/new`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: data
|
json: data,
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -113,7 +123,7 @@ async function createTask(data) {
|
|||||||
async function updateTask(data) {
|
async function updateTask(data) {
|
||||||
const url = `projects/tasks/update`;
|
const url = `projects/tasks/update`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: data
|
json: data,
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -121,11 +131,11 @@ async function updateTask(data) {
|
|||||||
async function deleteTask(taskId: string) {
|
async function deleteTask(taskId: string) {
|
||||||
const url = `projects/tasks/show`;
|
const url = `projects/tasks/show`;
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ id: taskId })
|
body: JSON.stringify({ id: taskId }),
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -133,7 +143,7 @@ async function deleteTask(taskId: string) {
|
|||||||
async function logTimeOnTask(data) {
|
async function logTimeOnTask(data) {
|
||||||
const url = `projects/tasks/log_time`;
|
const url = `projects/tasks/log_time`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: data
|
json: data,
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -141,7 +151,7 @@ async function logTimeOnTask(data) {
|
|||||||
async function completeTask(taskId: string) {
|
async function completeTask(taskId: string) {
|
||||||
const url = `projects/tasks/compete`;
|
const url = `projects/tasks/compete`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: { id: taskId }
|
json: { id: taskId },
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -149,22 +159,62 @@ async function completeTask(taskId: string) {
|
|||||||
async function reopenTask(taskId: string) {
|
async function reopenTask(taskId: string) {
|
||||||
const url = `projects/tasks/reopen`;
|
const url = `projects/tasks/reopen`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: { id: taskId }
|
json: { id: taskId },
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
export type binaryBooleanString = "0" | "1";
|
||||||
export type InvoiceResponse = { invoice_number: string, client_id: string, amount: string }
|
export type InvoiceResponse = {
|
||||||
|
id: number;
|
||||||
|
invoice_number: string;
|
||||||
|
client_id: string;
|
||||||
|
amount: string;
|
||||||
|
due_date: string;
|
||||||
|
description: string;
|
||||||
|
is_viewable: binaryBooleanString;
|
||||||
|
has_sent_notification: binaryBooleanString;
|
||||||
|
is_paid: boolean;
|
||||||
|
date_entered: string;
|
||||||
|
overdue: boolean;
|
||||||
|
payment_status: string;
|
||||||
|
payment_date: string;
|
||||||
|
company: string;
|
||||||
|
email: string;
|
||||||
|
/**
|
||||||
|
* "last_sent": "1527114961",
|
||||||
|
*/
|
||||||
|
last_sent: string;
|
||||||
|
/**
|
||||||
|
* "last_viewed": "1545454550",
|
||||||
|
*/
|
||||||
|
last_viewed: string;
|
||||||
|
};
|
||||||
// Invoices
|
// Invoices
|
||||||
async function getAllInvoices(params: { client_id: string } & PaginationParams): Promise<ListResponse<"invoices", InvoiceResponse>> {
|
async function getAllInvoices(
|
||||||
const { client_id, limit = 5, start = 0, sort_by = 'id', sort_dir = 'asc' } = params;
|
params: { client_id: string } & PaginationParams,
|
||||||
const queryParams = new URLSearchParams({ client_id, limit: limit.toFixed(0), start: start.toFixed(0), sort_by, sort_dir });
|
): Promise<ListResponse<"invoices", InvoiceResponse>> {
|
||||||
|
const {
|
||||||
|
client_id,
|
||||||
|
limit = 0,
|
||||||
|
start = 0,
|
||||||
|
sort_by = "id",
|
||||||
|
sort_dir = "asc",
|
||||||
|
} = params;
|
||||||
|
const queryParams = new URLSearchParams({
|
||||||
|
client_id,
|
||||||
|
limit: limit.toFixed(0),
|
||||||
|
start: start.toFixed(0),
|
||||||
|
sort_by,
|
||||||
|
sort_dir,
|
||||||
|
});
|
||||||
const url = `invoices?${queryParams.toString()}`;
|
const url = `invoices?${queryParams.toString()}`;
|
||||||
const response = await api.get(url);
|
const response = await api.get(url);
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getOneInvoice(id: string): Promise<InvoiceResponse> {
|
async function getOneInvoice(
|
||||||
|
id: string,
|
||||||
|
): Promise<{ status: string; invoice: InvoiceDetailsResponse }> {
|
||||||
const url = `invoices/show?id=${id}`;
|
const url = `invoices/show?id=${id}`;
|
||||||
const response = await api.get(url);
|
const response = await api.get(url);
|
||||||
return response.json();
|
return response.json();
|
||||||
@@ -173,7 +223,7 @@ async function getOneInvoice(id: string): Promise<InvoiceResponse> {
|
|||||||
async function createNewInvoice(data) {
|
async function createNewInvoice(data) {
|
||||||
const url = `invoices/new`;
|
const url = `invoices/new`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: data
|
json: data,
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -181,7 +231,7 @@ async function createNewInvoice(data) {
|
|||||||
async function updateInvoice(data) {
|
async function updateInvoice(data) {
|
||||||
const url = `invoices/edit`;
|
const url = `invoices/edit`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: data
|
json: data,
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -189,7 +239,7 @@ async function updateInvoice(data) {
|
|||||||
async function deleteInvoice(invoiceId: string) {
|
async function deleteInvoice(invoiceId: string) {
|
||||||
const url = `invoices/delete`;
|
const url = `invoices/delete`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: { id: invoiceId }
|
json: { id: invoiceId },
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -197,7 +247,7 @@ async function deleteInvoice(invoiceId: string) {
|
|||||||
async function openInvoice(invoiceId: string) {
|
async function openInvoice(invoiceId: string) {
|
||||||
const url = `invoices/open`;
|
const url = `invoices/open`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: { id: invoiceId }
|
json: { id: invoiceId },
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -205,7 +255,7 @@ async function openInvoice(invoiceId: string) {
|
|||||||
async function closeInvoice(invoiceId: string) {
|
async function closeInvoice(invoiceId: string) {
|
||||||
const url = `invoices/close`;
|
const url = `invoices/close`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: { id: invoiceId }
|
json: { id: invoiceId },
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -213,7 +263,7 @@ async function closeInvoice(invoiceId: string) {
|
|||||||
async function markInvoicePaid(invoiceId: string) {
|
async function markInvoicePaid(invoiceId: string) {
|
||||||
const url = `invoices/paid`;
|
const url = `invoices/paid`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: { id: invoiceId }
|
json: { id: invoiceId },
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -221,7 +271,7 @@ async function markInvoicePaid(invoiceId: string) {
|
|||||||
async function sendInvoice(invoiceId: string) {
|
async function sendInvoice(invoiceId: string) {
|
||||||
const url = `invoices/send`;
|
const url = `invoices/send`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: { id: invoiceId }
|
json: { id: invoiceId },
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -234,8 +284,13 @@ async function getOneUser(id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getAllUsers(params: PaginationParams) {
|
async function getAllUsers(params: PaginationParams) {
|
||||||
const { limit = 5, start = 0, sort_by = 'id', sort_dir = 'asc' } = params;
|
const { limit = 5, start = 0, sort_by = "id", sort_dir = "asc" } = params;
|
||||||
const queryParams = new URLSearchParams({ limit: limit.toFixed(0), start: start.toFixed(0), sort_by, sort_dir });
|
const queryParams = new URLSearchParams({
|
||||||
|
limit: limit.toFixed(0),
|
||||||
|
start: start.toFixed(0),
|
||||||
|
sort_by,
|
||||||
|
sort_dir,
|
||||||
|
});
|
||||||
const url = `users?${queryParams.toString()}`;
|
const url = `users?${queryParams.toString()}`;
|
||||||
const response = await api.get(url);
|
const response = await api.get(url);
|
||||||
return response.json();
|
return response.json();
|
||||||
@@ -244,7 +299,7 @@ async function getAllUsers(params: PaginationParams) {
|
|||||||
async function updateUser(data) {
|
async function updateUser(data) {
|
||||||
const url = `users/edit`;
|
const url = `users/edit`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: data
|
json: data,
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -252,7 +307,7 @@ async function updateUser(data) {
|
|||||||
async function deleteUser(userId: string) {
|
async function deleteUser(userId: string) {
|
||||||
const url = `users/delete`;
|
const url = `users/delete`;
|
||||||
const response = await api.post(url, {
|
const response = await api.post(url, {
|
||||||
json: { id: userId }
|
json: { id: userId },
|
||||||
});
|
});
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@@ -264,13 +319,11 @@ export {
|
|||||||
createNewClient,
|
createNewClient,
|
||||||
updateClient,
|
updateClient,
|
||||||
deleteClient,
|
deleteClient,
|
||||||
|
|
||||||
// Projects
|
// Projects
|
||||||
getOneProject,
|
getOneProject,
|
||||||
getAllProjects,
|
getAllProjects,
|
||||||
createNewProject,
|
createNewProject,
|
||||||
updateProject,
|
updateProject,
|
||||||
|
|
||||||
// Tasks
|
// Tasks
|
||||||
getTasksByProject,
|
getTasksByProject,
|
||||||
createTask,
|
createTask,
|
||||||
@@ -279,7 +332,6 @@ export {
|
|||||||
logTimeOnTask,
|
logTimeOnTask,
|
||||||
completeTask,
|
completeTask,
|
||||||
reopenTask,
|
reopenTask,
|
||||||
|
|
||||||
// Invoices
|
// Invoices
|
||||||
getAllInvoices,
|
getAllInvoices,
|
||||||
getOneInvoice,
|
getOneInvoice,
|
||||||
@@ -290,10 +342,9 @@ export {
|
|||||||
closeInvoice,
|
closeInvoice,
|
||||||
markInvoicePaid,
|
markInvoicePaid,
|
||||||
sendInvoice,
|
sendInvoice,
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
getOneUser,
|
getOneUser,
|
||||||
getAllUsers,
|
getAllUsers,
|
||||||
updateUser,
|
updateUser,
|
||||||
deleteUser
|
deleteUser,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user