@kahitsan/ksui 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kahitsan/ksui",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "ksui is a set of shared SolidJS UI components plus the @kserp/host-ui type contract for KahitSan/Hilinga plugins. Published to the public npm registry and consumed as a normal dependency. Ships source under a `solid` export condition so the consumer's vite-plugin-solid compiles it with solid-js + @kserp/host-ui externalized to the host runtime.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -6,8 +6,8 @@
6
6
  //
7
7
  // It is DOMAIN-FREE: it knows nothing about clients or payees. The caller wires
8
8
  // the data via `search` / `onCreate` and the display via `idOf` / `labelOf` /
9
- // `secondaryOf` / `icon` / `noun`. Thin presets (ClientPicker, PayeePicker) pass
10
- // those in, so there is exactly one copy of the popup mechanics.
9
+ // `secondaryOf` / `icon` / `noun` at each call site (e.g. a payee or client
10
+ // picker), so there is exactly one copy of the popup mechanics.
11
11
  //
12
12
  // `selectedName` is a free-text fallback shown in the trigger when nothing is
13
13
  // picked (e.g. a likely default) — handy when the backing API persists the name
@@ -0,0 +1,25 @@
1
+ // Domain option shapes shared across the KahitSan plugins.
2
+ //
3
+ // These previously lived inside the ClientPicker / PayeePicker preset
4
+ // components. Those presets were removed in favour of using the generic
5
+ // EntityPicker directly (consumers wire their own search/onCreate), but the
6
+ // option TYPES are still imported widely (transactions, counter, payees), so
7
+ // they keep a stable home here decoupled from any component.
8
+
9
+ export type PayeeKind = "vendor" | "customer" | "both";
10
+
11
+ export interface PayeeOption {
12
+ id: number;
13
+ name: string;
14
+ kind: PayeeKind;
15
+ default_subcategory?: string | null;
16
+ notes?: string | null;
17
+ is_active?: boolean;
18
+ }
19
+
20
+ export interface ClientOption {
21
+ id: number;
22
+ name_raw: string;
23
+ email?: string | null;
24
+ phone?: string | null;
25
+ }
package/src/index.ts CHANGED
@@ -95,16 +95,16 @@ export type { MentionTextareaProps } from "./components/composite/MentionTextare
95
95
  export { default as MarkdownNotes } from "./components/composite/MarkdownNotes";
96
96
  export type { MarkdownNotesProps } from "./components/composite/MarkdownNotes";
97
97
 
98
- // The generic searchable-combobox engine. ClientPicker / PayeePicker are thin
99
- // presets over it; new pickers can use EntityPicker directly.
98
+ // The generic searchable-combobox engine the ONE picker the library ships.
99
+ // Build a payee / client / anything picker by supplying search + onCreate +
100
+ // idOf/labelOf/secondaryOf/icon/noun. (The former ClientPicker / PayeePicker
101
+ // presets were removed; consumers wire the endpoint themselves.)
100
102
  export { default as EntityPicker } from "./components/composite/EntityPicker";
101
103
  export type { EntityPickerProps } from "./components/composite/EntityPicker";
102
104
 
103
- export { default as ClientPicker } from "./components/composite/ClientPicker";
104
- export type { ClientOption } from "./components/composite/ClientPicker";
105
-
106
- export { default as PayeePicker } from "./components/composite/PayeePicker";
107
- export type { PayeeOption, PayeeKind } from "./components/composite/PayeePicker";
105
+ // Shared domain option shapes for the common pickers, decoupled from any
106
+ // component (still imported across transactions / counter / payees).
107
+ export type { ClientOption, PayeeOption, PayeeKind } from "./components/composite/picker-types";
108
108
 
109
109
  export { default as VoucherPicker, calculateDiscount } from "./components/composite/VoucherPicker";
110
110
  export type { VoucherOption } from "./components/composite/VoucherPicker";
@@ -1,86 +0,0 @@
1
- // ClientPicker — the client preset of EntityPicker. A searchable combobox for a
2
- // "billed-to" / client field, backed by the sibling clients plugin's
3
- // /api/clients. All the popup mechanics live in EntityPicker; this file only
4
- // wires the clients endpoint, option shape (name_raw + email/phone), icon, and
5
- // the post-create callback.
6
- //
7
- // Public API is unchanged from the standalone version, so existing callers keep
8
- // working. Degrades gracefully when the clients plugin isn't deployed.
9
-
10
- import { type JSX } from "solid-js";
11
- import UserRound from "lucide-solid/icons/user-round";
12
- import EntityPicker from "./EntityPicker";
13
-
14
- export interface ClientOption {
15
- id: number;
16
- name_raw: string;
17
- email?: string | null;
18
- phone?: string | null;
19
- }
20
-
21
- interface ClientPickerProps {
22
- selected: ClientOption | null;
23
- onChange: (next: ClientOption | null) => void;
24
- onCreate?: (created: ClientOption) => void;
25
- disabled?: boolean;
26
- defaultOpen?: boolean;
27
- }
28
-
29
- async function searchClients(query: string): Promise<ClientOption[]> {
30
- const params = new URLSearchParams({ status: "active", limit: "10" });
31
- if (query) params.set("search", query);
32
- const r = await fetch(`/api/clients?${params.toString()}`, { credentials: "include" });
33
- if (!r.ok) {
34
- throw new Error(
35
- r.status === 403
36
- ? "Permission denied"
37
- : r.status === 404
38
- ? "Clients module isn't available — type a name instead"
39
- : "Failed to load",
40
- );
41
- }
42
- const json = await r.json();
43
- return (json.data || []) as ClientOption[];
44
- }
45
-
46
- async function createClient(name: string): Promise<ClientOption> {
47
- const res = await fetch("/api/clients", {
48
- method: "POST",
49
- credentials: "include",
50
- headers: { "Content-Type": "application/json" },
51
- body: JSON.stringify({ name_raw: name }),
52
- });
53
- if (!res.ok && res.status !== 200) {
54
- const body = await res.json().catch(() => ({ error: "Failed to create client" }));
55
- throw new Error(body.error || "Failed to create client");
56
- }
57
- return (await res.json()) as ClientOption;
58
- }
59
-
60
- function clientSecondary(c: ClientOption): string | null {
61
- return [c.email, c.phone].filter(Boolean).join(" · ") || null;
62
- }
63
-
64
- export default function ClientPicker(props: ClientPickerProps): JSX.Element {
65
- return (
66
- <EntityPicker<ClientOption>
67
- selected={props.selected}
68
- onChange={props.onChange}
69
- search={searchClients}
70
- onCreate={async (name) => {
71
- const created = await createClient(name);
72
- props.onCreate?.(created);
73
- return created;
74
- }}
75
- idOf={(c) => c.id}
76
- labelOf={(c) => c.name_raw}
77
- secondaryOf={clientSecondary}
78
- icon={UserRound}
79
- noun="client"
80
- placeholder="Walk-in"
81
- disabled={props.disabled}
82
- defaultOpen={props.defaultOpen}
83
- testIdPrefix="client-picker"
84
- />
85
- );
86
- }
@@ -1,91 +0,0 @@
1
- // PayeePicker — the payee preset of EntityPicker. A searchable combobox for the
2
- // "Paid to" / "Received from" / "Payable to" field, backed by the sibling payees
3
- // plugin's /api/payees. All the popup mechanics live in EntityPicker; this file
4
- // only wires the payee endpoint, option shape, icon, and create-as-kind.
5
- //
6
- // Public API is unchanged from the standalone version, so existing callers keep
7
- // working. Degrades gracefully — a missing payees plugin surfaces a notice and
8
- // the free-text fallback (selectedName) keeps the trigger usable.
9
-
10
- import { type JSX } from "solid-js";
11
- import Store from "lucide-solid/icons/store";
12
- import EntityPicker from "./EntityPicker";
13
-
14
- export type PayeeKind = "vendor" | "customer" | "both";
15
-
16
- export interface PayeeOption {
17
- id: number;
18
- name: string;
19
- kind: PayeeKind;
20
- default_subcategory?: string | null;
21
- notes?: string | null;
22
- is_active?: boolean;
23
- }
24
-
25
- interface PayeePickerProps {
26
- selected: PayeeOption | null;
27
- selectedName?: string | null;
28
- kind?: PayeeKind;
29
- createAsKind?: PayeeKind;
30
- placeholder?: string;
31
- onChange: (next: PayeeOption | null) => void;
32
- disabled?: boolean;
33
- testIdPrefix?: string;
34
- }
35
-
36
- async function searchPayees(query: string, kind?: PayeeKind): Promise<PayeeOption[]> {
37
- const params = new URLSearchParams({ status: "active", limit: "20" });
38
- if (query) params.set("search", query);
39
- if (kind) params.set("kind", kind);
40
- const r = await fetch(`/api/payees?${params.toString()}`, { credentials: "include" });
41
- if (!r.ok) {
42
- throw new Error(
43
- r.status === 403
44
- ? "Permission denied"
45
- : r.status === 404
46
- ? "Payees module isn't available — type a name instead"
47
- : "Failed to load",
48
- );
49
- }
50
- const json = await r.json();
51
- return (json.data || []) as PayeeOption[];
52
- }
53
-
54
- async function createPayee(name: string, kind: PayeeKind): Promise<PayeeOption> {
55
- const res = await fetch("/api/payees", {
56
- method: "POST",
57
- credentials: "include",
58
- headers: { "Content-Type": "application/json" },
59
- body: JSON.stringify({ name, kind }),
60
- });
61
- if (!res.ok && res.status !== 200) {
62
- const body = await res.json().catch(() => ({ error: "Failed to create payee" }));
63
- throw new Error(body.error || "Failed to create payee");
64
- }
65
- return (await res.json()) as PayeeOption;
66
- }
67
-
68
- function payeeSecondary(p: PayeeOption): string | null {
69
- if (!p.default_subcategory && p.kind === "vendor") return null;
70
- return [p.kind === "vendor" ? null : p.kind, p.default_subcategory].filter(Boolean).join(" · ") || null;
71
- }
72
-
73
- export default function PayeePicker(props: PayeePickerProps): JSX.Element {
74
- return (
75
- <EntityPicker<PayeeOption>
76
- selected={props.selected}
77
- selectedName={props.selectedName}
78
- onChange={props.onChange}
79
- search={(q) => searchPayees(q, props.kind)}
80
- onCreate={(name) => createPayee(name, props.createAsKind ?? props.kind ?? "vendor")}
81
- idOf={(p) => p.id}
82
- labelOf={(p) => p.name}
83
- secondaryOf={payeeSecondary}
84
- icon={Store}
85
- noun="payee"
86
- placeholder={props.placeholder}
87
- disabled={props.disabled}
88
- testIdPrefix={props.testIdPrefix ?? "payee-picker"}
89
- />
90
- );
91
- }