@arcforgelabs/flow 2026.10.46 → 2026.10.47
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/VISION.md +10 -10
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/server/api/invoicing.js +56 -16
- package/server/auth/people.js +3 -1
- package/server/auth/routes.js +4 -1
- package/server/core/invoice-rules.js +34 -4
- package/server/core/invoices.js +40 -1
- package/server/core/invoicing-rules.js +20 -3
- package/server/core/job-board.js +1 -1
- package/server/core/sending.js +76 -22
- package/server/integrations/sentry/index.js +14 -7
- package/server/ui/alerts.js +2 -1
- package/server/ui/app.js +105 -25
- package/server/ui/bulk-bar.js +18 -5
- package/server/ui/columns.js +5 -2
- package/server/ui/custom-fields.js +22 -7
- package/server/ui/flow.css +235 -24
- package/server/ui/invoicing.js +254 -55
- package/server/ui/job-board.js +10 -0
- package/server/ui/job-change.js +4 -3
- package/server/ui/job-view.js +3 -1
- package/server/ui/jobs.js +150 -19
- package/server/ui/kit.js +198 -43
- package/server/ui/list-views.js +50 -10
- package/server/ui/mobile.js +726 -0
- package/server/ui/monitoring.js +51 -0
- package/server/ui/nav-pins.js +41 -0
- package/server/ui/pager.js +57 -10
- package/server/ui/price-list.js +1 -1
- package/server/ui/price-reconcile.js +12 -12
- package/server/ui/reconcile.js +18 -17
- package/server/ui/routes.js +1 -1
- package/server/ui/saved-views.js +2 -1
- package/server/ui/sidecar.js +106 -6
- package/server/ui/view-bar.js +22 -11
- package/server/ui/xero-import.js +48 -19
package/server/ui/xero-import.js
CHANGED
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
import { bulkBar } from '/assets/ui/bulk-bar.js';
|
|
11
11
|
import { lucide } from '/assets/ui/custom-fields.js';
|
|
12
12
|
import { formatCents } from '/assets/ui/invoicing.js';
|
|
13
|
+
import { applyPhoneRule, assignPhoneSlots, phoneSlot } from './mobile.js';
|
|
14
|
+
|
|
15
|
+
// The list rows use the same phone slots as a column list.
|
|
16
|
+
const phoneAt = (defs) => {
|
|
17
|
+
const columns = assignPhoneSlots(defs);
|
|
18
|
+
return (index) => {
|
|
19
|
+
const slot = phoneSlot(columns[index]);
|
|
20
|
+
return slot ? { 'data-phone-slot': slot } : {};
|
|
21
|
+
};
|
|
22
|
+
};
|
|
13
23
|
|
|
14
24
|
const XERO_STATUS = { DRAFT: 'Draft', SUBMITTED: 'Awaiting approval', AUTHORISED: 'Approved', PAID: 'Paid' };
|
|
15
25
|
const XERO_TONE = { DRAFT: 'neutral', SUBMITTED: 'warn', AUTHORISED: 'info', PAID: 'success' };
|
|
@@ -212,13 +222,23 @@ export async function renderXeroImport(main, ui) {
|
|
|
212
222
|
drawBar();
|
|
213
223
|
} });
|
|
214
224
|
const sure = rows.filter((r) => r.sure);
|
|
225
|
+
const at = phoneAt([
|
|
226
|
+
{ kind: 'text', label: 'Number' },
|
|
227
|
+
{ kind: 'client', label: 'Client', phone: 'title' },
|
|
228
|
+
{ kind: 'date', label: 'Date', phone: 'meta' },
|
|
229
|
+
{ kind: 'total', num: true, label: 'Total' },
|
|
230
|
+
{ kind: 'status', label: 'Match', phone: 'meta' },
|
|
231
|
+
{ kind: 'text', label: 'Flow', phone: 'meta' },
|
|
232
|
+
{ kind: 'date', label: 'Flow date', phone: 'hide' },
|
|
233
|
+
{ kind: 'total', label: 'Flow total', phone: 'hide' },
|
|
234
|
+
]);
|
|
215
235
|
host.replaceChildren(
|
|
216
236
|
h('div', { class: 'fx-xi__bulk' }, icon('list-checks'), h('span', { class: 'fx-xi__bulk-text' }, 'Tick rows, then use the bar below.'),
|
|
217
237
|
canAct && sure.length ? h('button', { class: 'fx-btn fx-btn--sm', type: 'button', onclick: () => {
|
|
218
238
|
for (const r of sure) { ticked.add(r.xeroInvoiceId); boxes.get(r.xeroInvoiceId).checked = true; }
|
|
219
239
|
drawBar();
|
|
220
240
|
} }, icon('check-check'), `Tick ${sure.length} sure`) : null),
|
|
221
|
-
h('div', { class: 'fx-xi__scroll' }, h('table', { class: 'fx-table fx-table--rows fx-xi-list' },
|
|
241
|
+
applyPhoneRule(h('div', { class: 'fx-xi__scroll' }, h('table', { class: 'fx-table fx-table--rows fx-xi-list' },
|
|
222
242
|
h('thead', {}, h('tr', {},
|
|
223
243
|
canAct ? h('th', { class: 'fx-tick-cell' }, all) : null,
|
|
224
244
|
h('th', { colspan: '4' }, xeroMark()), h('th', { class: 'fx-xi-list__mid' }), h('th', { colspan: '3' }, flowMark()))),
|
|
@@ -231,15 +251,15 @@ export async function renderXeroImport(main, ui) {
|
|
|
231
251
|
boxes.set(row.xeroInvoiceId, box);
|
|
232
252
|
return h('tr', {},
|
|
233
253
|
canAct ? h('td', { class: 'fx-tick-cell' }, box) : null,
|
|
234
|
-
h('td', { class: 'fx-num' }, row.number ?? '—'),
|
|
235
|
-
h('td', {}, row.clientName ?? '—'),
|
|
236
|
-
h('td', { class: 'fx-dim' }, shortDate(row.issuedOn)),
|
|
237
|
-
h('td', { class: 'fx-num fx-strong' }, money(row.totalCents)),
|
|
238
|
-
h('td', { class: 'fx-xi-list__mid' }, row.sure ? icon('check-check', 'Sure') : fitIcon(fit), fit.count > 1 ? h('span', { class: 'fx-dim' }, `${fit.count}`) : null),
|
|
239
|
-
h('td', { class: 'fx-num' }, fit.name),
|
|
240
|
-
h('td', { class: 'fx-dim' }, shortDate(fit.date)),
|
|
241
|
-
h('td', { class: 'fx-num fx-strong' }, money(fit.totalCents)));
|
|
242
|
-
})))));
|
|
254
|
+
h('td', { class: 'fx-num', ...at(0) }, row.number ?? '—'),
|
|
255
|
+
h('td', { ...at(1) }, row.clientName ?? '—'),
|
|
256
|
+
h('td', { class: 'fx-dim', ...at(2) }, shortDate(row.issuedOn)),
|
|
257
|
+
h('td', { class: 'fx-num fx-strong', ...at(3) }, money(row.totalCents)),
|
|
258
|
+
h('td', { class: 'fx-xi-list__mid', ...at(4) }, row.sure ? icon('check-check', 'Sure') : fitIcon(fit), fit.count > 1 ? h('span', { class: 'fx-dim' }, `${fit.count}`) : null),
|
|
259
|
+
h('td', { class: 'fx-num', ...at(5) }, fit.name),
|
|
260
|
+
h('td', { class: 'fx-dim', ...at(6) }, shortDate(fit.date)),
|
|
261
|
+
h('td', { class: 'fx-num fx-strong', ...at(7) }, money(fit.totalCents)));
|
|
262
|
+
})))), 'columnTable'));
|
|
243
263
|
drawBar();
|
|
244
264
|
}
|
|
245
265
|
|
|
@@ -259,13 +279,22 @@ export async function renderXeroImport(main, ui) {
|
|
|
259
279
|
} });
|
|
260
280
|
const more = h('div', {});
|
|
261
281
|
const STATE = { linked: ['check', 'In Flow'], invoice_fits: ['receipt', 'A Flow invoice fits'], job_fits: ['briefcase', 'A job fits'], none: ['circle-dashed', 'Not in Flow'] };
|
|
282
|
+
const at = phoneAt([
|
|
283
|
+
{ kind: 'status', label: 'State', phone: 'meta' },
|
|
284
|
+
{ kind: 'text', label: 'Number' },
|
|
285
|
+
{ kind: 'client', label: 'Client', phone: 'title' },
|
|
286
|
+
{ kind: 'date', label: 'Date', phone: 'meta' },
|
|
287
|
+
{ kind: 'total', num: true, label: 'Total' },
|
|
288
|
+
{ kind: 'status', label: 'Xero', phone: 'meta' },
|
|
289
|
+
{ kind: 'text', label: 'Flow', phone: 'hide' },
|
|
290
|
+
]);
|
|
262
291
|
host.replaceChildren(
|
|
263
292
|
h('div', { class: 'fx-xi__bulk' }, icon('search'), search),
|
|
264
|
-
h('div', { class: 'fx-xi__scroll' }, h('table', { class: 'fx-table fx-table--rows fx-xi-list' },
|
|
293
|
+
applyPhoneRule(h('div', { class: 'fx-xi__scroll' }, h('table', { class: 'fx-table fx-table--rows fx-xi-list' },
|
|
265
294
|
h('thead', {}, h('tr', {},
|
|
266
295
|
canAct ? h('th', { class: 'fx-tick-cell' }, all) : null,
|
|
267
296
|
h('th', {}, ''), h('th', {}, 'Number'), h('th', {}, 'Client'), h('th', {}, 'Date'), h('th', { class: 'fx-num' }, 'Total'), h('th', {}, xeroMark()), h('th', {}, flowMark()))),
|
|
268
|
-
body)),
|
|
297
|
+
body)), 'columnTable'),
|
|
269
298
|
more);
|
|
270
299
|
let timer = null;
|
|
271
300
|
search.addEventListener('input', () => {
|
|
@@ -295,13 +324,13 @@ export async function renderXeroImport(main, ui) {
|
|
|
295
324
|
} });
|
|
296
325
|
return h('tr', { 'data-state': row.state },
|
|
297
326
|
canAct ? h('td', { class: 'fx-tick-cell' }, box) : null,
|
|
298
|
-
h('td', { class: 'fx-xi-list__mid' }, row.state === 'linked' || row.state === 'none' ? icon(glyph, label) : h('a', { href: '/xero-import', onclick: (event) => ui.go(event), 'aria-label': label, title: label }, icon(glyph))),
|
|
299
|
-
h('td', { class: 'fx-num' }, row.number ?? '—'),
|
|
300
|
-
h('td', {}, row.clientName ?? '—'),
|
|
301
|
-
h('td', { class: 'fx-dim' }, shortDate(row.issuedOn)),
|
|
302
|
-
h('td', { class: 'fx-num fx-strong' }, money(row.totalCents)),
|
|
303
|
-
h('td', {}, pill(row)),
|
|
304
|
-
h('td', { class: 'fx-num' }, row.state === 'linked' ? go(`/invoices/${row.link.invoiceId}`, row.link.number ?? 'Draft') : ''));
|
|
327
|
+
h('td', { class: 'fx-xi-list__mid', ...at(0) }, row.state === 'linked' || row.state === 'none' ? icon(glyph, label) : h('a', { href: '/xero-import', onclick: (event) => ui.go(event), 'aria-label': label, title: label }, icon(glyph))),
|
|
328
|
+
h('td', { class: 'fx-num', ...at(1) }, row.number ?? '—'),
|
|
329
|
+
h('td', { ...at(2) }, row.clientName ?? '—'),
|
|
330
|
+
h('td', { class: 'fx-dim', ...at(3) }, shortDate(row.issuedOn)),
|
|
331
|
+
h('td', { class: 'fx-num fx-strong', ...at(4) }, money(row.totalCents)),
|
|
332
|
+
h('td', { ...at(5) }, pill(row)),
|
|
333
|
+
h('td', { class: 'fx-num', ...at(6) }, row.state === 'linked' ? go(`/invoices/${row.link.invoiceId}`, row.link.number ?? 'Draft') : ''));
|
|
305
334
|
};
|
|
306
335
|
async function load() {
|
|
307
336
|
let page;
|