u
This commit is contained in:
@@ -1,90 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { render, Box, Text, useApp, useInput } from 'ink';
|
||||
import chalk from 'chalk';
|
||||
import { getCategories } from '../src/services/misc/getCategories.js';
|
||||
import { closePool } from '../src/db/mssqlClient.js';
|
||||
|
||||
import '../src/config/env.js';
|
||||
|
||||
const e = React.createElement;
|
||||
|
||||
const VISIBLE_ROWS = 20;
|
||||
|
||||
function flattenTree(nodes, depth = 0, out = []) {
|
||||
for (const node of nodes) {
|
||||
out.push({ ...node, depth });
|
||||
flattenTree(node.children, depth + 1, out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function formatRow(row) {
|
||||
const indent = ' '.repeat(row.depth);
|
||||
const count = chalk.gray(` (${row.count})`);
|
||||
return `${indent}${row.cName ?? '—'}${count} ${chalk.dim(String(row.kKategorie))}`;
|
||||
}
|
||||
|
||||
const CategoryBrowser = ({ rows }) => {
|
||||
const { exit } = useApp();
|
||||
const [selected, setSelected] = useState(0);
|
||||
const windowStart = Math.max(0, Math.min(selected - Math.floor(VISIBLE_ROWS / 2), rows.length - VISIBLE_ROWS));
|
||||
const visible = rows.slice(windowStart, windowStart + VISIBLE_ROWS);
|
||||
const current = rows[selected];
|
||||
|
||||
const quit = () => {
|
||||
closePool()
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
exit();
|
||||
process.exit(0);
|
||||
});
|
||||
};
|
||||
|
||||
useInput((input, key) => {
|
||||
if (input === 'q' || (key.ctrl && input === 'c')) {
|
||||
quit();
|
||||
return;
|
||||
}
|
||||
if (key.upArrow) {
|
||||
setSelected((s) => Math.max(0, s - 1));
|
||||
}
|
||||
if (key.downArrow) {
|
||||
setSelected((s) => Math.min(rows.length - 1, s + 1));
|
||||
}
|
||||
});
|
||||
|
||||
return e(Box, { flexDirection: 'column' },
|
||||
e(Text, null, chalk.cyan.bold(`=== Categories (${rows.length}) ===`)),
|
||||
e(Box, { marginTop: 1, flexDirection: 'column' },
|
||||
visible.map((row, index) => {
|
||||
const absoluteIndex = windowStart + index;
|
||||
const prefix = absoluteIndex === selected ? chalk.cyan('> ') : ' ';
|
||||
const label = absoluteIndex === selected
|
||||
? chalk.cyan.bold(formatRow(row))
|
||||
: formatRow(row);
|
||||
return e(Text, { key: row.kKategorie }, prefix, label);
|
||||
})
|
||||
),
|
||||
rows.length > VISIBLE_ROWS
|
||||
? e(Text, { dimColor: true }, `Showing ${windowStart + 1}-${windowStart + visible.length} of ${rows.length}`)
|
||||
: null,
|
||||
current
|
||||
? e(Box, { marginTop: 1, flexDirection: 'column' },
|
||||
e(Text, { dimColor: true },
|
||||
`kKategorie: ${current.kKategorie} parent: ${current.kOberKategorie} sort: ${current.nSort ?? '—'} articles: ${current.count}`
|
||||
)
|
||||
)
|
||||
: null,
|
||||
e(Box, { marginTop: 1 }, e(Text, null, chalk.gray('↑↓ navigate, q exit')))
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
const { tree } = await getCategories();
|
||||
const rows = flattenTree(tree);
|
||||
render(e(CategoryBrowser, { rows }));
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
await closePool().catch(() => {});
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import React from 'react';
|
||||
import { render, Box, Text, useApp, useInput } from 'ink';
|
||||
import chalk from 'chalk';
|
||||
import { getSteuer } from '../src/services/misc/getSteuer.js';
|
||||
import { closePool } from '../src/db/mssqlClient.js';
|
||||
|
||||
import '../src/config/env.js';
|
||||
|
||||
const e = React.createElement;
|
||||
|
||||
function SteuerTable({ steuerklassen }) {
|
||||
return e(Box, { flexDirection: 'column' },
|
||||
e(Text, null, chalk.cyan.bold('=== Steuerklassen ===')),
|
||||
e(Box, { marginTop: 1, flexDirection: 'column' },
|
||||
e(Text, { dimColor: true }, ' ID Rate Name'),
|
||||
...steuerklassen.map((row) =>
|
||||
e(Text, { key: row.kSteuerklasse },
|
||||
' ',
|
||||
chalk.white(String(row.kSteuerklasse).padEnd(4)),
|
||||
chalk.yellow(`${String(row.fSteuersatz).padStart(3)}%`.padEnd(8)),
|
||||
row.cName ?? '—',
|
||||
row.nStandard === 1 ? chalk.gray(' (standard)') : ''
|
||||
)
|
||||
)
|
||||
),
|
||||
e(Box, { marginTop: 1 }, e(Text, null, chalk.gray('Press any key to exit')))
|
||||
);
|
||||
}
|
||||
|
||||
const App = ({ steuerklassen }) => {
|
||||
const { exit } = useApp();
|
||||
|
||||
useInput(() => {
|
||||
closePool()
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
exit();
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
return e(SteuerTable, { steuerklassen });
|
||||
};
|
||||
|
||||
try {
|
||||
const { steuerklassen } = await getSteuer();
|
||||
render(e(App, { steuerklassen }));
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
await closePool().catch(() => {});
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { render, Box, Text, useApp, useInput } from 'ink';
|
||||
import TextInput from 'ink-text-input';
|
||||
import chalk from 'chalk';
|
||||
@@ -48,6 +48,17 @@ const App = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [emails, setEmails] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode !== MODES.LIST) {
|
||||
return;
|
||||
}
|
||||
setEmails(null);
|
||||
googleAuth.listAllowedEmails()
|
||||
.then(setEmails)
|
||||
.catch((err) => finish(false, err.message));
|
||||
}, [mode]);
|
||||
|
||||
const finish = (success, text) => {
|
||||
if (success) {
|
||||
@@ -72,28 +83,28 @@ const App = () => {
|
||||
setMode(action);
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
const handleAdd = async () => {
|
||||
try {
|
||||
const trimmed = email.trim();
|
||||
if (!trimmed || !trimmed.includes('@')) {
|
||||
finish(false, 'Invalid email address');
|
||||
return;
|
||||
}
|
||||
googleAuth.addAllowedEmail(trimmed);
|
||||
await googleAuth.addAllowedEmail(trimmed);
|
||||
finish(true, `Added ${trimmed.toLowerCase()} to google_auth`);
|
||||
} catch (err) {
|
||||
finish(false, err.message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = () => {
|
||||
const handleRemove = async () => {
|
||||
try {
|
||||
const trimmed = email.trim();
|
||||
if (!trimmed) {
|
||||
finish(false, 'Email is required');
|
||||
return;
|
||||
}
|
||||
const removed = googleAuth.removeAllowedEmail(trimmed);
|
||||
const removed = await googleAuth.removeAllowedEmail(trimmed);
|
||||
finish(true, removed ? `Removed ${trimmed.toLowerCase()}` : `Email not found: ${trimmed}`);
|
||||
} catch (err) {
|
||||
finish(false, err.message);
|
||||
@@ -107,7 +118,9 @@ const App = () => {
|
||||
return e(Text, null, chalk.red(`Error: ${error}`));
|
||||
}
|
||||
if (mode === MODES.LIST) {
|
||||
const emails = googleAuth.listAllowedEmails();
|
||||
if (!emails) {
|
||||
return e(Text, null, chalk.gray('Loading...'));
|
||||
}
|
||||
if (emails.length === 0) {
|
||||
finish(true, 'No allowed emails in google_auth');
|
||||
return e(Text, null, chalk.yellow('No allowed emails...'));
|
||||
|
||||
@@ -1,401 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { render, Box, Text, useApp, useInput } from 'ink';
|
||||
import TextInput from 'ink-text-input';
|
||||
import chalk from 'chalk';
|
||||
import { getArticle } from '../src/services/article/getArticle.js';
|
||||
import { searchArticles } from '../src/services/article/searchArticles.js';
|
||||
import { closePool } from '../src/db/mssqlClient.js';
|
||||
import { bruttoFromNetto, loadSteuerklassen } from '../src/services/misc/getSteuer.js';
|
||||
|
||||
import '../src/config/env.js';
|
||||
|
||||
const e = React.createElement;
|
||||
|
||||
const MODES = {
|
||||
SEARCH: 'search',
|
||||
LOADING: 'loading',
|
||||
LIST: 'list',
|
||||
RESULT: 'result',
|
||||
ERROR: 'error',
|
||||
};
|
||||
|
||||
const VISIBLE_ROWS = 15;
|
||||
|
||||
const eur = (value) => {
|
||||
if (value == null || Number.isNaN(Number(value))) {
|
||||
return '—';
|
||||
}
|
||||
return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(value);
|
||||
};
|
||||
|
||||
function formatPrice(row, steuerklassen) {
|
||||
if (row.fVKNEtto == null) {
|
||||
return '—';
|
||||
}
|
||||
const brutto = bruttoFromNetto(row.fVKNEtto, row.kSteuerklasse, steuerklassen);
|
||||
return brutto == null ? '—' : eur(brutto);
|
||||
}
|
||||
|
||||
function hasValue(value) {
|
||||
if (value == null || value === '' || value === 0) {
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.length > 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function DetailRow({ label, value, alwaysShow = false }) {
|
||||
if (!alwaysShow && !hasValue(value) && value !== 0 && value !== '0') {
|
||||
return null;
|
||||
}
|
||||
const text = value == null || value === '' ? '—' : String(value);
|
||||
return e(Box, null,
|
||||
e(Text, { dimColor: true }, `${label.padEnd(14)} `),
|
||||
e(Text, null, text)
|
||||
);
|
||||
}
|
||||
|
||||
function Section({ title, children }) {
|
||||
const items = React.Children.toArray(children).filter(Boolean);
|
||||
if (!items.length) {
|
||||
return null;
|
||||
}
|
||||
return e(Box, { flexDirection: 'column', marginTop: 1 },
|
||||
e(Text, null, chalk.cyan.bold(title)),
|
||||
...items
|
||||
);
|
||||
}
|
||||
|
||||
function ArticleDetail({ article, steuerklassen }) {
|
||||
const brutto = bruttoFromNetto(article.fVKNEtto, article.kSteuerklasse, steuerklassen);
|
||||
const aktiv = article.cAktiv === 'Y'
|
||||
? chalk.green('active')
|
||||
: article.cAktiv === 'N'
|
||||
? chalk.red('inactive')
|
||||
: article.cAktiv;
|
||||
|
||||
return e(Box, { flexDirection: 'column' },
|
||||
e(Text, null, chalk.white.bold(article.cName ?? '—')),
|
||||
e(Text, { dimColor: true }, article.cArtNr ?? '—'),
|
||||
|
||||
e(Section, { title: 'Overview' },
|
||||
e(DetailRow, { label: 'Artikel-ID', value: article.kArtikel }),
|
||||
e(DetailRow, { label: 'Status', value: aktiv }),
|
||||
e(DetailRow, { label: 'Hersteller', value: article.cHersteller }),
|
||||
e(DetailRow, { label: 'Barcode', value: article.cBarcode }),
|
||||
article.EANs?.length
|
||||
? e(DetailRow, { label: 'EANs', value: article.EANs.map((row) => row.cEAN).join(', ') })
|
||||
: null
|
||||
),
|
||||
|
||||
e(Section, { title: 'Pricing' },
|
||||
e(DetailRow, { label: 'VK brutto', value: brutto != null ? eur(brutto) : null }),
|
||||
e(DetailRow, { label: 'VK netto', value: article.fVKNEtto != null ? eur(article.fVKNEtto) : null }),
|
||||
e(DetailRow, { label: 'Steuerklasse', value: article.kSteuerklasse }),
|
||||
hasValue(article.fUVP) ? e(DetailRow, { label: 'UVP', value: eur(article.fUVP) }) : null,
|
||||
article.staffel?.length
|
||||
? e(Box, { flexDirection: 'column', marginTop: 0 },
|
||||
e(Text, { dimColor: true }, 'Staffel'),
|
||||
...article.staffel.map((row, index) =>
|
||||
e(Text, { key: index, dimColor: true }, ` ab ${row.menge} ${row.fVKBrutto} €`)
|
||||
)
|
||||
)
|
||||
: null
|
||||
),
|
||||
|
||||
e(Section, { title: 'Stock' },
|
||||
e(DetailRow, { label: 'Lager 1', value: article.fBestand, alwaysShow: true }),
|
||||
hasValue(article.garage) ? e(DetailRow, { label: 'Garage', value: article.garage }) : null,
|
||||
hasValue(article.keller) ? e(DetailRow, { label: 'Keller', value: article.keller }) : null,
|
||||
article.fAndereLager?.length
|
||||
? e(DetailRow, {
|
||||
label: 'Andere Lager',
|
||||
value: article.fAndereLager.map((row) => row.fSumme).join(', '),
|
||||
})
|
||||
: null,
|
||||
article.incoming?.length
|
||||
? e(Text, { dimColor: true }, ` ${article.incoming.length} open supplier order(s)`)
|
||||
: null
|
||||
),
|
||||
|
||||
e(Section, { title: 'Product' },
|
||||
e(DetailRow, { label: 'Versandklasse', value: article.tVersandklassecName ?? article.kVersandklasse }),
|
||||
hasValue(article.fGewicht) ? e(DetailRow, { label: 'Gewicht', value: `${article.fGewicht} kg` }) : null,
|
||||
hasValue(article.shops) ? e(DetailRow, { label: 'Shops', value: article.shops }) : null,
|
||||
article.KATs?.length
|
||||
? e(DetailRow, { label: 'Kategorien', value: article.KATs.map((row) => row.kKategorie).join(', ') })
|
||||
: null,
|
||||
article.merkmale?.length
|
||||
? e(Box, { flexDirection: 'column' },
|
||||
e(Text, { dimColor: true }, 'Merkmale'),
|
||||
...article.merkmale.map((row, index) =>
|
||||
e(Text, { key: index, dimColor: true }, ` ${row.cName}: ${row.cWert}`)
|
||||
)
|
||||
)
|
||||
: null
|
||||
),
|
||||
|
||||
article.lief?.length
|
||||
? e(Section, { title: `Suppliers (${article.lief.length})` },
|
||||
...article.lief.slice(0, 8).map((row, index) =>
|
||||
e(Text, { key: index, dimColor: true },
|
||||
` ${row.nStandard ? chalk.yellow('* ') : ' '}${row.cFirma ?? '—'} ${row.cLiefArtNr ?? '—'} EK ${eur(row.fEKNetto)}`
|
||||
)
|
||||
),
|
||||
article.lief.length > 8
|
||||
? e(Text, { dimColor: true }, ` … +${article.lief.length - 8} more`)
|
||||
: null
|
||||
)
|
||||
: null,
|
||||
|
||||
article.children?.length
|
||||
? e(Section, { title: `Variants (${article.children.length})` },
|
||||
...article.children.slice(0, 6).map((row, index) =>
|
||||
e(Text, { key: index, dimColor: true },
|
||||
` ${row.cArtNr} ${row.cName ?? '—'}`
|
||||
)
|
||||
)
|
||||
)
|
||||
: null,
|
||||
|
||||
article.liste?.length
|
||||
? e(Section, { title: `Stückliste (${article.liste.length})` },
|
||||
...article.liste.map((row, index) =>
|
||||
e(Text, { key: index, dimColor: true },
|
||||
` ${row.cArtNr} × ${row.fAnzahl} ${row.cName ?? '—'}`
|
||||
)
|
||||
)
|
||||
)
|
||||
: null,
|
||||
|
||||
article.orders?.length
|
||||
? e(Section, { title: 'Open orders' },
|
||||
...article.orders.map((row, index) =>
|
||||
e(Text, { key: index, dimColor: true }, ` ${row.cType} ${row.cNote ?? ''}`)
|
||||
)
|
||||
)
|
||||
: null,
|
||||
|
||||
hasValue(article.cBeschreibung)
|
||||
? e(Section, { title: 'Description' },
|
||||
e(Text, null, truncateForDisplay(article.cBeschreibung, 240))
|
||||
)
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
function truncateForDisplay(value, max = 80) {
|
||||
if (typeof value !== 'string' || value.length <= max) {
|
||||
return value;
|
||||
}
|
||||
return `${value.slice(0, max)}… (${value.length} chars)`;
|
||||
}
|
||||
|
||||
function formatListRow(row) {
|
||||
const stock = row.stock ?? '—';
|
||||
const price = row.price == null ? '—' : eur(row.price);
|
||||
const name = row.name ?? '—';
|
||||
return `${row.sku ?? '—'} ${stock} ${price} ${name}`;
|
||||
}
|
||||
|
||||
const ResultList = ({ rows, onSelect, onBack }) => {
|
||||
const [selected, setSelected] = useState(0);
|
||||
const windowStart = Math.max(0, Math.min(selected - Math.floor(VISIBLE_ROWS / 2), rows.length - VISIBLE_ROWS));
|
||||
const visible = rows.slice(windowStart, windowStart + VISIBLE_ROWS);
|
||||
|
||||
useInput((_input, key) => {
|
||||
if (key.escape) {
|
||||
onBack();
|
||||
return;
|
||||
}
|
||||
if (key.upArrow) {
|
||||
setSelected((s) => Math.max(0, s - 1));
|
||||
}
|
||||
if (key.downArrow) {
|
||||
setSelected((s) => Math.min(rows.length - 1, s + 1));
|
||||
}
|
||||
if (key.return && rows[selected]) {
|
||||
onSelect(rows[selected]);
|
||||
}
|
||||
});
|
||||
|
||||
return e(Box, { flexDirection: 'column' },
|
||||
e(Text, null, chalk.cyan.bold(`${rows.length} result(s) — ↑↓ select, Enter open, Esc back`)),
|
||||
visible.map((row, index) => {
|
||||
const absoluteIndex = windowStart + index;
|
||||
const prefix = absoluteIndex === selected ? chalk.cyan('> ') : ' ';
|
||||
const label = absoluteIndex === selected
|
||||
? chalk.cyan.bold(formatListRow(row))
|
||||
: formatListRow(row);
|
||||
return e(Text, { key: row.id ?? absoluteIndex }, prefix, label);
|
||||
}),
|
||||
rows.length > VISIBLE_ROWS
|
||||
? e(Text, null, chalk.gray(`Showing ${windowStart + 1}-${windowStart + visible.length} of ${rows.length}`))
|
||||
: null
|
||||
);
|
||||
};
|
||||
|
||||
const App = ({ steuerklassen }) => {
|
||||
const { exit } = useApp();
|
||||
const [mode, setMode] = useState(MODES.SEARCH);
|
||||
const [target, setTarget] = useState('');
|
||||
const [rows, setRows] = useState([]);
|
||||
const [article, setArticle] = useState(null);
|
||||
const [error, setError] = useState('');
|
||||
const [loadingLabel, setLoadingLabel] = useState('Searching…');
|
||||
|
||||
const quit = async (code = 0) => {
|
||||
try {
|
||||
await closePool();
|
||||
} catch {
|
||||
// ignore cleanup errors
|
||||
}
|
||||
exit();
|
||||
process.exit(code);
|
||||
};
|
||||
|
||||
const loadArticle = async (cArtNr) => {
|
||||
setLoadingLabel(`Loading ${cArtNr}…`);
|
||||
setMode(MODES.LOADING);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const result = await getArticle(cArtNr);
|
||||
if (!result) {
|
||||
setError(`Article not found: ${cArtNr}`);
|
||||
setMode(MODES.ERROR);
|
||||
return;
|
||||
}
|
||||
setArticle(result);
|
||||
setMode(MODES.RESULT);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
setMode(MODES.ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = async () => {
|
||||
const trimmed = target.trim();
|
||||
if (!trimmed) {
|
||||
setError('Search terms are required');
|
||||
setMode(MODES.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingLabel('Searching…');
|
||||
setMode(MODES.LOADING);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const { recordset } = await searchArticles(trimmed);
|
||||
if (!recordset.length) {
|
||||
setError(`No articles found for: ${trimmed}`);
|
||||
setMode(MODES.ERROR);
|
||||
return;
|
||||
}
|
||||
if (recordset.length === 1) {
|
||||
await loadArticle(recordset[0].sku);
|
||||
return;
|
||||
}
|
||||
setRows(recordset);
|
||||
setMode(MODES.LIST);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
setMode(MODES.ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
const backToSearch = () => {
|
||||
setRows([]);
|
||||
setArticle(null);
|
||||
setError('');
|
||||
setMode(MODES.SEARCH);
|
||||
};
|
||||
|
||||
const resetSearch = () => {
|
||||
setTarget('');
|
||||
backToSearch();
|
||||
};
|
||||
|
||||
if (mode === MODES.LOADING) {
|
||||
return e(Text, null, chalk.yellow(loadingLabel));
|
||||
}
|
||||
|
||||
if (mode === MODES.ERROR) {
|
||||
return e(Box, { flexDirection: 'column' },
|
||||
e(Text, null, chalk.red(`Error: ${error}`)),
|
||||
e(Box, { marginTop: 1 }, e(Text, null, chalk.gray('Press any key to try again'))),
|
||||
e(ExitOnKey, { onExit: backToSearch })
|
||||
);
|
||||
}
|
||||
|
||||
if (mode === MODES.LIST) {
|
||||
return e(ResultList, {
|
||||
rows,
|
||||
onSelect: (row) => loadArticle(row.sku),
|
||||
onBack: backToSearch,
|
||||
});
|
||||
}
|
||||
|
||||
const backToList = () => {
|
||||
setArticle(null);
|
||||
setError('');
|
||||
setMode(rows.length ? MODES.LIST : MODES.SEARCH);
|
||||
};
|
||||
|
||||
if (mode === MODES.RESULT) {
|
||||
const footer = rows.length
|
||||
? 'Esc = back to results, Enter = new search, q = quit'
|
||||
: 'Enter = new search, q = quit';
|
||||
|
||||
return e(Box, { flexDirection: 'column' },
|
||||
e(ArticleDetail, { article, steuerklassen }),
|
||||
e(Box, { marginTop: 1 }, e(Text, null, chalk.gray(footer))),
|
||||
e(ResultInput, {
|
||||
onAgain: resetSearch,
|
||||
onBack: backToList,
|
||||
onQuit: () => quit(0),
|
||||
canGoBack: rows.length > 0,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return e(Box, { flexDirection: 'column' },
|
||||
e(Box, { marginBottom: 1 },
|
||||
e(Text, null, chalk.cyan.bold('=== Article Search ==='))
|
||||
),
|
||||
e(Text, null, chalk.gray('Partial match on name, number, EAN, barcode, supplier number')),
|
||||
e(Box, { marginTop: 1 },
|
||||
e(Text, null, 'Target: '),
|
||||
e(TextInput, { value: target, onChange: setTarget, onSubmit: handleSearch })
|
||||
),
|
||||
e(Box, { marginTop: 1 }, e(Text, null, chalk.gray('Enter to search, Ctrl+C to quit')))
|
||||
);
|
||||
};
|
||||
|
||||
const ResultInput = ({ onAgain, onBack, onQuit, canGoBack }) => {
|
||||
useInput((_input, key) => {
|
||||
if (key.escape && canGoBack) {
|
||||
onBack();
|
||||
return;
|
||||
}
|
||||
if (_input === 'q' || (key.ctrl && _input === 'c')) {
|
||||
onQuit();
|
||||
return;
|
||||
}
|
||||
if (key.return) {
|
||||
onAgain();
|
||||
}
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
const ExitOnKey = ({ onExit }) => {
|
||||
useInput(onExit);
|
||||
return null;
|
||||
};
|
||||
|
||||
const steuerklassen = await loadSteuerklassen();
|
||||
render(e(App, { steuerklassen }));
|
||||
Reference in New Issue
Block a user