@lotics/ui 11.8.9 → 12.0.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/AGENTS.md +13 -0
- package/docs/ai_patterns.md +16 -0
- package/docs/catalog.md +37 -19
- package/docs/composition.md +60 -3
- package/docs/data_entry.md +26 -7
- package/docs/templates.md +148 -50
- package/examples/tpl_documents.tsx +29 -18
- package/examples/tpl_item_list.tsx +27 -8
- package/examples/tpl_record.tsx +2214 -555
- package/package.json +1 -1
- package/src/back_button.tsx +26 -33
- package/src/checklist.tsx +30 -15
- package/src/comments_thread.tsx +3 -62
- package/src/detail_row.tsx +86 -40
- package/src/inline_edit.tsx +5 -0
- package/src/inline_select.tsx +5 -5
- package/src/radio_picker.tsx +11 -2
- package/src/section_heading.tsx +3 -3
- package/src/section_stack.tsx +6 -4
- package/src/text_input_field.tsx +6 -0
- package/src/timeline.tsx +9 -9
|
@@ -12,10 +12,9 @@ import { Alert } from "@lotics/ui/alert";
|
|
|
12
12
|
import { ActionMenu, type ActionMenuItem } from "@lotics/ui/action_menu";
|
|
13
13
|
import { Table, TableRow, TableCell, type TableColumn } from "@lotics/ui/table";
|
|
14
14
|
import { cycleSort, sortBy, type SortState } from "@lotics/ui/sort_header";
|
|
15
|
-
import { FileBadge } from "@lotics/ui/file_badge";
|
|
16
15
|
import { Finding, FindingComparison } from "@lotics/ui/finding";
|
|
17
16
|
import { FileGalleryModal } from "@lotics/ui/file_gallery_modal";
|
|
18
|
-
import { type DisplayFile } from "@lotics/ui/file_thumbnail";
|
|
17
|
+
import { FileThumbnail, type DisplayFile } from "@lotics/ui/file_thumbnail";
|
|
19
18
|
import { FormTextInput } from "@lotics/ui/form_text_input";
|
|
20
19
|
import { CheckboxInput } from "@lotics/ui/checkbox_input";
|
|
21
20
|
import { SearchInput } from "@lotics/ui/search_input";
|
|
@@ -43,25 +42,35 @@ import { CompletionState } from "@lotics/ui/completion_state";
|
|
|
43
42
|
// dialogue-shaped file iteration happens in the Lotics chat; the fork is the ONE
|
|
44
43
|
// AI entry point, never per-row AI buttons). Plus CREATE DOCUMENTS: a
|
|
45
44
|
// readiness checklist → generate → the files land back on the record. All
|
|
46
|
-
// mock, useState + the timer that streams the AgentRun.
|
|
45
|
+
// mock, useState + the timer that streams the AgentRun. The record template
|
|
46
|
+
// (tpl_record) carries this desk as its Documents section — change it in
|
|
47
|
+
// BOTH. One sanctioned divergence: GENERATION. This page has no output
|
|
48
|
+
// pipeline, so Create documents runs as the toolbar dialog here; tpl_record
|
|
49
|
+
// moves it into its Document set OUTPUT section (intake desk / output section
|
|
50
|
+
// split) and drops the toolbar button.
|
|
47
51
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
48
52
|
|
|
49
53
|
type ScriptStep = Omit<AgentRunStep, "status">;
|
|
50
54
|
type Task = "extract" | "check";
|
|
51
55
|
type Phase = "fork" | "running" | "review" | "done";
|
|
52
56
|
|
|
53
|
-
interface Doc { id: string; name: string; mimeType: string; kind: string; sizeKB: number; added: string; addedAt: number }
|
|
57
|
+
interface Doc { id: string; name: string; mimeType: string; kind: string; sizeKB: number; added: string; addedAt: number; url?: string }
|
|
54
58
|
|
|
55
59
|
// Display derives from the canonical numeric — strings would sort "8.4 MB" < "96 KB".
|
|
56
60
|
function fmtSize(kb: number): string {
|
|
57
61
|
if (kb <= 0) return "—";
|
|
58
62
|
return kb < 1024 ? `${kb} KB` : `${(kb / 1024).toFixed(1)} MB`;
|
|
59
63
|
}
|
|
64
|
+
const MOCK_PHOTO_URL =
|
|
65
|
+
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSczMjAnIGhlaWdodD0nMzIwJz48cmVjdCB3aWR0aD0nMzIwJyBoZWlnaHQ9JzMyMCcgZmlsbD0nI2JmZGJmZScvPjxyZWN0IHk9JzIxMCcgd2lkdGg9JzMyMCcgaGVpZ2h0PScxMTAnIGZpbGw9JyNhOGEyOWUnLz48cmVjdCB4PScyMCcgeT0nMTUwJyB3aWR0aD0nMTI0JyBoZWlnaHQ9JzYwJyBmaWxsPScjZGMyNjI2Jy8+PHJlY3QgeD0nMTUyJyB5PScxNTAnIHdpZHRoPScxMjQnIGhlaWdodD0nNjAnIGZpbGw9JyMyNTYzZWInLz48cmVjdCB4PSc4NicgeT0nODgnIHdpZHRoPScxMjQnIGhlaWdodD0nNjAnIGZpbGw9JyNmNTllMGInLz48cmVjdCB4PScyNjInIHk9JzI0JyB3aWR0aD0nMTAnIGhlaWdodD0nMTg2JyBmaWxsPScjNTI1MjUyJy8+PHJlY3QgeD0nMTUwJyB5PScyNCcgd2lkdGg9JzEyMicgaGVpZ2h0PScxMCcgZmlsbD0nIzUyNTI1MicvPjwvc3ZnPg==";
|
|
60
66
|
const DOCS: Doc[] = [
|
|
61
67
|
{ id: "f1", name: "invoice.pdf", mimeType: "application/pdf", kind: "PDF", sizeKB: 214, added: "26 Jun", addedAt: 626 },
|
|
62
68
|
{ id: "f2", name: "packing-list.pdf", mimeType: "application/pdf", kind: "PDF", sizeKB: 96, added: "26 Jun", addedAt: 626 },
|
|
63
69
|
{ id: "f3", name: "booking-confirmation.pdf", mimeType: "application/pdf", kind: "PDF", sizeKB: 182, added: "28 Jun", addedAt: 628 },
|
|
64
70
|
{ id: "f4", name: "photos.zip", mimeType: "application/zip", kind: "ZIP", sizeKB: 8602, added: "30 Jun", addedAt: 630 },
|
|
71
|
+
// an IMAGE file — the register renders it as a real square thumbnail (the
|
|
72
|
+
// SVG data URI stands in for the stored photo URL a live app serves)
|
|
73
|
+
{ id: "f5", name: "delivery-photo.jpg", mimeType: "image/jpeg", kind: "JPG", sizeKB: 1424, added: "30 Jun", addedAt: 630, url: MOCK_PHOTO_URL },
|
|
65
74
|
];
|
|
66
75
|
|
|
67
76
|
const EXTRACT_STEPS: ScriptStep[] = [
|
|
@@ -80,17 +89,17 @@ const CHECK_STEPS: ScriptStep[] = [
|
|
|
80
89
|
// CONFLICT — are the SAME `ChangeField` row: an add has no `before`, an update
|
|
81
90
|
// bands it, a removal is the − band alone, and a conflict's outcome is the
|
|
82
91
|
// read-only + band over the candidate rows.
|
|
83
|
-
const
|
|
92
|
+
const CARRIER_REF_PROPOSED = "MAEU129394855";
|
|
84
93
|
const VESSEL_CURRENT = "MSC AURA";
|
|
85
94
|
const VESSEL_PROPOSED = "MAERSK SALINA";
|
|
86
95
|
const CONSIGNEE_CURRENT = "Nordic Furniture AB";
|
|
87
96
|
const NOTIFY_CURRENT = "Euro Textile Trading GmbH, Frankfurt";
|
|
88
|
-
const EXTRACT_REASONING = "The booking confirmation names the substitute vessel for this
|
|
97
|
+
const EXTRACT_REASONING = "The booking confirmation names the substitute vessel for this shipping week; the invoice and the packing list disagree on the consignee.";
|
|
89
98
|
const CONSIGNEE_OPTIONS = [
|
|
90
99
|
{ value: "Nordic Furniture AB, Jönköping DC", source: "invoice.pdf", recommended: true },
|
|
91
100
|
{ value: "NF Distribution ApS, Kolding", source: "packing-list.pdf" },
|
|
92
101
|
];
|
|
93
|
-
const EXTRACT_FIELD_IDS = ["
|
|
102
|
+
const EXTRACT_FIELD_IDS = ["carrier_ref", "vessel", "consignee", "gross", "notify"] as const;
|
|
94
103
|
|
|
95
104
|
// Cross-check findings — severity reads through ONE colored dot badge (red /
|
|
96
105
|
// amber / zinc), the rest stays calm text.
|
|
@@ -110,7 +119,7 @@ const FINDINGS: Check[] = [
|
|
|
110
119
|
},
|
|
111
120
|
{
|
|
112
121
|
id: "q4", severity: "warning", title: "No certificate of origin among the documents",
|
|
113
|
-
detail: "Destination customs requires a
|
|
122
|
+
detail: "Destination customs requires a certificate of origin for these goods — request it before the shipping cut-off.",
|
|
114
123
|
},
|
|
115
124
|
{
|
|
116
125
|
id: "q3", severity: "info", title: "Booking cut-off is earlier than the invoiced ship week",
|
|
@@ -121,7 +130,7 @@ const FINDINGS: Check[] = [
|
|
|
121
130
|
|
|
122
131
|
// A tiny REAL pdf (data URI) so the gallery preview genuinely renders.
|
|
123
132
|
const MOCK_PDF_URL = "data:application/pdf;base64,JVBERi0xLjQKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgNjEyIDc5Ml0vQ29udGVudHMgNCAwIFIvUmVzb3VyY2VzPDwvRm9udDw8L0YxIDUgMCBSPj4+Pj4+ZW5kb2JqCjQgMCBvYmo8PC9MZW5ndGggNjM+PnN0cmVhbQpCVCAvRjEgMTggVGYgNzIgNzIwIFRkIChOb3JkaWMgRnVybml0dXJlIC0gbW9jayBkb2N1bWVudCkgVGogRVQKZW5kc3RyZWFtIGVuZG9iago1IDAgb2JqPDwvVHlwZS9Gb250L1N1YnR5cGUvVHlwZTEvQmFzZUZvbnQvSGVsdmV0aWNhPj5lbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTIgMDAwMDAgbiAKMDAwMDAwMDEwMSAwMDAwMCBuIAowMDAwMDAwMjExIDAwMDAwIG4gCjAwMDAwMDAzMjAgMDAwMDAgbiAKdHJhaWxlcjw8L1NpemUgNi9Sb290IDEgMCBSPj4Kc3RhcnR4cmVmCjM4MQolJUVPRg==";
|
|
124
|
-
const toDisplay = (d: Doc): DisplayFile => ({ id: d.id, filename: d.name, mimeType: d.mimeType, url: MOCK_PDF_URL });
|
|
133
|
+
const toDisplay = (d: Doc): DisplayFile => ({ id: d.id, filename: d.name, mimeType: d.mimeType, url: d.url ?? MOCK_PDF_URL });
|
|
125
134
|
|
|
126
135
|
// The documents register — columns ONCE (the Table renders the header band
|
|
127
136
|
// and every cell width from these; facts get dedicated scannable columns
|
|
@@ -129,15 +138,15 @@ const toDisplay = (d: Doc): DisplayFile => ({ id: d.id, filename: d.name, mimeTy
|
|
|
129
138
|
// (tpl_item_list): leading checkbox + select-all, trailing ⋯ gutter.
|
|
130
139
|
const DOC_COLUMNS: TableColumn[] = [
|
|
131
140
|
{ key: "doc", label: "Document", flex: 1, sortable: true },
|
|
132
|
-
{ key: "size", label: "Size", width: 90, align: "right", sortable: true },
|
|
133
|
-
{ key: "added", label: "Added", width: 96, sortable: true },
|
|
141
|
+
{ key: "size", label: "Size", width: 90, align: "right", sortable: true, priority: 3 },
|
|
142
|
+
{ key: "added", label: "Added", width: 96, sortable: true, priority: 2 },
|
|
134
143
|
];
|
|
135
144
|
|
|
136
145
|
interface Producible { id: string; label: string; file: string; sizeKB: number; needs?: { key: string; label: string }[] }
|
|
137
146
|
const PRODUCIBLE: Producible[] = [
|
|
138
147
|
{ id: "g1", label: "Shipping instruction", file: "shipping-instruction.pdf", sizeKB: 84 },
|
|
139
148
|
{ id: "g2", label: "Certificate of origin", file: "certificate-of-origin.pdf", sizeKB: 61 },
|
|
140
|
-
{ id: "g3", label: "Delivery note", file: "delivery-note.pdf", sizeKB: 72, needs: [{ key: "delivery_address", label: "Delivery address" }, { key: "
|
|
149
|
+
{ id: "g3", label: "Delivery note", file: "delivery-note.pdf", sizeKB: 72, needs: [{ key: "delivery_address", label: "Delivery address" }, { key: "commodity_code", label: "Commodity code" }] },
|
|
141
150
|
{ id: "g4", label: "Customs declaration draft", file: "customs-declaration-draft.pdf", sizeKB: 118 },
|
|
142
151
|
];
|
|
143
152
|
|
|
@@ -164,7 +173,7 @@ export function TplDocuments() {
|
|
|
164
173
|
// The HOST owns the review: one editable proposal per field (editing IS the
|
|
165
174
|
// review), a tri-state decision per field (dropped rows collapse struck,
|
|
166
175
|
// out of the apply), and the conflict's pick (empty until the user chooses).
|
|
167
|
-
const [
|
|
176
|
+
const [carrierRef, setCarrierRef] = useState(CARRIER_REF_PROPOSED);
|
|
168
177
|
const [vessel, setVessel] = useState(VESSEL_PROPOSED);
|
|
169
178
|
const [consignee, setConsignee] = useState<string | null>(null);
|
|
170
179
|
const [grossWeight, setGrossWeight] = useState("1,540");
|
|
@@ -189,7 +198,7 @@ export function TplDocuments() {
|
|
|
189
198
|
const commitUpload = () => setFiles((fs) => [...fs, ...picked.filter((p2) => !fs.some((f) => f.id === p2.id))]);
|
|
190
199
|
const closeAi = () => {
|
|
191
200
|
setAiOpen(false); setTask(null); setPhase("fork"); setRevealed(0); setUploadFlow(false); setBrief(""); setTaskChoice(null);
|
|
192
|
-
|
|
201
|
+
setCarrierRef(CARRIER_REF_PROPOSED); setVessel(VESSEL_PROPOSED); setConsignee(null); setConsigneePick(null); setCustomConsignee(""); setGrossWeight("1,540"); setLineAdd("pending"); setLineEdit({}); setNewItem("Corner protectors, foam"); setNewQty("400"); setEditedQty("1,450"); setFieldDecisions({});
|
|
193
202
|
};
|
|
194
203
|
const startTask = (t: Task) => { setTask(t); setRevealed(0); setPhase("running"); };
|
|
195
204
|
// ⋯ menu per document row. "Edit with AI" is the app→chat handoff for
|
|
@@ -414,7 +423,9 @@ export function TplDocuments() {
|
|
|
414
423
|
>
|
|
415
424
|
<TableCell>
|
|
416
425
|
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
|
|
417
|
-
|
|
426
|
+
{/* ONE square 32px slot for every type: an image fills it as a
|
|
427
|
+
real thumbnail, a document centers its badge in it */}
|
|
428
|
+
<FileThumbnail file={toDisplay(f)} size={32} onPress={() => openPreview(visibleFiles, fi)} />
|
|
418
429
|
<Text size="sm" weight="medium" numberOfLines={1} style={{ flexShrink: 1 }}>{f.name}</Text>
|
|
419
430
|
</View>
|
|
420
431
|
</TableCell>
|
|
@@ -477,7 +488,7 @@ export function TplDocuments() {
|
|
|
477
488
|
{taskChoice === "check" ? (
|
|
478
489
|
<FormTextInput
|
|
479
490
|
label="Instructions (optional)"
|
|
480
|
-
placeholder="Anything specific to check — e.g. verify the
|
|
491
|
+
placeholder="Anything specific to check — e.g. verify the commodity codes against the order"
|
|
481
492
|
value={brief}
|
|
482
493
|
onChangeText={setBrief}
|
|
483
494
|
multiline
|
|
@@ -505,8 +516,8 @@ export function TplDocuments() {
|
|
|
505
516
|
<ChangeReasoning>{EXTRACT_REASONING}</ChangeReasoning>
|
|
506
517
|
<ChangeFields>
|
|
507
518
|
{/* An ADD — the record holds nothing yet, so no `before`. */}
|
|
508
|
-
<ChangeField label="
|
|
509
|
-
<ChangeValueInput value={
|
|
519
|
+
<ChangeField label="Carrier reference" value={carrierRef} summary={carrierRef} {...fieldRow("carrier_ref")}>
|
|
520
|
+
<ChangeValueInput value={carrierRef} onChangeText={setCarrierRef} accessibilityLabel="Carrier reference" />
|
|
510
521
|
</ChangeField>
|
|
511
522
|
{/* An UPDATE — the current value banded above the editor. */}
|
|
512
523
|
<ChangeField label="Vessel" value={vessel} summary={vessel} before={VESSEL_CURRENT} {...fieldRow("vessel")}>
|
|
@@ -31,6 +31,7 @@ import { Table, TableRow, TableCell, type TableColumn } from "@lotics/ui/table";
|
|
|
31
31
|
import { SearchInput } from "@lotics/ui/search_input";
|
|
32
32
|
import { Select } from "@lotics/ui/select";
|
|
33
33
|
import { FilterChip, selectSummary } from "@lotics/ui/filter_chip";
|
|
34
|
+
import { ColumnFilter, type ColumnFilterValue } from "@lotics/ui/column_filter";
|
|
34
35
|
import { OptionList } from "@lotics/ui/option_list";
|
|
35
36
|
import { cycleSort, sortBy, type SortState } from "@lotics/ui/sort_header";
|
|
36
37
|
import { Combobox, ComboboxInput, ComboboxContent } from "@lotics/ui/combobox";
|
|
@@ -355,13 +356,18 @@ const ASSIGNEES = [...new Set(HO_SO.map((r) => r.phuTrach))].map((n) => ({ label
|
|
|
355
356
|
|
|
356
357
|
// Columns defined ONCE — Table renders the header from these and each
|
|
357
358
|
// TableCell takes its width/align by position; the ⋯ is the trailing gutter.
|
|
359
|
+
// `priority` = the MOBILE contract: when the container can't fit every column
|
|
360
|
+
// the highest numbers drop first (Received → Tasks → Status), the identity +
|
|
361
|
+
// Customer + Fee survive longest, and at the register floor rows STACK
|
|
362
|
+
// (label-over-value) so nothing is lost — the drawer carries full detail
|
|
363
|
+
// either way.
|
|
358
364
|
const COLUMNS: TableColumn[] = [
|
|
359
365
|
{ key: "ma", label: "Record ID", width: 112, sortable: true },
|
|
360
|
-
{ key: "khach", label: "Customer", flex: 1, sortable: true },
|
|
361
|
-
{ key: "trangThai", label: "Status", width: 112, sortable: true },
|
|
362
|
-
{ key: "viec", label: "Tasks", width: 82 },
|
|
363
|
-
{ key: "phi", label: "Fee", width: 116, align: "right", sortable: true },
|
|
364
|
-
{ key: "ngayNhan", label: "Received", width: 72, sortable: true },
|
|
366
|
+
{ key: "khach", label: "Customer", flex: 1, sortable: true, priority: 1 },
|
|
367
|
+
{ key: "trangThai", label: "Status", width: 112, sortable: true, priority: 3 },
|
|
368
|
+
{ key: "viec", label: "Tasks", width: 82, priority: 4 },
|
|
369
|
+
{ key: "phi", label: "Fee", width: 116, align: "right", sortable: true, priority: 2 },
|
|
370
|
+
{ key: "ngayNhan", label: "Received", width: 72, sortable: true, priority: 5 },
|
|
365
371
|
];
|
|
366
372
|
|
|
367
373
|
// DOCUMENT — the paper the row needs NOW: while the fee is unpaid the
|
|
@@ -682,7 +688,6 @@ function HoSoWorkspace({ hs, daThu, onPaid, tasks, onTasksChange, suggestions, o
|
|
|
682
688
|
<Button
|
|
683
689
|
title="Add file"
|
|
684
690
|
color="secondary"
|
|
685
|
-
icon="plus"
|
|
686
691
|
onPress={() => {
|
|
687
692
|
void pickFiles({ multiple: true }).then((picked) => {
|
|
688
693
|
if (picked.length > 0) setFiles((prev) => [...prev, ...toDisplayFiles(picked)]);
|
|
@@ -1143,6 +1148,8 @@ export function TplItemList() {
|
|
|
1143
1148
|
const [sort, setSort] = useState<SortState | null>(null);
|
|
1144
1149
|
const [assignee, setAssignee] = useState<string[]>([]);
|
|
1145
1150
|
const [feeStatus, setFeeStatus] = useState<"paid" | "unpaid" | null>(null);
|
|
1151
|
+
// The per-column pill (ColumnFilter, number variant) — min/max over the Fee column.
|
|
1152
|
+
const [feeAmount, setFeeAmount] = useState<ColumnFilterValue | undefined>(undefined);
|
|
1146
1153
|
const sel = useSelection();
|
|
1147
1154
|
|
|
1148
1155
|
// Filter / sort changes reset paging — page 3 of the old ordering is
|
|
@@ -1169,6 +1176,10 @@ export function TplItemList() {
|
|
|
1169
1176
|
if (assignee.length > 0 && !assignee.includes(r.phuTrach)) return false;
|
|
1170
1177
|
if (feeStatus === "paid" && !daThuOf(r)) return false;
|
|
1171
1178
|
if (feeStatus === "unpaid" && daThuOf(r)) return false;
|
|
1179
|
+
if (feeAmount?.kind === "number") {
|
|
1180
|
+
if (feeAmount.min != null && r.phi < feeAmount.min) return false;
|
|
1181
|
+
if (feeAmount.max != null && r.phi > feeAmount.max) return false;
|
|
1182
|
+
}
|
|
1172
1183
|
if (q && !`${r.ma} ${r.khach} ${r.dienThoai}`.toLowerCase().includes(q)) return false;
|
|
1173
1184
|
return true;
|
|
1174
1185
|
});
|
|
@@ -1251,10 +1262,10 @@ export function TplItemList() {
|
|
|
1251
1262
|
{/* Single-select sibling — the render-prop `close` shuts the popover on
|
|
1252
1263
|
pick (the multi above stays open to collect several values). */}
|
|
1253
1264
|
<FilterChip
|
|
1254
|
-
label="
|
|
1265
|
+
label="Payment"
|
|
1255
1266
|
summary={feeStatus === "paid" ? "Paid" : feeStatus === "unpaid" ? "Unpaid" : undefined}
|
|
1256
1267
|
onClear={() => { setFeeStatus(null); setPage(0); }}
|
|
1257
|
-
clearLabel="Clear
|
|
1268
|
+
clearLabel="Clear payment filter"
|
|
1258
1269
|
>
|
|
1259
1270
|
{({ close }) => (
|
|
1260
1271
|
<OptionList
|
|
@@ -1266,6 +1277,14 @@ export function TplItemList() {
|
|
|
1266
1277
|
/>
|
|
1267
1278
|
)}
|
|
1268
1279
|
</FilterChip>
|
|
1280
|
+
{/* the per-column filter pill — the NUMBER variant (min/max) over
|
|
1281
|
+
the Fee column; text/select variants live on the Tables gallery
|
|
1282
|
+
page. Filter changes reset paging like every other control. */}
|
|
1283
|
+
<ColumnFilter
|
|
1284
|
+
column={{ key: "phi", label: "Fee", type: "number" }}
|
|
1285
|
+
value={feeAmount}
|
|
1286
|
+
onChange={(v) => { setFeeAmount(v); setPage(0); }}
|
|
1287
|
+
/>
|
|
1269
1288
|
</View>
|
|
1270
1289
|
<CreateRecordDialog onCreate={createRecord} />
|
|
1271
1290
|
</View>
|