@arcforgelabs/flow 2026.10.51 → 2026.10.53-1
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/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/server/api/captures.js +26 -4
- package/server/api/invoicing.js +1 -1
- package/server/app.js +2 -0
- package/server/auth/people.js +3 -3
- package/server/core/ai.js +2 -1
- package/server/core/capture-match.js +158 -0
- package/server/core/captures.js +141 -7
- package/server/core/invoice-rules.js +16 -5
- package/server/core/reader.js +1 -10
- package/server/core/sending.js +7 -3
- package/server/db/migrations/0024_rule_icon.sql +5 -0
- package/server/db/migrations/meta/0024_snapshot.json +4703 -0
- package/server/db/migrations/meta/_journal.json +8 -1
- package/server/db/schema.js +3 -0
- package/server/integrations/seazona/history/backfill.js +39 -9
- package/server/integrations/seazona/index.js +1 -12
- package/server/integrations/seazona/lanes.js +44 -27
- package/server/integrations/seazona/stage-moves.js +3 -13
- package/server/integrations/sentry/index.js +6 -6
- package/server/integrations/setup.js +1 -0
- package/server/integrations/xero/drafts.js +27 -13
- package/server/ui/app-page.js +1 -1
- package/server/ui/app.js +61 -20
- package/server/ui/capture-fields.js +17 -0
- package/server/ui/captures.js +289 -0
- package/server/ui/columns.js +68 -3
- package/server/ui/custom-fields.js +3 -9
- package/server/ui/flow.css +94 -18
- package/server/ui/invoice-rules.js +10 -2
- package/server/ui/invoicing-settings.js +2 -8
- package/server/ui/invoicing.js +12 -8
- package/server/ui/job-view.js +6 -3
- package/server/ui/jobs.js +40 -117
- package/server/ui/kit.js +86 -1
- package/server/ui/list-views.js +47 -3
- package/server/ui/mobile.js +45 -5
- package/server/ui/nav-pins.js +4 -2
- package/server/ui/price-list.js +138 -69
- package/server/ui/routes.js +4 -1
- package/server/ui/saved-views.js +2 -0
- package/server/ui/view-bar.js +61 -2
package/server/ui/view-bar.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The view bar over a list: chips that open a small panel (Group, Sort,
|
|
2
2
|
// Filter, Columns), as on ClickUp. Every list page builds its chips here, so
|
|
3
|
-
// they look and work the same: Jobs
|
|
3
|
+
// they look and work the same: Jobs, Invoices, Reconcile and the Price list.
|
|
4
4
|
//
|
|
5
5
|
// viewBar(h) answers the parts:
|
|
6
6
|
// menu(icon, build, { rows, clearLabel }) a chip, its × and its panel.
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// option(text, checked, onclick, role, icon) a row in a panel, with a tick.
|
|
10
10
|
// title, choose, bin, addButton, rowsFoot, clearAll the parts of a rows
|
|
11
11
|
// panel: a title, rows of selects with a bin, a foot with Add and Clear.
|
|
12
|
+
// groupMenu(config), sortMenu(config) the Group and Sort chips (below).
|
|
12
13
|
// filterMenu(config) the Filter chip, as ClickUp's: groups of rows of
|
|
13
14
|
// field, operator and value, And / Or in a group and between groups,
|
|
14
15
|
// "Add nested filter", "Add filter" and "Clear all" (job-view.js has
|
|
@@ -161,6 +162,64 @@ export function viewBar(h) {
|
|
|
161
162
|
pop.style.left = `${Math.round(left)}px`;
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
// ── Group by ──
|
|
166
|
+
// One row: what to group by, the group order, and a bin that takes the
|
|
167
|
+
// groups away when `clearable()` (Jobs' board always has lanes). The page
|
|
168
|
+
// names the chip with set(), as only it knows its group's name.
|
|
169
|
+
// options() [{ value, label } | { group, options }]
|
|
170
|
+
// view() { group, groupDir }; group 'none' is no groups
|
|
171
|
+
function groupMenu({ options, view, change, clearable = () => true }) {
|
|
172
|
+
return menu(ICONS.group, (redraw) => {
|
|
173
|
+
const { group, groupDir } = view();
|
|
174
|
+
const none = group === 'none';
|
|
175
|
+
return [
|
|
176
|
+
title('Group by'),
|
|
177
|
+
h('div', { class: 'fx-rows' }, h('div', { class: 'fx-rows__row fx-rows__row--group' },
|
|
178
|
+
choose('Group by', none ? '' : group, [...(none ? [{ value: '', label: 'Choose…', disabled: true }] : []), ...options()], (value) => { change({ group: value }); redraw(); }),
|
|
179
|
+
choose('Group order', groupDir, [{ value: 'asc', label: 'Ascending' }, { value: 'desc', label: 'Descending' }], (value) => { change({ groupDir: value }); redraw(); }, { disabled: none }),
|
|
180
|
+
clearable() && !none ? bin('Remove the grouping', () => { change({ group: 'none' }); redraw(); }) : h('span', { class: 'fx-rows__gap' }))),
|
|
181
|
+
];
|
|
182
|
+
}, { rows: true, clearLabel: 'Remove the grouping' });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── Sort ──
|
|
186
|
+
// Rows of sorts, each applied in turn; a list's column heads sort too.
|
|
187
|
+
// options() [{ value, label } | { group, options }]
|
|
188
|
+
// view() { sorts: [{ by, dir }] }
|
|
189
|
+
// dirWords(by) the two directions' words, as ['A to Z', 'Z to A']
|
|
190
|
+
// nameOf(by) the chip's name for a sort
|
|
191
|
+
function sortMenu({ options, view, change, dirWords, nameOf, empty = 'No sort.' }) {
|
|
192
|
+
const setSorts = (sorts) => change({ sorts });
|
|
193
|
+
const control = menu(ICONS.sort, (redraw) => {
|
|
194
|
+
const { sorts } = view();
|
|
195
|
+
const unused = options().flatMap((o) => o.options ?? [o]).filter((o) => !sorts.some((s) => s.by === o.value));
|
|
196
|
+
return [
|
|
197
|
+
title('Sort'),
|
|
198
|
+
sorts.length ? h('div', { class: 'fx-rows' }, sorts.map((s, i) => {
|
|
199
|
+
const [up, down] = dirWords(s.by);
|
|
200
|
+
const others = sorts.filter((_, n) => n !== i).map((x) => x.by);
|
|
201
|
+
const shown = options().map((o) => (o.group ? { ...o, options: o.options.filter((x) => !others.includes(x.value)) } : o)).filter((o) => o.group || !others.includes(o.value));
|
|
202
|
+
return h('div', { class: 'fx-rows__row fx-rows__row--sort' },
|
|
203
|
+
h('span', { class: 'fx-rows__lead' }, i ? 'then' : 'Sort by'),
|
|
204
|
+
choose('Sort by', s.by, shown, (by) => { setSorts(sorts.map((x, n) => (n === i ? { by, dir: x.dir } : x))); redraw(); }),
|
|
205
|
+
choose('Order', s.dir, [{ value: 'asc', label: up }, { value: 'desc', label: down }], (dir) => { setSorts(sorts.map((x, n) => (n === i ? { ...x, dir } : x))); redraw(); }),
|
|
206
|
+
bin('Remove this sort', () => { setSorts(sorts.filter((_, n) => n !== i)); redraw(); }));
|
|
207
|
+
})) : h('p', { class: 'fx-rows__empty' }, empty),
|
|
208
|
+
rowsFoot(
|
|
209
|
+
sorts.length < 5 && unused.length ? addButton('Add sort', () => { setSorts([...sorts, { by: unused[0].value, dir: 'asc' }]); redraw(); }) : h('span'),
|
|
210
|
+
sorts.length ? clearAll(() => { setSorts([]); redraw(); }) : null),
|
|
211
|
+
];
|
|
212
|
+
}, { rows: true, clearLabel: 'Clear the sort' });
|
|
213
|
+
// The chip names the sort that is on.
|
|
214
|
+
const draw = () => {
|
|
215
|
+
const { sorts } = view();
|
|
216
|
+
const [first] = sorts;
|
|
217
|
+
control.set(sorts.length > 1 ? `Sort · ${sorts.length}` : first ? `Sort: ${nameOf(first.by)} ${first.dir === 'desc' ? '↓' : '↑'}` : 'Sort', sorts.length > 0,
|
|
218
|
+
sorts.length ? () => setSorts([]) : null);
|
|
219
|
+
};
|
|
220
|
+
return { ...control, draw };
|
|
221
|
+
}
|
|
222
|
+
|
|
164
223
|
// ── Filters ──
|
|
165
224
|
// As ClickUp's: groups of rows of field, operator and value, with a bin on
|
|
166
225
|
// each row. A group's second row has And / Or for the rows in it; the
|
|
@@ -329,7 +388,7 @@ export function viewBar(h) {
|
|
|
329
388
|
return { ...control, draw };
|
|
330
389
|
}
|
|
331
390
|
|
|
332
|
-
return { menu, option, title, choose, bin, addButton, rowsFoot, clearAll, filterMenu };
|
|
391
|
+
return { menu, option, title, choose, bin, addButton, rowsFoot, clearAll, groupMenu, sortMenu, filterMenu };
|
|
333
392
|
}
|
|
334
393
|
|
|
335
394
|
// A list's own search: a round search icon in the view bar that opens into a
|