From 22cb7438358b1c305548d0d83fd3c04109c6ab5b Mon Sep 17 00:00:00 2001 From: Tim Bendt Date: Fri, 19 Apr 2024 17:39:39 -0600 Subject: [PATCH] feat: list clients and show list of invoice ids when clicked --- package.json | 2 + pnpm-lock.yaml | 26 +++++++ src/components/listClientInvoices.ts | 71 +++++++++++++++++++ src/components/listProjects.ts | 11 --- src/index.ts | 6 +- src/services/{api-client.ts => pancakeApi.ts} | 17 +++-- 6 files changed, 115 insertions(+), 18 deletions(-) create mode 100644 src/components/listClientInvoices.ts delete mode 100644 src/components/listProjects.ts rename src/services/{api-client.ts => pancakeApi.ts} (92%) diff --git a/package.json b/package.json index ae09524..eaab981 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "license": "UNLICENSED", "dependencies": { "@biomejs/biome": "1.7.0", + "@types/lodash-es": "^4.17.12", "@types/node": "^20.12.7", "dotenv": "^16.4.5", "esbuild": "0.20.2", @@ -19,6 +20,7 @@ "eslint-config-sheriff": "^18.2.0", "eslint-define-config": "^2.1.0", "ky": "^1.2.3", + "lodash-es": "^4.17.21", "tinyrainbow": "^1.1.1", "tsx": "4.7.2", "typescript": "5.4.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b3395a..4953219 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ dependencies: '@biomejs/biome': specifier: 1.7.0 version: registry.npmjs.org/@biomejs/biome@1.7.0 + '@types/lodash-es': + specifier: ^4.17.12 + version: registry.npmjs.org/@types/lodash-es@4.17.12 '@types/node': specifier: ^20.12.7 version: registry.npmjs.org/@types/node@20.12.7 @@ -29,6 +32,9 @@ dependencies: ky: specifier: ^1.2.3 version: registry.npmjs.org/ky@1.2.3 + lodash-es: + specifier: ^4.17.21 + version: registry.npmjs.org/lodash-es@4.17.21 tinyrainbow: specifier: ^1.1.1 version: registry.npmjs.org/tinyrainbow@1.1.1 @@ -1166,6 +1172,20 @@ packages: version: 0.0.29 dev: false + registry.npmjs.org/@types/lodash-es@4.17.12: + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==, registry: https://gitlab.internal.granular.ag/api/v4/packages/npm, tarball: https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz} + name: '@types/lodash-es' + version: 4.17.12 + dependencies: + '@types/lodash': registry.npmjs.org/@types/lodash@4.17.0 + dev: false + + registry.npmjs.org/@types/lodash@4.17.0: + resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==, registry: https://gitlab.internal.granular.ag/api/v4/packages/npm, tarball: https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz} + name: '@types/lodash' + version: 4.17.0 + dev: false + registry.npmjs.org/@types/node@20.12.7: resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==, registry: https://gitlab.internal.granular.ag/api/v4/packages/npm, tarball: https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz} name: '@types/node' @@ -4028,6 +4048,12 @@ packages: p-locate: registry.npmjs.org/p-locate@6.0.0 dev: false + registry.npmjs.org/lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==, registry: https://gitlab.internal.granular.ag/api/v4/packages/npm, tarball: https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz} + name: lodash-es + version: 4.17.21 + dev: false + registry.npmjs.org/lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==, registry: https://gitlab.internal.granular.ag/api/v4/packages/npm, tarball: https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz} name: lodash.get diff --git a/src/components/listClientInvoices.ts b/src/components/listClientInvoices.ts new file mode 100644 index 0000000..99c66aa --- /dev/null +++ b/src/components/listClientInvoices.ts @@ -0,0 +1,71 @@ + +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 = 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 + ], + }), + ] +}) diff --git a/src/components/listProjects.ts b/src/components/listProjects.ts deleted file mode 100644 index ef56b39..0000000 --- a/src/components/listProjects.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Button, Flex } from "wretched"; -import { getAllProjects } from "../services/api-client.js"; - -const projectsResp = await getAllProjects({ limit: 100, sort_by: "date_updated", sort_dir: "desc" }); - -export const projectView = Flex.down({ - children: projectsResp.projects.map(x => new Button({ - text: x.name - }) - ) -}); diff --git a/src/index.ts b/src/index.ts index 0736f81..9c3d0bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import 'dotenv/config' import { Screen, Box, Flow, Text, Button, interceptConsoleLog, ConsoleLog, iTerm2, Window, Flex } from 'wretched' import * as utility from "wretched/dist/components/utility"; -import { projectView } from "./components/listProjects.js"; +import { clientInvoicesView } from "./components/listClientInvoices.js"; @@ -9,7 +9,7 @@ interceptConsoleLog(); process.title = 'Wretched'; const consoleLog = new ConsoleLog({ - height: 10, + height: 12, }) const [screen, program] = await Screen.start( async (program) => { @@ -19,7 +19,7 @@ const [screen, program] = await Screen.start( child: new utility.TrackMouse({ content: Flex.down({ children: [ - ['flex1', projectView], + ['flex1', clientInvoicesView], ['natural', consoleLog], ], }), diff --git a/src/services/api-client.ts b/src/services/pancakeApi.ts similarity index 92% rename from src/services/api-client.ts rename to src/services/pancakeApi.ts index 2fff533..b5e5b9d 100644 --- a/src/services/api-client.ts +++ b/src/services/pancakeApi.ts @@ -9,9 +9,17 @@ console.log("🚀 ~ API_URL:", API_URL) 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' } +export type ClientResponse = { + id: string, + first_name: string, + last_name: string, + company: string, + total: number, + unpaid_total: number +} // Clients -async function getAllClients(params: PaginationParams) { - const { limit = 5, start = 0, sort_by = 'id', sort_dir = 'asc' } = params; +async function getAllClients(params: PaginationParams): Promise> { + 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 response = api.get(url); return response.json(); @@ -146,8 +154,9 @@ async function reopenTask(taskId: string) { return response.json(); } +export type InvoiceResponse = { invoice_number: string, client_id: string, amount: string } // Invoices -async function getAllInvoices(params: { client_id: string } & PaginationParams) { +async function getAllInvoices(params: { client_id: string } & PaginationParams): Promise> { const { client_id, limit = 5, 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()}`; @@ -155,7 +164,7 @@ async function getAllInvoices(params: { client_id: string } & PaginationParams) return response.json(); } -async function getOneInvoice(id: string) { +async function getOneInvoice(id: string): Promise { const url = `invoices/show?id=${id}`; const response = await api.get(url); return response.json();