feat: list clients and show list of invoice ids when clicked

This commit is contained in:
Tim Bendt
2024-04-19 17:39:39 -06:00
parent efb5de13cc
commit 22cb743835
6 changed files with 115 additions and 18 deletions

View File

@@ -12,6 +12,7 @@
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@biomejs/biome": "1.7.0", "@biomejs/biome": "1.7.0",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.12.7", "@types/node": "^20.12.7",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"esbuild": "0.20.2", "esbuild": "0.20.2",
@@ -19,6 +20,7 @@
"eslint-config-sheriff": "^18.2.0", "eslint-config-sheriff": "^18.2.0",
"eslint-define-config": "^2.1.0", "eslint-define-config": "^2.1.0",
"ky": "^1.2.3", "ky": "^1.2.3",
"lodash-es": "^4.17.21",
"tinyrainbow": "^1.1.1", "tinyrainbow": "^1.1.1",
"tsx": "4.7.2", "tsx": "4.7.2",
"typescript": "5.4.5", "typescript": "5.4.5",

26
pnpm-lock.yaml generated
View File

@@ -8,6 +8,9 @@ dependencies:
'@biomejs/biome': '@biomejs/biome':
specifier: 1.7.0 specifier: 1.7.0
version: registry.npmjs.org/@biomejs/biome@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': '@types/node':
specifier: ^20.12.7 specifier: ^20.12.7
version: registry.npmjs.org/@types/node@20.12.7 version: registry.npmjs.org/@types/node@20.12.7
@@ -29,6 +32,9 @@ dependencies:
ky: ky:
specifier: ^1.2.3 specifier: ^1.2.3
version: registry.npmjs.org/ky@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: tinyrainbow:
specifier: ^1.1.1 specifier: ^1.1.1
version: registry.npmjs.org/tinyrainbow@1.1.1 version: registry.npmjs.org/tinyrainbow@1.1.1
@@ -1166,6 +1172,20 @@ packages:
version: 0.0.29 version: 0.0.29
dev: false 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: 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} 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' name: '@types/node'
@@ -4028,6 +4048,12 @@ packages:
p-locate: registry.npmjs.org/p-locate@6.0.0 p-locate: registry.npmjs.org/p-locate@6.0.0
dev: false 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: 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} 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 name: lodash.get

View File

@@ -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<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
],
}),
]
})

View File

@@ -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
})
)
});

View File

@@ -1,7 +1,7 @@
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 { projectView } from "./components/listProjects.js"; import { clientInvoicesView } from "./components/listClientInvoices.js";
@@ -9,7 +9,7 @@ interceptConsoleLog();
process.title = 'Wretched'; process.title = 'Wretched';
const consoleLog = new ConsoleLog({ const consoleLog = new ConsoleLog({
height: 10, height: 12,
}) })
const [screen, program] = await Screen.start( const [screen, program] = await Screen.start(
async (program) => { async (program) => {
@@ -19,7 +19,7 @@ const [screen, program] = await Screen.start(
child: new utility.TrackMouse({ child: new utility.TrackMouse({
content: Flex.down({ content: Flex.down({
children: [ children: [
['flex1', projectView], ['flex1', clientInvoicesView],
['natural', consoleLog], ['natural', consoleLog],
], ],
}), }),

View File

@@ -9,9 +9,17 @@ 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 = {
id: string,
first_name: string,
last_name: string,
company: string,
total: number,
unpaid_total: number
}
// Clients // Clients
async function getAllClients(params: PaginationParams) { async function getAllClients(params: PaginationParams): Promise<ListResponse<"clients", ClientResponse>> {
const { limit = 5, start = 0, sort_by = 'id', sort_dir = 'asc' } = params; 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();
@@ -146,8 +154,9 @@ async function reopenTask(taskId: string) {
return response.json(); return response.json();
} }
export type InvoiceResponse = { invoice_number: string, client_id: string, amount: string }
// Invoices // Invoices
async function getAllInvoices(params: { client_id: string } & PaginationParams) { async function getAllInvoices(params: { client_id: string } & PaginationParams): Promise<ListResponse<"invoices", InvoiceResponse>> {
const { client_id, limit = 5, start = 0, sort_by = 'id', sort_dir = 'asc' } = params; 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 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()}`;
@@ -155,7 +164,7 @@ async function getAllInvoices(params: { client_id: string } & PaginationParams)
return response.json(); return response.json();
} }
async function getOneInvoice(id: string) { async function getOneInvoice(id: string): Promise<InvoiceResponse> {
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();