@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.
|
|
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
|
|
10
|
-
//
|
|
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
|
|
99
|
-
//
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
}
|