akanjs 3.0.0-alpha.25 → 3.0.0-alpha.26
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/client/clientRuntime.ts +2 -0
- package/dictionary/base.dictionary.ts +5 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
- package/package.json +1 -1
- package/signal/serializer/fetch.serializer.ts +4 -0
- package/store/agent/AgentContext.ts +2 -2
- package/store/agent/StoreCatalogue.ts +2 -23
- package/types/client/clientRuntime.d.ts +2 -0
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/store/agent/AgentContext.d.ts +2 -2
- package/types/store/agent/StoreCatalogue.d.ts +1 -1
- package/types/ui/Data/Dashboard.d.ts +3 -2
- package/types/ui/Data/Insight.d.ts +1 -1
- package/types/ui/Data/ListContainer.d.ts +3 -3
- package/types/ui/Data/dataExport.d.ts +5 -0
- package/types/ui/Data/dataText.d.ts +6 -0
- package/types/ui/Model/AdminPanel.d.ts +6 -2
- package/ui/BottomSheet.tsx +1 -1
- package/ui/Data/CardList.tsx +14 -3
- package/ui/Data/Dashboard.tsx +34 -50
- package/ui/Data/Insight.tsx +18 -12
- package/ui/Data/Item.tsx +42 -56
- package/ui/Data/ListContainer.tsx +116 -116
- package/ui/Data/TableList.tsx +22 -13
- package/ui/Data/dataExport.ts +54 -0
- package/ui/Data/dataText.ts +22 -0
- package/ui/KeyboardAvoiding.tsx +2 -2
- package/ui/Layout/Sider.tsx +1 -1
- package/ui/Link/CsrLink.tsx +1 -1
- package/ui/Menu.tsx +1 -1
- package/ui/Model/AdminPanel.tsx +29 -11
- package/ui/Signal/Doc.tsx +2 -2
- package/ui/Signal/RestApi.tsx +2 -2
- package/ui/System/CSR.tsx +1 -1
- package/ui/System/Client.tsx +5 -5
- package/ui/System/DevModeToggle.tsx +1 -1
- package/ui/System/Gtag.tsx +2 -2
- package/ui/System/Messages.tsx +2 -2
- package/ui/System/ThemeToggle.tsx +43 -63
- package/ui/Table.tsx +1 -1
- package/webkit/useFrameRuntime.ts +1 -1
package/client/clientRuntime.ts
CHANGED
|
@@ -47,6 +47,8 @@ type RuntimeFetch = typeof globalThis.fetch & {
|
|
|
47
47
|
serializedSignal: Record<string, unknown>;
|
|
48
48
|
setJwt: (jwt: string | null) => void;
|
|
49
49
|
slice: Record<string, SliceMeta>;
|
|
50
|
+
/** Sort keys per model refName, registered from each serialized signal's filter. */
|
|
51
|
+
sortKeyMap?: Map<string, string[]>;
|
|
50
52
|
ws: RuntimeWs;
|
|
51
53
|
[key: string]: unknown;
|
|
52
54
|
};
|
|
@@ -89,6 +89,11 @@ export const baseDictionary = serviceDictionary(["en", "ko"])
|
|
|
89
89
|
removeMsg: ["Are you sure to remove?", "정말로 삭제하시겠습니까?"],
|
|
90
90
|
confirmMsg: ["Are you sure to {actionType}", "진행하시겠습니까? ({actionType})"],
|
|
91
91
|
perPage: ["/page", "/페이지"],
|
|
92
|
+
refresh: ["Refresh", "새로고침"],
|
|
93
|
+
cardView: ["Card view", "카드 보기"],
|
|
94
|
+
tableView: ["Table view", "표 보기"],
|
|
95
|
+
exportCsv: ["Export CSV", "CSV 내보내기"],
|
|
96
|
+
exportJson: ["Export JSON", "JSON 내보내기"],
|
|
92
97
|
actions: ["Actions", "작업"],
|
|
93
98
|
new: ["New", "신규"],
|
|
94
99
|
edit: ["Edit", "수정"],
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Cls, ENDPOINT_META, PrimitiveRegistry, type PrimitiveScalar, SLICE_META } from "akanjs/base";
|
|
2
2
|
import { Logger } from "akanjs/common";
|
|
3
3
|
import { ConstantRegistry, type ConstantType } from "akanjs/constant";
|
|
4
|
+
import { getFilterMeta } from "akanjs/document";
|
|
4
5
|
import type { LiveRegistry } from "akanjs/service";
|
|
5
6
|
import type {
|
|
6
7
|
ArgInfo,
|
|
@@ -90,9 +91,12 @@ export class FetchSerializer {
|
|
|
90
91
|
for (const [key, endpointInfo] of Object.entries(endpointMeta)) {
|
|
91
92
|
endpoint[key] = FetchSerializer.#serializeEndpoint(endpointInfo);
|
|
92
93
|
}
|
|
94
|
+
|
|
95
|
+
const sortKeys = Object.keys(getFilterMeta(sliceCls.srv.db.filter, { allowEmpty: true })?.sort ?? {});
|
|
93
96
|
return {
|
|
94
97
|
...(prefix ? { prefix } : {}),
|
|
95
98
|
...(Object.keys(slice).length ? { slice } : {}),
|
|
99
|
+
...(sortKeys.length ? { filter: { filter: {}, sortKeys } } : {}),
|
|
96
100
|
...(sliceCls.getGuards.filter((g) => g.name !== "None").length
|
|
97
101
|
? { getGuards: sliceCls.getGuards.map((g) => g.name) }
|
|
98
102
|
: {}),
|
|
@@ -8,8 +8,8 @@ import { ensureStoreSurface } from "./storeSurface";
|
|
|
8
8
|
* The akan default context for a turn: where the user is (route), what is on screen (scopes and their curated
|
|
9
9
|
* resources), and which store keys the mounted components are reading (live keys). Live entries carry names and
|
|
10
10
|
* small primitives only — anything bigger is one `readState` call away, masked — so the block cannot bloat with
|
|
11
|
-
* what happens to be in the store. Base-store
|
|
12
|
-
*
|
|
11
|
+
* what happens to be in the store. Base-store plumbing is kept off the surface by `st.use.x({ agent: false })`
|
|
12
|
+
* at each call site, and the route block carries the three of them that matter.
|
|
13
13
|
*/
|
|
14
14
|
export class AgentContext {
|
|
15
15
|
static of(): AgentContext {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Cls } from "akanjs/base";
|
|
2
2
|
import { ConstantRegistry } from "akanjs/constant";
|
|
3
3
|
import type { AgentRefusal } from "akanjs/signal";
|
|
4
4
|
import { databaseStateModelTypes, databaseStateNames } from "../databaseStateNames";
|
|
5
5
|
import type { SliceStateKey } from "../state";
|
|
6
6
|
import type { StoreInstance } from "../storeInstance";
|
|
7
|
-
import { StoreRegistry } from "../storeRegistry";
|
|
8
7
|
import type { SerializedStoreState } from "./types";
|
|
9
8
|
|
|
10
9
|
/** Which of the model's classes each slice state key holds. The rest hold a primitive or a query descriptor. */
|
|
@@ -26,7 +25,7 @@ const sliceStateModelTypes: { [key in SliceStateKey]?: SerializedStoreState["mod
|
|
|
26
25
|
*
|
|
27
26
|
* Reads stay store-derived because a read has to be masked and only the store declares which model masks it. Which
|
|
28
27
|
* of these keys is readable at a given moment is liveness rather than this catalogue — the keys the mounted
|
|
29
|
-
* components subscribe.
|
|
28
|
+
* components subscribe. Plumbing in the base store opts out at the call site with `{ agent: false }`.
|
|
30
29
|
*
|
|
31
30
|
* It is derived on the client rather than shipped from the server: the store classes are in the bundle already,
|
|
32
31
|
* and a second copy over the wire is a second thing to keep in step.
|
|
@@ -42,30 +41,10 @@ export class StoreCatalogue {
|
|
|
42
41
|
this.state = this.#state();
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
/**
|
|
46
|
-
* The framework's own store keys, held back however widely they are subscribed.
|
|
47
|
-
*
|
|
48
|
-
* `st.use.path()` and friends are read all over an app, so publishing them would put plumbing in the context
|
|
49
|
-
* block of nearly every screen — and `tryJwt` / `tryAccount` hold the caller's credential and are genuinely
|
|
50
|
-
* subscribed (the API explorer's `Doc` and `RestApi` both read them), so a screen reading one must not be able
|
|
51
|
-
* to hand it to a model. The route context block carries the three of these an agent legitimately needs. This
|
|
52
|
-
* is a fixed rule rather than a declaration a store makes, because forgetting it leaks a credential.
|
|
53
|
-
*/
|
|
54
|
-
static #frameworkKeys() {
|
|
55
|
-
return new Set(Object.keys(StoreRegistry.get("base")?.[STATE_META] ?? {}));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
44
|
#state(): { [key: string]: SerializedStoreState } {
|
|
59
45
|
const state = this.#instance.get();
|
|
60
46
|
const declared = StoreCatalogue.#declaredStateModels();
|
|
61
|
-
const framework = StoreCatalogue.#frameworkKeys();
|
|
62
|
-
if (framework.size)
|
|
63
|
-
this.refusals.push({
|
|
64
|
-
key: "base",
|
|
65
|
-
reason: "the framework's own store: routing plumbing an agent cannot act on, and the caller's credential.",
|
|
66
|
-
});
|
|
67
47
|
const entries = Object.keys(state)
|
|
68
|
-
.filter((key) => !framework.has(key))
|
|
69
48
|
.sort()
|
|
70
49
|
.map((key): [string, SerializedStoreState] => {
|
|
71
50
|
const role = this.#instance.sliceStateRoles.get(key);
|
|
@@ -39,6 +39,8 @@ type RuntimeFetch = typeof globalThis.fetch & {
|
|
|
39
39
|
serializedSignal: Record<string, unknown>;
|
|
40
40
|
setJwt: (jwt: string | null) => void;
|
|
41
41
|
slice: Record<string, SliceMeta>;
|
|
42
|
+
/** Sort keys per model refName, registered from each serialized signal's filter. */
|
|
43
|
+
sortKeyMap?: Map<string, string[]>;
|
|
42
44
|
ws: RuntimeWs;
|
|
43
45
|
[key: string]: unknown;
|
|
44
46
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const baseDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], "ping" | "pingBody" | "pingParam" | "pingQuery" | "wsPing" | "pubsubPing", never, "error" | "remove" | "save" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "edit" | "view">;
|
|
1
|
+
export declare const baseDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], "ping" | "pingBody" | "pingParam" | "pingQuery" | "wsPing" | "pubsubPing", never, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view">;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { AgentEndpoint, AgentTurn, BaseEndpoint } from "akanjs/signal";
|
|
2
2
|
export declare const dictionary: {
|
|
3
|
-
base: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "edit" | "view">, never>;
|
|
3
|
+
base: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view">, never>;
|
|
4
4
|
agentTurn: import("./locale.d.ts").DictModule<import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc`, never>;
|
|
5
5
|
agent: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed">;
|
|
6
6
|
};
|
|
7
|
-
export declare const Err: import("./trans.d.ts").ErrConstructor<"agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed">, translate: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, data?: import("./trans.d.ts").TranslationData) => string, msg: {
|
|
8
|
-
info: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
9
|
-
success: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
10
|
-
error: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
11
|
-
warning: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
12
|
-
loading: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
13
|
-
}, getDictionary: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, __Error_Key__: "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed";
|
|
7
|
+
export declare const Err: import("./trans.d.ts").ErrConstructor<"agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed">, translate: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, data?: import("./trans.d.ts").TranslationData) => string, msg: {
|
|
8
|
+
info: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
9
|
+
success: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
10
|
+
error: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
11
|
+
warning: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
12
|
+
loading: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
13
|
+
}, getDictionary: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, __Error_Key__: "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed";
|
|
@@ -5,8 +5,8 @@ import type { AgentBridge } from "./AgentBridge.d.ts";
|
|
|
5
5
|
* The akan default context for a turn: where the user is (route), what is on screen (scopes and their curated
|
|
6
6
|
* resources), and which store keys the mounted components are reading (live keys). Live entries carry names and
|
|
7
7
|
* small primitives only — anything bigger is one `readState` call away, masked — so the block cannot bloat with
|
|
8
|
-
* what happens to be in the store. Base-store
|
|
9
|
-
*
|
|
8
|
+
* what happens to be in the store. Base-store plumbing is kept off the surface by `st.use.x({ agent: false })`
|
|
9
|
+
* at each call site, and the route block carries the three of them that matter.
|
|
10
10
|
*/
|
|
11
11
|
export declare class AgentContext {
|
|
12
12
|
#private;
|
|
@@ -11,7 +11,7 @@ import type { SerializedStoreState } from "./types.d.ts";
|
|
|
11
11
|
*
|
|
12
12
|
* Reads stay store-derived because a read has to be masked and only the store declares which model masks it. Which
|
|
13
13
|
* of these keys is readable at a given moment is liveness rather than this catalogue — the keys the mounted
|
|
14
|
-
* components subscribe.
|
|
14
|
+
* components subscribe. Plumbing in the base store opts out at the call site with `{ agent: false }`.
|
|
15
15
|
*
|
|
16
16
|
* It is derived on the client rather than shipped from the server: the store classes are in the bundle already,
|
|
17
17
|
* and a second copy over the wire is a second thing to keep in step.
|
|
@@ -3,9 +3,10 @@ export interface DashboardProps<T extends string, State> {
|
|
|
3
3
|
className?: string;
|
|
4
4
|
summary: Record<string, unknown>;
|
|
5
5
|
slice: SliceMeta;
|
|
6
|
-
|
|
6
|
+
/** Columns that narrow the listing when clicked. A column absent from the map renders as a plain tile. */
|
|
7
|
+
queryMap?: Record<string, unknown>;
|
|
7
8
|
columns?: string[];
|
|
8
9
|
presents?: string[];
|
|
9
10
|
hidePresents?: boolean;
|
|
10
11
|
}
|
|
11
|
-
export default function Dashboard<T extends string, State>({ className, summary, slice, queryMap, columns, presents, hidePresents, }: DashboardProps<T, State>): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export default function Dashboard<T extends string, State>({ className, summary, slice, queryMap, columns, presents, hidePresents, }: DashboardProps<T, State>): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -5,5 +5,5 @@ interface InsightProps<T extends string, Insight> {
|
|
|
5
5
|
slice: SliceMeta;
|
|
6
6
|
columns?: (keyof Insight)[];
|
|
7
7
|
}
|
|
8
|
-
export default function Insight<T extends string, Insight>({ className, insight, slice, columns, }: InsightProps<T, Insight>): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default function Insight<T extends string, Insight>({ className, insight, slice, columns, }: InsightProps<T, Insight>): import("react/jsx-runtime").JSX.Element | null;
|
|
9
9
|
export {};
|
|
@@ -10,11 +10,11 @@ export interface ListContainerProps<T extends string, State, Input, Full extends
|
|
|
10
10
|
className?: string;
|
|
11
11
|
/** Additional classes for card-list rendering. */
|
|
12
12
|
cardListClassName?: string;
|
|
13
|
-
/**
|
|
13
|
+
/** Initial rendering mode. The toolbar toggle switches it from here. */
|
|
14
14
|
type?: "card" | "list";
|
|
15
|
-
/** Static query object passed
|
|
15
|
+
/** Static query object passed as the first argument of the generated init action. */
|
|
16
16
|
query?: Record<string, unknown>;
|
|
17
|
-
/** Initial fetch form
|
|
17
|
+
/** Initial fetch form: page, limit, sort, and the default values a new model starts from. */
|
|
18
18
|
init?: FetchInitForm<Input, any>;
|
|
19
19
|
/** Generated slice metadata for the target model. */
|
|
20
20
|
slice: SliceMeta;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DataColumn } from "akanjs/client";
|
|
2
|
+
export declare const columnKey: (column: DataColumn<any>) => string;
|
|
3
|
+
export declare const toCsvBlob: (columns: DataColumn<any>[], models: Record<string, unknown>[], title: (column: DataColumn<any>) => string) => Blob;
|
|
4
|
+
export declare const toJsonBlob: (models: unknown[]) => Blob;
|
|
5
|
+
export declare const downloadBlob: (blob: Blob, filename: string) => void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const humanize: (name: string) => string;
|
|
2
|
+
/** A dictionary miss returns the key itself (`makeTrans`), so a raw `user.insight.count` would ship as the label. */
|
|
3
|
+
export declare const dictLabel: (translate: (key: string) => string, key: string, name: string) => string;
|
|
4
|
+
/** A column with no renderer still has to print something a React child accepts, objects and arrays included. */
|
|
5
|
+
export declare const formatCell: (value: unknown) => string;
|
|
6
|
+
export declare const formatStat: (value: unknown) => string;
|
|
@@ -6,6 +6,7 @@ interface AdminPanelProps<T extends string, State, Input, Full extends {
|
|
|
6
6
|
id: string;
|
|
7
7
|
}> extends ListContainerProps<T, State, Input, Full, Light> {
|
|
8
8
|
slice: SliceMeta;
|
|
9
|
+
/** Generated `<Model>.Unit` / `.Template` / `.View` namespaces. A role without a `General` export is skipped. */
|
|
9
10
|
components: {
|
|
10
11
|
Template: {
|
|
11
12
|
[key: string]: any;
|
|
@@ -17,15 +18,18 @@ interface AdminPanelProps<T extends string, State, Input, Full extends {
|
|
|
17
18
|
[key: string]: any;
|
|
18
19
|
};
|
|
19
20
|
};
|
|
21
|
+
/** Summary column to query descriptor. Only a column this map names becomes a link to its filtered listing. */
|
|
20
22
|
queryMap?: {
|
|
21
|
-
[key: string]:
|
|
23
|
+
[key: string]: unknown;
|
|
22
24
|
};
|
|
25
|
+
/** Keys of the app's `summary` state shown as dashboard tiles above the list. */
|
|
23
26
|
summaryColumns?: string[];
|
|
27
|
+
/** Model insight keys shown above the list. The header already carries the total count. */
|
|
24
28
|
insightColumns?: string[];
|
|
25
29
|
}
|
|
26
30
|
export default function AdminPanel<RefName extends string, State, Input, Full extends {
|
|
27
31
|
id: string;
|
|
28
32
|
}, Light extends {
|
|
29
33
|
id: string;
|
|
30
|
-
}>({ slice, components, summaryColumns, insightColumns, renderInsight, renderDashboard, ...props }: AdminPanelProps<RefName, State, Input, Full, Light>): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
}>({ slice, components, queryMap, summaryColumns, insightColumns, renderInsight, renderDashboard, ...props }: AdminPanelProps<RefName, State, Input, Full, Light>): import("react/jsx-runtime").JSX.Element;
|
|
31
35
|
export {};
|
package/ui/BottomSheet.tsx
CHANGED
|
@@ -23,7 +23,7 @@ export interface BottomSheetRef {
|
|
|
23
23
|
export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>(
|
|
24
24
|
({ open, onCancel, type = "half", children }: BottomSheetProps, bottomSheetRef) => {
|
|
25
25
|
const ref = useRef<HTMLDivElement>(null);
|
|
26
|
-
const pageState = st.use.pageState();
|
|
26
|
+
const pageState = st.use.pageState({ agent: false });
|
|
27
27
|
|
|
28
28
|
const [{ y, opacity }, api] = useSpring(() => ({ y: window.innerHeight, opacity: 0 }));
|
|
29
29
|
|
package/ui/Data/CardList.tsx
CHANGED
|
@@ -6,11 +6,14 @@ import type { FilterInstance } from "akanjs/document";
|
|
|
6
6
|
import type { FetchInitForm, SliceMeta } from "akanjs/fetch";
|
|
7
7
|
import { st } from "akanjs/store";
|
|
8
8
|
import { type ReactNode, useEffect } from "react";
|
|
9
|
+
import { Empty } from "../Empty";
|
|
9
10
|
import { Loading } from "../Loading";
|
|
10
11
|
import { Model } from "../Model";
|
|
11
12
|
import DataItem from "./Item";
|
|
12
13
|
import DataPagination from "./Pagination";
|
|
13
14
|
|
|
15
|
+
const gridClassName = "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4";
|
|
16
|
+
|
|
14
17
|
type DataItemProps<T extends string, M extends { id: string }, L extends { id: string }> = {
|
|
15
18
|
[key in T]: L;
|
|
16
19
|
} & { slice: SliceMeta };
|
|
@@ -86,14 +89,22 @@ export default function CardList<
|
|
|
86
89
|
return (
|
|
87
90
|
<div className={className}>
|
|
88
91
|
{modelListLoading ? (
|
|
89
|
-
<div className={cn(
|
|
92
|
+
<div className={cn(gridClassName, cardListClassName)}>
|
|
90
93
|
{new Array(limitOfModel || 20)
|
|
91
94
|
.fill(0)
|
|
92
95
|
.map((_, idx) => `skeleton-${idx}`)
|
|
93
|
-
.map((key) =>
|
|
96
|
+
.map((key) =>
|
|
97
|
+
renderLoading ? (
|
|
98
|
+
renderLoading()
|
|
99
|
+
) : (
|
|
100
|
+
<Loading.Skeleton key={key} className="rounded-box border border-border bg-card p-4" active />
|
|
101
|
+
),
|
|
102
|
+
)}
|
|
94
103
|
</div>
|
|
104
|
+
) : !modelList.length ? (
|
|
105
|
+
<Empty />
|
|
95
106
|
) : (
|
|
96
|
-
<div className={cn(
|
|
107
|
+
<div className={cn(gridClassName, cardListClassName)}>
|
|
97
108
|
{modelList.map((model, idx) => {
|
|
98
109
|
return (
|
|
99
110
|
<DataItem
|
package/ui/Data/Dashboard.tsx
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn, usePage } from "akanjs/client";
|
|
3
|
-
import { capitalize } from "akanjs/common";
|
|
4
3
|
import type { SliceMeta } from "akanjs/fetch";
|
|
5
4
|
import { st } from "akanjs/store";
|
|
6
5
|
|
|
7
|
-
import { buttonRecipe } from "../Button";
|
|
8
6
|
import { Link } from "../Link";
|
|
7
|
+
import { dictLabel, formatStat } from "./dataText";
|
|
9
8
|
|
|
10
9
|
export interface DashboardProps<T extends string, State> {
|
|
11
10
|
className?: string;
|
|
12
11
|
summary: Record<string, unknown>;
|
|
13
12
|
slice: SliceMeta;
|
|
14
|
-
|
|
13
|
+
/** Columns that narrow the listing when clicked. A column absent from the map renders as a plain tile. */
|
|
14
|
+
queryMap?: Record<string, unknown>;
|
|
15
15
|
columns?: string[];
|
|
16
16
|
presents?: string[];
|
|
17
17
|
hidePresents?: boolean;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const tileClassName = "flex min-w-40 flex-1 flex-col gap-1 rounded-box border px-4 py-3 text-left";
|
|
21
|
+
|
|
20
22
|
export default function Dashboard<T extends string, State>({
|
|
21
23
|
className,
|
|
22
24
|
summary,
|
|
@@ -26,56 +28,38 @@ export default function Dashboard<T extends string, State>({
|
|
|
26
28
|
presents,
|
|
27
29
|
hidePresents,
|
|
28
30
|
}: DashboardProps<T, State>) {
|
|
29
|
-
const { refName
|
|
31
|
+
const { refName } = slice;
|
|
30
32
|
const { l } = usePage();
|
|
31
|
-
const searchParams = st.use.searchParams();
|
|
33
|
+
const searchParams = st.use.searchParams({ agent: false });
|
|
32
34
|
const filter = Array.isArray(searchParams.filter) ? searchParams.filter[0] : searchParams.filter;
|
|
33
|
-
const [
|
|
34
|
-
const
|
|
35
|
-
|
|
35
|
+
const shownColumns = (columns ?? []).filter((column) => summary[column] !== undefined);
|
|
36
|
+
const shownPresents = hidePresents ? [] : (presents ?? []).filter((column) => summary[column] !== undefined);
|
|
37
|
+
if (!shownColumns.length && !shownPresents.length) return null;
|
|
36
38
|
return (
|
|
37
|
-
<div className={cn("
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{!hidePresents
|
|
62
|
-
? presents?.map(
|
|
63
|
-
(column) =>
|
|
64
|
-
summary[column] !== undefined &&
|
|
65
|
-
queryMap[column] !== undefined && (
|
|
66
|
-
<button
|
|
67
|
-
key={column}
|
|
68
|
-
className={buttonRecipe({ variant: "ghost" }, "mx-1 h-32 w-48 rounded-none border-none pt-3")}
|
|
69
|
-
>
|
|
70
|
-
<div className="flex flex-col gap-1">
|
|
71
|
-
<div className="text-foreground/60 text-xs">{l(`summary.${column}` as "base.new")}</div>
|
|
72
|
-
<div className="font-semibold text-2xl text-primary">{formatSummaryValue(summary[column])}</div>
|
|
73
|
-
</div>
|
|
74
|
-
</button>
|
|
75
|
-
),
|
|
76
|
-
)
|
|
77
|
-
: null}
|
|
78
|
-
</div>
|
|
39
|
+
<div className={cn("mb-4 flex flex-wrap gap-2", className)}>
|
|
40
|
+
{[...shownColumns, ...shownPresents].map((column) => {
|
|
41
|
+
const linkable = queryMap?.[column] !== undefined;
|
|
42
|
+
return (
|
|
43
|
+
<Link
|
|
44
|
+
key={column}
|
|
45
|
+
disabled={!linkable}
|
|
46
|
+
href={`/admin?topMenu=data&subMenu=${refName}&filter=${column}`}
|
|
47
|
+
className={cn(
|
|
48
|
+
tileClassName,
|
|
49
|
+
"bg-card",
|
|
50
|
+
linkable && "transition hover:border-primary/50",
|
|
51
|
+
filter === column && linkable ? "border-primary" : "border-border",
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
<span className="truncate text-muted-foreground text-xs">
|
|
55
|
+
{dictLabel(l._, `summary.${column}`, column)}
|
|
56
|
+
</span>
|
|
57
|
+
<span className="truncate font-semibold text-2xl text-primary tabular-nums">
|
|
58
|
+
{formatStat(summary[column])}
|
|
59
|
+
</span>
|
|
60
|
+
</Link>
|
|
61
|
+
);
|
|
62
|
+
})}
|
|
79
63
|
</div>
|
|
80
64
|
);
|
|
81
65
|
}
|
package/ui/Data/Insight.tsx
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { cn, usePage } from "akanjs/client";
|
|
3
3
|
import type { SliceMeta } from "akanjs/fetch";
|
|
4
4
|
|
|
5
|
+
import { dictLabel, formatStat } from "./dataText";
|
|
6
|
+
|
|
5
7
|
interface InsightProps<T extends string, Insight> {
|
|
6
8
|
className?: string;
|
|
7
9
|
insight: Insight;
|
|
@@ -17,19 +19,23 @@ export default function Insight<T extends string, Insight>({
|
|
|
17
19
|
}: InsightProps<T, Insight>) {
|
|
18
20
|
const { l } = usePage();
|
|
19
21
|
const { refName } = slice;
|
|
22
|
+
const cells = (columns ?? []).filter((column) => insight[column] !== undefined && insight[column] !== null);
|
|
23
|
+
if (!cells.length) return null;
|
|
20
24
|
return (
|
|
21
|
-
<div className={cn("
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
<div className={cn("mb-4 grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4", className)}>
|
|
26
|
+
{cells.map((column) => {
|
|
27
|
+
const name = String(column);
|
|
28
|
+
return (
|
|
29
|
+
<div key={name} className="rounded-box border border-border bg-card px-4 py-3">
|
|
30
|
+
<div className="truncate text-muted-foreground text-xs">
|
|
31
|
+
{dictLabel(l._, `${refName}.insight.${name}`, name)}
|
|
32
|
+
</div>
|
|
33
|
+
<div className="mt-1 truncate font-semibold text-foreground text-xl tabular-nums">
|
|
34
|
+
{formatStat(insight[column])}
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
})}
|
|
33
39
|
</div>
|
|
34
40
|
);
|
|
35
41
|
}
|