Desktop: Prices sheet panel is one row (Export primary, Compare a sheet, rest in ...)

This commit is contained in:
CRTkafa 2026-10-23 09:45:19 +08:00
parent 5b6d5cdac2
commit ef6651c8d2

View file

@ -1,7 +1,7 @@
// Prices: what every product costs in every territory on both stores (`prices export --json`, read-only), the price
// sheet as a CSV (save it anywhere, or write store/prices.csv for the terminal), and a price sheet compared with the
// stores (`prices import --dry --json`) and applied as an owner action: dry run, native dialog, then the real run.
import { useContextMenu } from '../../ui/ContextMenu';
import { useContextMenu, useMenuButton } from '../../ui/ContextMenu';
import { copyToast } from '../../ui/app-menu';
import '../../styles/prices.css';
import { useEffect, useMemo, useState } from 'react';
@ -92,6 +92,7 @@ export function PricesTab({ project }: { project: Project }) {
const e = d.statusOf(project);
const s = e?.summary ?? null;
const w = useWrite(project.id);
const more = useMenuButton();
const runExport = useRunExport();
const cm = useContextMenu();
const [withAuto, setWithAuto] = useState(true);
@ -129,20 +130,15 @@ export function PricesTab({ project }: { project: Project }) {
return (
<div className="scr-prices">
<Section title={t('prices.sheetTitle')} icon="coins" sub={t('prices.sheetSub')}
actions={<button className="btn btn-sm btn-primary" onClick={() => runExport(exportItem)} disabled={!p}><Icon name="download" />{t('prices.exportCsv')}</button>}>
<div className="act-grid">
<div className="act-col">
<div className="act-head"><Icon name="download" />{t('prices.getSheet')}</div>
<button className="btn btn-sm" onClick={() => (p ? runExport(exportItem) : cur.refresh())} disabled={cur.loading}><Icon name={p ? 'download' : 'refresh'} className={cur.loading ? 'spin' : undefined} />{p ? t('prices.exportCsv') : t('prices.readFirst')}</button>
<WriteButton run={w} icon="text" w={{ method: 'prices.export', args: {}, title: t('act.pricesExport'), done: t('act.pricesExported') }} hint={t('prices.writeHint')}>{t('prices.writeProject')}</WriteButton>
</div>
<div className="act-col">
<div className="act-head"><Icon name="layers" />{t('prices.compareHead')}</div>
<button className="btn btn-sm" onClick={pick}><Icon name="folder" />{t('prices.pickSheet')}</button>
<button className="btn btn-sm" onClick={() => setSheet({})}><Icon name="text" />{t('prices.compareProject')}</button>
{p && rel(p.csv) && <button className="btn btn-sm btn-ghost" onClick={() => api.reveal(project.id, rel(p.csv)).catch(x => d.toast({ kind: 'info', title: t('prices.noSheetYet'), detail: errorText(x) }))}><Icon name="eye" />{t('prices.showSheet')}</button>}
</div>
</div>
actions={<>
<button className="btn btn-sm" onClick={pick}><Icon name="layers" />{t('prices.pickSheet')}</button>
<button className="btn btn-sm btn-primary" onClick={() => (p ? runExport(exportItem) : cur.refresh())} disabled={cur.loading}><Icon name={p ? 'download' : 'refresh'} className={cur.loading ? 'spin' : undefined} />{p ? t('prices.exportCsv') : t('prices.readFirst')}</button>
<button className="btn btn-sm btn-ghost btn-icon" aria-label={t('cm.more')} title={t('cm.more')} {...more(() => [
{ label: t('prices.writeProject'), icon: 'text', disabled: w.busy ? t('cm.busy') : false, onSelect: () => { w.run({ method: 'prices.export', args: {}, title: t('act.pricesExport'), done: t('act.pricesExported') }); } },
{ label: t('prices.compareProject'), icon: 'layers', onSelect: () => setSheet({}) },
p && rel(p.csv) && { label: t('prices.showSheet'), icon: 'eye', onSelect: () => { api.reveal(project.id, rel(p.csv)).catch(x => d.toast({ kind: 'info', title: t('prices.noSheetYet'), detail: errorText(x) })); } },
])}><Icon name="more" /></button>
</>}>
<p className="small muted act-note"><Icon name="shield" />{t('prices.sheetNote')}</p>
</Section>