feat: list clients and show list of invoice ids when clicked
This commit is contained in:
@@ -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",
|
||||
|
||||
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
71
src/components/listClientInvoices.ts
Normal file
71
src/components/listClientInvoices.ts
Normal 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
|
||||
],
|
||||
}),
|
||||
]
|
||||
})
|
||||
@@ -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
|
||||
})
|
||||
)
|
||||
});
|
||||
@@ -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],
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -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<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 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<ListResponse<"invoices", InvoiceResponse>> {
|
||||
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<InvoiceResponse> {
|
||||
const url = `invoices/show?id=${id}`;
|
||||
const response = await api.get(url);
|
||||
return response.json();
|
||||
Reference in New Issue
Block a user