akanjs 3.0.0-alpha.62 → 3.0.0-alpha.63
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/constant/fieldQueryMeta.ts +35 -0
- package/constant/index.ts +1 -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/types/constant/fieldQueryMeta.d.ts +17 -0
- package/types/constant/index.d.ts +1 -0
- package/types/ui/Data/Dashboard.d.ts +7 -2
- package/types/ui/Model/AdminPanel.d.ts +7 -2
- package/ui/Data/Dashboard.tsx +18 -3
- package/ui/Model/AdminPanel.tsx +8 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FIELD_META } from "akanjs/base";
|
|
2
|
+
import { ConstantRegistry } from "./constantRegistry";
|
|
3
|
+
import type { FieldObject } from "./fieldInfo";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The query a field's value stands for, declared on the field itself with `.meta(...)`. A summary counter is the
|
|
7
|
+
* case this exists for: `hau` is however many rows one filter returns, so the tile showing it can open that exact
|
|
8
|
+
* listing without the page restating the filter beside it.
|
|
9
|
+
*
|
|
10
|
+
* The shape is read structurally rather than by class, so an app declares it with whatever builder it already has.
|
|
11
|
+
*/
|
|
12
|
+
export interface FieldQueryMeta {
|
|
13
|
+
/** The model the query runs against. */
|
|
14
|
+
refName: string;
|
|
15
|
+
/** One of that model's declared filter keys. */
|
|
16
|
+
queryKey: string | null;
|
|
17
|
+
/** Read when the query is applied, so an arg relative to now — `() => [dayjs().subtract(1, "hour")]` — is current. */
|
|
18
|
+
queryArgs?: unknown[] | (() => unknown[]);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const isFieldQueryMeta = (meta: unknown): meta is FieldQueryMeta => {
|
|
22
|
+
if (!meta || typeof meta !== "object") return false;
|
|
23
|
+
const { refName, queryKey, queryArgs } = meta as Record<string, unknown>;
|
|
24
|
+
if (typeof refName !== "string" || !refName) return false;
|
|
25
|
+
if (queryKey !== null && typeof queryKey !== "string") return false;
|
|
26
|
+
return queryArgs === undefined || Array.isArray(queryArgs) || typeof queryArgs === "function";
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** The query one field of `refName` names, or nothing when the model, the field, or the declaration is absent. */
|
|
30
|
+
export const fieldQueryMetaOf = (refName: string, field: string): FieldQueryMeta | undefined => {
|
|
31
|
+
const cnst = ConstantRegistry.getDatabase(refName, { allowEmpty: true });
|
|
32
|
+
const fieldMap = cnst?.full[FIELD_META] as FieldObject | undefined;
|
|
33
|
+
const meta = fieldMap?.[field]?.meta;
|
|
34
|
+
return isFieldQueryMeta(meta) ? meta : undefined;
|
|
35
|
+
};
|
package/constant/index.ts
CHANGED
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The query a field's value stands for, declared on the field itself with `.meta(...)`. A summary counter is the
|
|
3
|
+
* case this exists for: `hau` is however many rows one filter returns, so the tile showing it can open that exact
|
|
4
|
+
* listing without the page restating the filter beside it.
|
|
5
|
+
*
|
|
6
|
+
* The shape is read structurally rather than by class, so an app declares it with whatever builder it already has.
|
|
7
|
+
*/
|
|
8
|
+
export interface FieldQueryMeta {
|
|
9
|
+
/** The model the query runs against. */
|
|
10
|
+
refName: string;
|
|
11
|
+
/** One of that model's declared filter keys. */
|
|
12
|
+
queryKey: string | null;
|
|
13
|
+
/** Read when the query is applied, so an arg relative to now — `() => [dayjs().subtract(1, "hour")]` — is current. */
|
|
14
|
+
queryArgs?: unknown[] | (() => unknown[]);
|
|
15
|
+
}
|
|
16
|
+
/** The query one field of `refName` names, or nothing when the model, the field, or the declaration is absent. */
|
|
17
|
+
export declare const fieldQueryMetaOf: (refName: string, field: string) => FieldQueryMeta | undefined;
|
|
@@ -3,6 +3,7 @@ export * from "./constantRegistry.d.ts";
|
|
|
3
3
|
export * from "./crystalize.d.ts";
|
|
4
4
|
export * from "./deserialize.d.ts";
|
|
5
5
|
export * from "./fieldInfo.d.ts";
|
|
6
|
+
export * from "./fieldQueryMeta.d.ts";
|
|
6
7
|
export * from "./getDefault.d.ts";
|
|
7
8
|
export * from "./immerify.d.ts";
|
|
8
9
|
export * from "./labelOf.d.ts";
|
|
@@ -4,10 +4,15 @@ export interface DashboardProps<T extends string, State> {
|
|
|
4
4
|
summary: Record<string, unknown>;
|
|
5
5
|
/** The listing these tiles belong to. Kept so a caller names its target; the labels are app-level keys. */
|
|
6
6
|
slice: SliceMeta;
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Overrides the filter a column applies. A column left out still narrows the listing when its own field
|
|
9
|
+
* declares a query with `.meta(...)`; one with neither renders as a plain tile.
|
|
10
|
+
*/
|
|
8
11
|
queryMap?: {
|
|
9
12
|
[column: string]: QuerySetting;
|
|
10
13
|
};
|
|
14
|
+
/** Model whose fields the columns name. Its `.meta(...)` declarations are where a tile's filter comes from. */
|
|
15
|
+
summaryRefName?: string;
|
|
11
16
|
/** Applies one column's filter. Without it a mapped column still renders, but as a plain tile. */
|
|
12
17
|
onSelect?: (setting: QuerySetting, column: string) => void;
|
|
13
18
|
/** The filter key the listing is showing, so a tile stops looking active once the toolbar moves off it. */
|
|
@@ -16,4 +21,4 @@ export interface DashboardProps<T extends string, State> {
|
|
|
16
21
|
presents?: string[];
|
|
17
22
|
hidePresents?: boolean;
|
|
18
23
|
}
|
|
19
|
-
export default function Dashboard<T extends string, State>({ className, summary, queryMap, onSelect, queryKey, columns, presents, hidePresents, }: DashboardProps<T, State>): import("react/jsx-runtime").JSX.Element | null;
|
|
24
|
+
export default function Dashboard<T extends string, State>({ className, summary, slice, queryMap, summaryRefName, onSelect, queryKey, columns, presents, hidePresents, }: DashboardProps<T, State>): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -21,10 +21,15 @@ interface AdminPanelProps<T extends string, State, Input, Full extends {
|
|
|
21
21
|
template?: any;
|
|
22
22
|
unit?: any;
|
|
23
23
|
view?: any;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Overrides the filter a summary column applies. A column left out still narrows the listing when its own
|
|
26
|
+
* field declares one with `.meta(...)`.
|
|
27
|
+
*/
|
|
25
28
|
queryMap?: {
|
|
26
29
|
[column: string]: QuerySetting;
|
|
27
30
|
};
|
|
31
|
+
/** Model whose fields `summaryColumns` name. Defaults to the `summary` model the state key already implies. */
|
|
32
|
+
summaryRefName?: string;
|
|
28
33
|
/** Keys of the app's `summary` state shown as dashboard tiles above the list. */
|
|
29
34
|
summaryColumns?: string[];
|
|
30
35
|
/** Model insight keys shown above the list. The header already carries the total count. */
|
|
@@ -34,5 +39,5 @@ export default function AdminPanel<RefName extends string, State, Input, Full ex
|
|
|
34
39
|
id: string;
|
|
35
40
|
}, Light extends {
|
|
36
41
|
id: string;
|
|
37
|
-
}>({ slice, components, queryMap, template, unit, view, summaryColumns, insightColumns, renderInsight, renderDashboard, ...props }: AdminPanelProps<RefName, State, Input, Full, Light>): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
}>({ slice, components, queryMap, summaryRefName, template, unit, view, summaryColumns, insightColumns, renderInsight, renderDashboard, ...props }: AdminPanelProps<RefName, State, Input, Full, Light>): import("react/jsx-runtime").JSX.Element;
|
|
38
43
|
export {};
|
package/ui/Data/Dashboard.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn, usePage } from "akanjs/client";
|
|
3
|
+
import { fieldQueryMetaOf } from "akanjs/constant";
|
|
3
4
|
import type { QuerySetting, SliceMeta } from "akanjs/fetch";
|
|
4
5
|
import { st } from "akanjs/store";
|
|
5
6
|
import { useState } from "react";
|
|
@@ -11,8 +12,13 @@ export interface DashboardProps<T extends string, State> {
|
|
|
11
12
|
summary: Record<string, unknown>;
|
|
12
13
|
/** The listing these tiles belong to. Kept so a caller names its target; the labels are app-level keys. */
|
|
13
14
|
slice: SliceMeta;
|
|
14
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Overrides the filter a column applies. A column left out still narrows the listing when its own field
|
|
17
|
+
* declares a query with `.meta(...)`; one with neither renders as a plain tile.
|
|
18
|
+
*/
|
|
15
19
|
queryMap?: { [column: string]: QuerySetting };
|
|
20
|
+
/** Model whose fields the columns name. Its `.meta(...)` declarations are where a tile's filter comes from. */
|
|
21
|
+
summaryRefName?: string;
|
|
16
22
|
/** Applies one column's filter. Without it a mapped column still renders, but as a plain tile. */
|
|
17
23
|
onSelect?: (setting: QuerySetting, column: string) => void;
|
|
18
24
|
/** The filter key the listing is showing, so a tile stops looking active once the toolbar moves off it. */
|
|
@@ -27,7 +33,9 @@ const tileClassName = "flex min-w-40 flex-1 flex-col gap-1 rounded-box border px
|
|
|
27
33
|
export default function Dashboard<T extends string, State>({
|
|
28
34
|
className,
|
|
29
35
|
summary,
|
|
36
|
+
slice,
|
|
30
37
|
queryMap,
|
|
38
|
+
summaryRefName = "summary",
|
|
31
39
|
onSelect,
|
|
32
40
|
queryKey,
|
|
33
41
|
columns,
|
|
@@ -39,14 +47,21 @@ export default function Dashboard<T extends string, State>({
|
|
|
39
47
|
const filter = Array.isArray(searchParams.filter) ? searchParams.filter[0] : searchParams.filter;
|
|
40
48
|
|
|
41
49
|
const [selected, setSelected] = useState(typeof filter === "string" ? filter : undefined);
|
|
50
|
+
|
|
51
|
+
const settingOf = (column: string): QuerySetting | undefined => {
|
|
52
|
+
if (queryMap?.[column]) return queryMap[column];
|
|
53
|
+
const meta = fieldQueryMetaOf(summaryRefName, column);
|
|
54
|
+
if (!meta?.queryKey || meta.refName !== slice.refName) return undefined;
|
|
55
|
+
return { queryKey: meta.queryKey, args: meta.queryArgs };
|
|
56
|
+
};
|
|
42
57
|
const shownColumns = (columns ?? []).filter((column) => summary[column] !== undefined);
|
|
43
58
|
const shownPresents = hidePresents ? [] : (presents ?? []).filter((column) => summary[column] !== undefined);
|
|
44
59
|
if (!shownColumns.length && !shownPresents.length) return null;
|
|
45
|
-
const activeColumn = selected &&
|
|
60
|
+
const activeColumn = selected && settingOf(selected)?.queryKey === queryKey ? selected : undefined;
|
|
46
61
|
return (
|
|
47
62
|
<div className={cn("mb-4 flex flex-wrap gap-2", className)}>
|
|
48
63
|
{[...shownColumns, ...shownPresents].map((column) => {
|
|
49
|
-
const setting =
|
|
64
|
+
const setting = settingOf(column);
|
|
50
65
|
const label = dictLabel(l._, `summary.${column}`, column);
|
|
51
66
|
const body = (
|
|
52
67
|
<>
|
package/ui/Model/AdminPanel.tsx
CHANGED
|
@@ -17,8 +17,13 @@ interface AdminPanelProps<T extends string, State, Input, Full extends { id: str
|
|
|
17
17
|
template?: any;
|
|
18
18
|
unit?: any;
|
|
19
19
|
view?: any;
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Overrides the filter a summary column applies. A column left out still narrows the listing when its own
|
|
22
|
+
* field declares one with `.meta(...)`.
|
|
23
|
+
*/
|
|
21
24
|
queryMap?: { [column: string]: QuerySetting };
|
|
25
|
+
/** Model whose fields `summaryColumns` name. Defaults to the `summary` model the state key already implies. */
|
|
26
|
+
summaryRefName?: string;
|
|
22
27
|
/** Keys of the app's `summary` state shown as dashboard tiles above the list. */
|
|
23
28
|
summaryColumns?: string[];
|
|
24
29
|
/** Model insight keys shown above the list. The header already carries the total count. */
|
|
@@ -35,6 +40,7 @@ export default function AdminPanel<
|
|
|
35
40
|
slice,
|
|
36
41
|
components,
|
|
37
42
|
queryMap,
|
|
43
|
+
summaryRefName,
|
|
38
44
|
template,
|
|
39
45
|
unit,
|
|
40
46
|
view,
|
|
@@ -51,6 +57,7 @@ export default function AdminPanel<
|
|
|
51
57
|
slice={slice}
|
|
52
58
|
columns={summaryColumns}
|
|
53
59
|
queryMap={queryMap}
|
|
60
|
+
summaryRefName={summaryRefName}
|
|
54
61
|
onSelect={onSelect}
|
|
55
62
|
queryKey={queryKey}
|
|
56
63
|
hidePresents={hidePresents}
|