@lotics/ui 27.17.0 → 28.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 -10
- package/MIGRATION.md +64 -0
- package/docs/catalog.md +97 -140
- package/docs/data_entry.md +57 -87
- package/docs/templates.md +95 -57
- package/examples/tpl_item_list.tsx +77 -36
- package/examples/tpl_record.tsx +372 -460
- package/package.json +3 -3
- package/src/checklist.tsx +339 -0
- package/src/comments_button.tsx +108 -0
- package/src/control_surface.ts +13 -0
- package/src/detail_row.tsx +2 -2
- package/src/field_annotations.tsx +1 -1
- package/src/icon.tsx +10 -0
- package/src/icon_button.tsx +17 -1
- package/src/locale.tsx +12 -1
- package/src/stepper.tsx +107 -18
- package/src/stepper_layout.ts +21 -1
- package/src/table.tsx +101 -11
- package/src/pipeline.tsx +0 -231
- package/src/task.tsx +0 -518
- package/src/task_metrics.ts +0 -55
|
@@ -17,6 +17,7 @@ import { FloatingActionBar } from "@lotics/ui/floating_action_bar";
|
|
|
17
17
|
import { FormField } from "@lotics/ui/form_field";
|
|
18
18
|
import { Icon, type IconName } from "@lotics/ui/icon";
|
|
19
19
|
import { IconButton } from "@lotics/ui/icon_button";
|
|
20
|
+
import { CommentsButton } from "@lotics/ui/comments_button";
|
|
20
21
|
import { CopyButton } from "@lotics/ui/copy_button";
|
|
21
22
|
import { ListItem } from "@lotics/ui/list_item";
|
|
22
23
|
import { TplRecord } from "./tpl_record";
|
|
@@ -109,6 +110,9 @@ const TRANG_THAI: Record<TrangThai, { label: string; color: ColorName; doc: { ti
|
|
|
109
110
|
|
|
110
111
|
interface HoSo {
|
|
111
112
|
ma: string;
|
|
113
|
+
/** How many comments the record carries — surfaced on the row so a thread is
|
|
114
|
+
* discoverable from the book rather than only after opening. */
|
|
115
|
+
binhLuan: number;
|
|
112
116
|
khach: string;
|
|
113
117
|
dienThoai: string;
|
|
114
118
|
trangThai: TrangThai;
|
|
@@ -120,29 +124,29 @@ interface HoSo {
|
|
|
120
124
|
}
|
|
121
125
|
|
|
122
126
|
const HO_SO: HoSo[] = [
|
|
123
|
-
{ ma: "RC-2026-0041", khach: "Michael Torres", dienThoai: "(555) 384-7560", trangThai: "dangxuly", phi: 2_500_000, daThu: true, ngayThu: "03/06", ngayNhan: "02/06", phuTrach: "Sarah Chen" },
|
|
124
|
-
{ ma: "RC-2026-0042", khach: "Anna Whitfield", dienThoai: "(555) 220-4158", trangThai: "dangxuly", phi: 1_800_000, daThu: false, ngayNhan: "03/06", phuTrach: "Sarah Chen" },
|
|
125
|
-
{ ma: "RC-2026-0043", khach: "Northwind Packaging", dienThoai: "(555) 558-2143", trangThai: "chobosung", phi: 4_200_000, daThu: true, ngayThu: "05/06", ngayNhan: "04/06", phuTrach: "David Park" },
|
|
126
|
-
{ ma: "RC-2026-0044", khach: "Robert Hayes", dienThoai: "(555) 471-8026", trangThai: "hoantat", phi: 2_500_000, daThu: true, ngayThu: "29/05", ngayNhan: "28/05", phuTrach: "Sarah Chen" },
|
|
127
|
-
{ ma: "RC-2026-0045", khach: "Karen Mitchell", dienThoai: "(555) 605-3317", trangThai: "dangxuly", phi: 3_100_000, daThu: true, ngayThu: "06/06", ngayNhan: "05/06", phuTrach: "David Park" },
|
|
128
|
-
{ ma: "RC-2026-0046", khach: "Daniel Brooks", dienThoai: "(555) 112-9084", trangThai: "chobosung", phi: 1_800_000, daThu: false, ngayNhan: "06/06", phuTrach: "Sarah Chen" },
|
|
129
|
-
{ ma: "RC-2026-0047", khach: "Crestline Foods", dienThoai: "(555) 446-2708", trangThai: "hoantat", phi: 5_600_000, daThu: true, ngayThu: "31/05", ngayNhan: "30/05", phuTrach: "David Park" },
|
|
130
|
-
{ ma: "RC-2026-0048", khach: "Laura Bennett", dienThoai: "(555) 833-5192", trangThai: "dangxuly", phi: 2_500_000, daThu: false, ngayNhan: "09/06", phuTrach: "James Walker" },
|
|
131
|
-
{ ma: "RC-2026-0049", khach: "Thomas Reed", dienThoai: "(555) 760-3844", trangThai: "hoantat", phi: 1_800_000, daThu: true, ngayThu: "02/06", ngayNhan: "27/05", phuTrach: "James Walker" },
|
|
132
|
-
{ ma: "RC-2026-0050", khach: "Kevin Doyle", dienThoai: "(555) 905-1273", trangThai: "dangxuly", phi: 4_200_000, daThu: true, ngayThu: "10/06", ngayNhan: "10/06", phuTrach: "James Walker" },
|
|
133
|
-
{ ma: "RC-2026-0051", khach: "Atlas Components", dienThoai: "(555) 271-6355", trangThai: "dangxuly", phi: 6_800_000, daThu: true, ngayThu: "06/06", ngayNhan: "05/06", phuTrach: "Maria Lopez" },
|
|
134
|
-
{ ma: "RC-2026-0052", khach: "Rachel Foster", dienThoai: "(555) 540-2866", trangThai: "chobosung", phi: 2_500_000, daThu: true, ngayThu: "04/06", ngayNhan: "03/06", phuTrach: "Sarah Chen" },
|
|
135
|
-
{ ma: "RC-2026-0053", khach: "Patrick Shaw", dienThoai: "(555) 119-4727", trangThai: "hoantat", phi: 1_200_000, daThu: true, ngayThu: "30/05", ngayNhan: "26/05", phuTrach: "David Park" },
|
|
136
|
-
{ ma: "RC-2026-0054", khach: "Summit Logistics", dienThoai: "(555) 662-8059", trangThai: "dangxuly", phi: 5_600_000, daThu: false, ngayNhan: "09/06", phuTrach: "Maria Lopez" },
|
|
137
|
-
{ ma: "RC-2026-0055", khach: "Brian Keller", dienThoai: "(555) 484-9131", trangThai: "chobosung", phi: 3_100_000, daThu: false, ngayNhan: "07/06", phuTrach: "James Walker" },
|
|
138
|
-
{ ma: "RC-2026-0056", khach: "Emily Carter", dienThoai: "(555) 358-0274", trangThai: "hoantat", phi: 2_500_000, daThu: true, ngayThu: "03/06", ngayNhan: "29/05", phuTrach: "Sarah Chen" },
|
|
139
|
-
{ ma: "RC-2026-0057", khach: "Jason Pratt", dienThoai: "(555) 705-4693", trangThai: "dangxuly", phi: 1_200_000, daThu: true, ngayThu: "08/06", ngayNhan: "08/06", phuTrach: "David Park" },
|
|
140
|
-
{ ma: "RC-2026-0058", khach: "Beacon Supplies", dienThoai: "(555) 813-2460", trangThai: "hoantat", phi: 4_200_000, daThu: true, ngayThu: "01/06", ngayNhan: "28/05", phuTrach: "Maria Lopez" },
|
|
141
|
-
{ ma: "RC-2026-0059", khach: "Nina Alvarez", dienThoai: "(555) 270-1548", trangThai: "dangxuly", phi: 1_800_000, daThu: false, ngayNhan: "10/06", phuTrach: "Sarah Chen" },
|
|
142
|
-
{ ma: "RC-2026-0060", khach: "Steven Drake", dienThoai: "(555) 593-7186", trangThai: "chobosung", phi: 2_500_000, daThu: true, ngayThu: "09/06", ngayNhan: "08/06", phuTrach: "James Walker" },
|
|
143
|
-
{ ma: "RC-2026-0061", khach: "Harbor Industries", dienThoai: "(555) 047-5829", trangThai: "dangxuly", phi: 6_800_000, daThu: true, ngayThu: "11/06", ngayNhan: "10/06", phuTrach: "Maria Lopez" },
|
|
144
|
-
{ ma: "RC-2026-0062", khach: "Grace Holloway", dienThoai: "(555) 826-3050", trangThai: "hoantat", phi: 3_100_000, daThu: true, ngayThu: "05/06", ngayNhan: "31/05", phuTrach: "David Park" },
|
|
145
|
-
{ ma: "RC-2026-0063", khach: "Olivia Manning", dienThoai: "(555) 631-2904", trangThai: "dangxuly", phi: 1_200_000, daThu: false, ngayNhan: "11/06", phuTrach: "Sarah Chen" },
|
|
127
|
+
{ ma: "RC-2026-0041", binhLuan: 2, khach: "Michael Torres", dienThoai: "(555) 384-7560", trangThai: "dangxuly", phi: 2_500_000, daThu: true, ngayThu: "03/06", ngayNhan: "02/06", phuTrach: "Sarah Chen" },
|
|
128
|
+
{ ma: "RC-2026-0042", binhLuan: 0, khach: "Anna Whitfield", dienThoai: "(555) 220-4158", trangThai: "dangxuly", phi: 1_800_000, daThu: false, ngayNhan: "03/06", phuTrach: "Sarah Chen" },
|
|
129
|
+
{ ma: "RC-2026-0043", binhLuan: 5, khach: "Northwind Packaging", dienThoai: "(555) 558-2143", trangThai: "chobosung", phi: 4_200_000, daThu: true, ngayThu: "05/06", ngayNhan: "04/06", phuTrach: "David Park" },
|
|
130
|
+
{ ma: "RC-2026-0044", binhLuan: 0, khach: "Robert Hayes", dienThoai: "(555) 471-8026", trangThai: "hoantat", phi: 2_500_000, daThu: true, ngayThu: "29/05", ngayNhan: "28/05", phuTrach: "Sarah Chen" },
|
|
131
|
+
{ ma: "RC-2026-0045", binhLuan: 1, khach: "Karen Mitchell", dienThoai: "(555) 605-3317", trangThai: "dangxuly", phi: 3_100_000, daThu: true, ngayThu: "06/06", ngayNhan: "05/06", phuTrach: "David Park" },
|
|
132
|
+
{ ma: "RC-2026-0046", binhLuan: 0, khach: "Daniel Brooks", dienThoai: "(555) 112-9084", trangThai: "chobosung", phi: 1_800_000, daThu: false, ngayNhan: "06/06", phuTrach: "Sarah Chen" },
|
|
133
|
+
{ ma: "RC-2026-0047", binhLuan: 3, khach: "Crestline Foods", dienThoai: "(555) 446-2708", trangThai: "hoantat", phi: 5_600_000, daThu: true, ngayThu: "31/05", ngayNhan: "30/05", phuTrach: "David Park" },
|
|
134
|
+
{ ma: "RC-2026-0048", binhLuan: 0, khach: "Laura Bennett", dienThoai: "(555) 833-5192", trangThai: "dangxuly", phi: 2_500_000, daThu: false, ngayNhan: "09/06", phuTrach: "James Walker" },
|
|
135
|
+
{ ma: "RC-2026-0049", binhLuan: 2, khach: "Thomas Reed", dienThoai: "(555) 760-3844", trangThai: "hoantat", phi: 1_800_000, daThu: true, ngayThu: "02/06", ngayNhan: "27/05", phuTrach: "James Walker" },
|
|
136
|
+
{ ma: "RC-2026-0050", binhLuan: 0, khach: "Kevin Doyle", dienThoai: "(555) 905-1273", trangThai: "dangxuly", phi: 4_200_000, daThu: true, ngayThu: "10/06", ngayNhan: "10/06", phuTrach: "James Walker" },
|
|
137
|
+
{ ma: "RC-2026-0051", binhLuan: 5, khach: "Atlas Components", dienThoai: "(555) 271-6355", trangThai: "dangxuly", phi: 6_800_000, daThu: true, ngayThu: "06/06", ngayNhan: "05/06", phuTrach: "Maria Lopez" },
|
|
138
|
+
{ ma: "RC-2026-0052", binhLuan: 0, khach: "Rachel Foster", dienThoai: "(555) 540-2866", trangThai: "chobosung", phi: 2_500_000, daThu: true, ngayThu: "04/06", ngayNhan: "03/06", phuTrach: "Sarah Chen" },
|
|
139
|
+
{ ma: "RC-2026-0053", binhLuan: 1, khach: "Patrick Shaw", dienThoai: "(555) 119-4727", trangThai: "hoantat", phi: 1_200_000, daThu: true, ngayThu: "30/05", ngayNhan: "26/05", phuTrach: "David Park" },
|
|
140
|
+
{ ma: "RC-2026-0054", binhLuan: 0, khach: "Summit Logistics", dienThoai: "(555) 662-8059", trangThai: "dangxuly", phi: 5_600_000, daThu: false, ngayNhan: "09/06", phuTrach: "Maria Lopez" },
|
|
141
|
+
{ ma: "RC-2026-0055", binhLuan: 3, khach: "Brian Keller", dienThoai: "(555) 484-9131", trangThai: "chobosung", phi: 3_100_000, daThu: false, ngayNhan: "07/06", phuTrach: "James Walker" },
|
|
142
|
+
{ ma: "RC-2026-0056", binhLuan: 0, khach: "Emily Carter", dienThoai: "(555) 358-0274", trangThai: "hoantat", phi: 2_500_000, daThu: true, ngayThu: "03/06", ngayNhan: "29/05", phuTrach: "Sarah Chen" },
|
|
143
|
+
{ ma: "RC-2026-0057", binhLuan: 2, khach: "Jason Pratt", dienThoai: "(555) 705-4693", trangThai: "dangxuly", phi: 1_200_000, daThu: true, ngayThu: "08/06", ngayNhan: "08/06", phuTrach: "David Park" },
|
|
144
|
+
{ ma: "RC-2026-0058", binhLuan: 0, khach: "Beacon Supplies", dienThoai: "(555) 813-2460", trangThai: "hoantat", phi: 4_200_000, daThu: true, ngayThu: "01/06", ngayNhan: "28/05", phuTrach: "Maria Lopez" },
|
|
145
|
+
{ ma: "RC-2026-0059", binhLuan: 5, khach: "Nina Alvarez", dienThoai: "(555) 270-1548", trangThai: "dangxuly", phi: 1_800_000, daThu: false, ngayNhan: "10/06", phuTrach: "Sarah Chen" },
|
|
146
|
+
{ ma: "RC-2026-0060", binhLuan: 0, khach: "Steven Drake", dienThoai: "(555) 593-7186", trangThai: "chobosung", phi: 2_500_000, daThu: true, ngayThu: "09/06", ngayNhan: "08/06", phuTrach: "James Walker" },
|
|
147
|
+
{ ma: "RC-2026-0061", binhLuan: 1, khach: "Harbor Industries", dienThoai: "(555) 047-5829", trangThai: "dangxuly", phi: 6_800_000, daThu: true, ngayThu: "11/06", ngayNhan: "10/06", phuTrach: "Maria Lopez" },
|
|
148
|
+
{ ma: "RC-2026-0062", binhLuan: 0, khach: "Grace Holloway", dienThoai: "(555) 826-3050", trangThai: "hoantat", phi: 3_100_000, daThu: true, ngayThu: "05/06", ngayNhan: "31/05", phuTrach: "David Park" },
|
|
149
|
+
{ ma: "RC-2026-0063", binhLuan: 3, khach: "Olivia Manning", dienThoai: "(555) 631-2904", trangThai: "dangxuly", phi: 1_200_000, daThu: false, ngayNhan: "11/06", phuTrach: "Sarah Chen" },
|
|
146
150
|
];
|
|
147
151
|
|
|
148
152
|
const TABS: { value: Tab; label: string }[] = [
|
|
@@ -165,13 +169,18 @@ const ASSIGNEES = [...new Set(HO_SO.map((r) => r.phuTrach))].map((n) => ({ label
|
|
|
165
169
|
// (label-over-value) so nothing is lost — the drawer carries full detail
|
|
166
170
|
// either way.
|
|
167
171
|
const COLUMNS: TableColumn[] = [
|
|
168
|
-
|
|
172
|
+
// The SUBJECT leads. A reader scans this book by who the record is FOR, and
|
|
173
|
+
// says "the Harbor Freight one" — never "RC-2026-0041". The key is how the
|
|
174
|
+
// SYSTEM refers to the record, so it rides the identity cell's supporting line
|
|
175
|
+
// where every other supporting value lives. It kept a 112px column of its own
|
|
176
|
+
// while the thing everyone actually looks for sat second.
|
|
169
177
|
{ key: "khach", label: "Customer", flex: 1, sortable: true, priority: 1 },
|
|
170
178
|
{ key: "trangThai", label: "Status", width: 112, sortable: true, priority: 3 },
|
|
171
179
|
{ key: "phi", label: "Fee", width: 116, align: "right", sortable: true, priority: 2 },
|
|
172
180
|
{ key: "ngayNhan", label: "Received", width: 72, sortable: true, priority: 5 },
|
|
173
181
|
];
|
|
174
182
|
|
|
183
|
+
|
|
175
184
|
// DOCUMENT — the paper the row needs NOW: while the fee is unpaid the
|
|
176
185
|
// paper that matters is the payment slip; once paid, the stage's own document.
|
|
177
186
|
function docFor(r: HoSo, daThu: boolean): { title: string; icon: IconName } {
|
|
@@ -189,10 +198,11 @@ function menuFor(r: HoSo, daThu: boolean): ActionMenuItem[] {
|
|
|
189
198
|
];
|
|
190
199
|
}
|
|
191
200
|
|
|
192
|
-
function HoSoRow({ hs, daThu, selected, marked, selectable, onToggle, onPress }: { hs: HoSo; daThu: boolean; selected: boolean; marked: boolean; selectable: boolean; onToggle: (on: boolean) => void; onPress: () => void }) {
|
|
201
|
+
function HoSoRow({ hs, ordinal, daThu, selected, marked, selectable, onToggle, onPress, onOpenComments }: { hs: HoSo; ordinal: number; daThu: boolean; selected: boolean; marked: boolean; selectable: boolean; onToggle: (on: boolean) => void; onPress: () => void; onOpenComments: () => void }) {
|
|
193
202
|
return (
|
|
194
203
|
<TableRow
|
|
195
204
|
onPress={onPress}
|
|
205
|
+
ordinal={ordinal}
|
|
196
206
|
selected={selected}
|
|
197
207
|
marked={marked}
|
|
198
208
|
minHeight={56}
|
|
@@ -202,11 +212,17 @@ function HoSoRow({ hs, daThu, selected, marked, selectable, onToggle, onPress }:
|
|
|
202
212
|
trailing={<ActionMenu items={menuFor(hs, daThu)} accessibilityLabel={`Actions for ${hs.ma}`} />}
|
|
203
213
|
>
|
|
204
214
|
<TableCell>
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
215
|
+
<View style={{ gap: 2, alignSelf: "stretch" }}>
|
|
216
|
+
{/* The name takes the row, the thread pins right. A comment count is
|
|
217
|
+
the one thing on this row that is about PEOPLE talking rather than
|
|
218
|
+
the record's own state, so it sits apart from the values below it
|
|
219
|
+
instead of joining them on the supporting line. */}
|
|
220
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
|
221
|
+
<Text size="sm" weight="medium" numberOfLines={1} style={{ flexShrink: 1 }}>{hs.khach}</Text>
|
|
222
|
+
{/* Rendered only where a thread EXISTS — a zero on every quiet row is a
|
|
223
|
+
column of noise that trains the eye to skip where the signal appears. */}
|
|
224
|
+
{hs.binhLuan > 0 ? <CommentsButton count={hs.binhLuan} subject={hs.khach} onPress={onOpenComments} /> : null}
|
|
225
|
+
</View>
|
|
210
226
|
{/* Every value on this row is selectable — that is what a register owes
|
|
211
227
|
its reader, and it costs no chrome. The phone gets a CONTROL on top
|
|
212
228
|
because it is the one value copied often enough to earn one (a desk
|
|
@@ -214,7 +230,19 @@ function HoSoRow({ hs, daThu, selected, marked, selectable, onToggle, onPress }:
|
|
|
214
230
|
inside its own cell: the trailing gutter belongs to the ⋯, and with
|
|
215
231
|
two values in this cell it would have nothing to name. */}
|
|
216
232
|
<View style={{ flexDirection: "row", alignItems: "center", gap: 2 }}>
|
|
217
|
-
|
|
233
|
+
{/* The KEY, muted, on the supporting line — at most this, never a
|
|
234
|
+
column and never the row's name.
|
|
235
|
+
|
|
236
|
+
It does NOT shrink. A key is quoted whole or it is not a key: a
|
|
237
|
+
phone number clipped to "(555) 384-7…" is still recognisably a
|
|
238
|
+
number the reader can widen the column to finish, while
|
|
239
|
+
"RC-2026-0…" is unusable — it is the exact string a desk reads
|
|
240
|
+
out. So the fixed-length identifier holds its width and the phone
|
|
241
|
+
gives way, which is also the right order of loss: the phone has a
|
|
242
|
+
Copy control beside it that hands over the full value regardless. */}
|
|
243
|
+
<Text size="xs" color="muted" tabular numberOfLines={1} style={{ flexShrink: 0 }}>{hs.ma}</Text>
|
|
244
|
+
<Text size="xs" color="muted" style={{ flexShrink: 0 }}> · </Text>
|
|
245
|
+
<Text size="xs" color="muted" tabular numberOfLines={1} style={{ flexShrink: 1 }}>{hs.dienThoai}</Text>
|
|
218
246
|
<CopyButton value={hs.dienThoai} label="Copy phone number" />
|
|
219
247
|
</View>
|
|
220
248
|
</View>
|
|
@@ -248,8 +276,11 @@ function HoSoRow({ hs, daThu, selected, marked, selectable, onToggle, onPress }:
|
|
|
248
276
|
* to a BACK button + the pushed case id (the proper home for back — drawer
|
|
249
277
|
* chrome, not scroll content), and the sequence hides (stepping the root
|
|
250
278
|
* record from inside a linked one would be disorienting). */
|
|
251
|
-
function RecordDrawer({ hs, daThu: _daThu, onPaid: _onPaid, onClose, onPrev, onNext, position }: {
|
|
279
|
+
function RecordDrawer({ hs, daThu: _daThu, onPaid: _onPaid, onClose, onPrev, onNext, position, openSection }: {
|
|
252
280
|
hs: HoSo;
|
|
281
|
+
/** The section the record should land on — set when the row's comment bubble
|
|
282
|
+
* opened it, so the reader arrives in the conversation they pressed. */
|
|
283
|
+
openSection?: string;
|
|
253
284
|
daThu: boolean;
|
|
254
285
|
onPaid: () => void;
|
|
255
286
|
onClose: () => void;
|
|
@@ -288,7 +319,7 @@ function RecordDrawer({ hs, daThu: _daThu, onPaid: _onPaid, onClose, onPrev, onN
|
|
|
288
319
|
reading order as its last section. Rendering the component is what keeps
|
|
289
320
|
the two identical; the hand-built workspace that used to live here is
|
|
290
321
|
how they drifted — a checklist on this side, a pipeline on that one. */}
|
|
291
|
-
<TplRecord chrome="drawer" code={hs.ma} />
|
|
322
|
+
<TplRecord chrome="drawer" code={hs.ma} openSection={openSection} />
|
|
292
323
|
</Screen>
|
|
293
324
|
<Screen route="/record/:ma">{(p) => <LinkedRecordScreen ma={p.ma} />}</Screen>
|
|
294
325
|
</Drawer>
|
|
@@ -789,6 +820,9 @@ export function TplItemList() {
|
|
|
789
820
|
const [search, setSearch] = useState("");
|
|
790
821
|
const [page, setPage] = useState(0);
|
|
791
822
|
const [openMa, setOpenMa] = useState<string | null>(null);
|
|
823
|
+
/** Which section the record should OPEN on — set by the row's comment bubble,
|
|
824
|
+
* cleared on close so the next plain row press lands at the top. */
|
|
825
|
+
const [openSection, setOpenSection] = useState<string | undefined>(undefined);
|
|
792
826
|
// Paid state is LIFTED, not owned by the drawer: settling the ledger inside the
|
|
793
827
|
// drawer flips the row's Unpaid/Print/menu live, because both read one source.
|
|
794
828
|
const [paidMap, setPaidMap] = useState<Record<string, boolean>>({});
|
|
@@ -802,6 +836,7 @@ export function TplItemList() {
|
|
|
802
836
|
const ma = `RC-2026-${String(nextMa.current++).padStart(4, "0")}`;
|
|
803
837
|
const row: HoSo = {
|
|
804
838
|
ma,
|
|
839
|
+
binhLuan: 0,
|
|
805
840
|
khach,
|
|
806
841
|
dienThoai,
|
|
807
842
|
trangThai: "dangxuly",
|
|
@@ -1013,9 +1048,14 @@ export function TplItemList() {
|
|
|
1013
1048
|
|
|
1014
1049
|
{/* the register — eyebrow band (Table header) → list rows → totals + paging */}
|
|
1015
1050
|
<View>
|
|
1051
|
+
{/* THE COUNTED REGISTER. `count` is what MATCHED, not what this page
|
|
1052
|
+
shows — the reader's "how many" survives paging — and each row's
|
|
1053
|
+
`ordinal` continues across pages rather than restarting at 1, so the
|
|
1054
|
+
last row of page 2 reads 100 under a header that says 100. */}
|
|
1016
1055
|
<Table
|
|
1017
1056
|
columns={COLUMNS}
|
|
1018
1057
|
leading={24}
|
|
1058
|
+
count={filtered.length}
|
|
1019
1059
|
trailing={132}
|
|
1020
1060
|
sort={sort}
|
|
1021
1061
|
onSort={onSort}
|
|
@@ -1029,8 +1069,8 @@ export function TplItemList() {
|
|
|
1029
1069
|
/>
|
|
1030
1070
|
}
|
|
1031
1071
|
>
|
|
1032
|
-
{pageRows.map((r) => (
|
|
1033
|
-
<HoSoRow key={r.ma} hs={r} daThu={daThuOf(r)} selected={r.ma === openMa} marked={sel.has(r.ma)} selectable={isSelectable(r)} onToggle={(on) => sel.toggle(r.ma, on)} onPress={() => setOpenMa(r.ma)} />
|
|
1072
|
+
{pageRows.map((r, i) => (
|
|
1073
|
+
<HoSoRow key={r.ma} hs={r} ordinal={page * PAGE_SIZE + i + 1} daThu={daThuOf(r)} selected={r.ma === openMa} marked={sel.has(r.ma)} selectable={isSelectable(r)} onToggle={(on) => sel.toggle(r.ma, on)} onPress={() => setOpenMa(r.ma)} onOpenComments={() => { setOpenSection("comments"); setOpenMa(r.ma); }} />
|
|
1034
1074
|
))}
|
|
1035
1075
|
</Table>
|
|
1036
1076
|
{pageRows.length === 0 ? (
|
|
@@ -1078,9 +1118,10 @@ export function TplItemList() {
|
|
|
1078
1118
|
<ScreenRouter key={openRow.ma}>
|
|
1079
1119
|
<RecordDrawer
|
|
1080
1120
|
hs={openRow}
|
|
1121
|
+
openSection={openSection}
|
|
1081
1122
|
daThu={daThuOf(openRow)}
|
|
1082
1123
|
onPaid={() => setPaidMap((prev) => ({ ...prev, [openRow.ma]: true }))}
|
|
1083
|
-
onClose={() => setOpenMa(null)}
|
|
1124
|
+
onClose={() => { setOpenMa(null); setOpenSection(undefined); }}
|
|
1084
1125
|
onPrev={seqIndex > 0 ? () => setOpenMa(pageRows[seqIndex - 1].ma) : undefined}
|
|
1085
1126
|
onNext={seqIndex >= 0 && seqIndex < pageRows.length - 1 ? () => setOpenMa(pageRows[seqIndex + 1].ma) : undefined}
|
|
1086
1127
|
position={seqIndex >= 0 ? `${seqIndex + 1}/${pageRows.length}` : undefined}
|