Simplify cutting controls
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m45s

This commit is contained in:
Cauê Faleiros
2026-07-09 13:23:25 -03:00
parent 423442578f
commit 634cf4db48
2 changed files with 178 additions and 162 deletions

View File

@@ -14,6 +14,7 @@ type CutSort = 'need_desc' | 'need_asc' | 'demand_desc' | 'stock_asc' | 'sold_de
type SaveStatus = 'idle' | 'saving' | 'saved' | 'error'; type SaveStatus = 'idle' | 'saving' | 'saved' | 'error';
type CutProductIssue = Exclude<CutIssue, 'missing_yield_rule'>; type CutProductIssue = Exclude<CutIssue, 'missing_yield_rule'>;
type CorrectionIssueFilter = CutProductIssue | 'all'; type CorrectionIssueFilter = CutProductIssue | 'all';
type SettingsSection = 'rules' | 'corrections';
const SETTINGS_STORAGE_KEY = 'nexstar_cutting_settings'; const SETTINGS_STORAGE_KEY = 'nexstar_cutting_settings';
const coverageTargetOptions = [7, 15, 30, 60]; const coverageTargetOptions = [7, 15, 30, 60];
@@ -124,6 +125,7 @@ const Cutting = () => {
const [cutFilter, setCutFilter] = useState<CutFilter>('need'); const [cutFilter, setCutFilter] = useState<CutFilter>('need');
const [sortBy, setSortBy] = useState<CutSort>('need_desc'); const [sortBy, setSortBy] = useState<CutSort>('need_desc');
const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [settingsSection, setSettingsSection] = useState<SettingsSection>('rules');
const [cuttingSettings, setCuttingSettings] = useState<CuttingSettings>(loadCuttingSettings); const [cuttingSettings, setCuttingSettings] = useState<CuttingSettings>(loadCuttingSettings);
const [saveStatus, setSaveStatus] = useState<SaveStatus>('idle'); const [saveStatus, setSaveStatus] = useState<SaveStatus>('idle');
const [hasUnsavedSettings, setHasUnsavedSettings] = useState(false); const [hasUnsavedSettings, setHasUnsavedSettings] = useState(false);
@@ -261,10 +263,6 @@ const Cutting = () => {
const configuredYieldCount = CUT_FAMILY_RULES.filter(rule => cuttingSettings.familyYields[rule.key]).length; const configuredYieldCount = CUT_FAMILY_RULES.filter(rule => cuttingSettings.familyYields[rule.key]).length;
const productOverrideCount = Object.keys(cuttingSettings.productOverrides).length; const productOverrideCount = Object.keys(cuttingSettings.productOverrides).length;
useEffect(() => {
setCorrectionPage(1);
}, [correctionIssueFilter, dateRange, targetCoverageDays]);
useEffect(() => { useEffect(() => {
if (!isSettingsOpen) return undefined; if (!isSettingsOpen) return undefined;
@@ -350,6 +348,11 @@ const Cutting = () => {
setSaveStatus('idle'); setSaveStatus('idle');
}; };
const openSettingsSection = (section: SettingsSection) => {
setSettingsSection(section);
setIsSettingsOpen(true);
};
const exportRows = () => { const exportRows = () => {
exportToCSV(filteredRows.map(row => ({ exportToCSV(filteredRows.map(row => ({
'ID Produto': row.id, 'ID Produto': row.id,
@@ -408,6 +411,7 @@ const Cutting = () => {
onChange={(range) => { onChange={(range) => {
setDateRange(range); setDateRange(range);
setCurrentPage(1); setCurrentPage(1);
setCorrectionPage(1);
}} }}
/> />
@@ -423,9 +427,9 @@ const Cutting = () => {
<button <button
type="button" type="button"
onClick={() => setIsSettingsOpen(current => !current)} onClick={() => openSettingsSection('rules')}
className="flex items-center justify-center gap-2 rounded-xl border border-dark-border bg-dark-card px-4 py-2.5 text-sm font-medium text-dark-text shadow-sm transition-colors hover:border-brand-primary cursor-pointer" className="flex items-center justify-center gap-2 rounded-xl border border-dark-border bg-dark-card px-4 py-2.5 text-sm font-medium text-dark-text shadow-sm transition-colors hover:border-brand-primary cursor-pointer"
title="Configurar regras de corte" title="Configurar rendimentos por família"
> >
<Settings2 size={16} className="text-brand-primary" /> <Settings2 size={16} className="text-brand-primary" />
<span className="hidden sm:inline">Regras</span> <span className="hidden sm:inline">Regras</span>
@@ -449,7 +453,7 @@ const Cutting = () => {
> >
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between"> <div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div> <div>
<h2 className="text-sm font-bold text-dark-text">Regras de Corte</h2> <h2 className="text-sm font-bold text-dark-text">Configuração de Corte</h2>
<p className="mt-1 text-xs font-semibold text-dark-muted"> <p className="mt-1 text-xs font-semibold text-dark-muted">
{configuredYieldCount} rendimentos configurados · {productOverrideCount} correções de produto {configuredYieldCount} rendimentos configurados · {productOverrideCount} correções de produto
</p> </p>
@@ -461,7 +465,7 @@ const Cutting = () => {
className="inline-flex items-center justify-center gap-2 rounded-xl border border-dark-border bg-dark-input px-3 py-2 text-xs font-bold text-dark-muted transition-colors hover:border-red-400/40 hover:text-red-300 cursor-pointer" className="inline-flex items-center justify-center gap-2 rounded-xl border border-dark-border bg-dark-input px-3 py-2 text-xs font-bold text-dark-muted transition-colors hover:border-red-400/40 hover:text-red-300 cursor-pointer"
> >
<RotateCcw className="h-4 w-4" /> <RotateCcw className="h-4 w-4" />
Limpar regras Limpar tudo
</button> </button>
<button <button
type="button" type="button"
@@ -470,7 +474,7 @@ const Cutting = () => {
className="inline-flex items-center justify-center gap-2 rounded-xl border border-brand-primary/30 bg-brand-primary/15 px-3 py-2 text-xs font-bold text-brand-primary transition-colors hover:border-brand-primary disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer" className="inline-flex items-center justify-center gap-2 rounded-xl border border-brand-primary/30 bg-brand-primary/15 px-3 py-2 text-xs font-bold text-brand-primary transition-colors hover:border-brand-primary disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"
> >
<SaveIcon className="h-4 w-4" /> <SaveIcon className="h-4 w-4" />
{saveStatus === 'saving' ? 'Salvando' : 'Salvar regras'} {saveStatus === 'saving' ? 'Salvando' : 'Salvar'}
</button> </button>
<button <button
type="button" type="button"
@@ -493,6 +497,26 @@ const Cutting = () => {
<p className="text-xs font-semibold text-amber-300">Existem alterações ainda não salvas.</p> <p className="text-xs font-semibold text-amber-300">Existem alterações ainda não salvas.</p>
)} )}
<div className="inline-flex rounded-xl border border-dark-border bg-dark-input p-1">
<button
type="button"
onClick={() => setSettingsSection('rules')}
aria-pressed={settingsSection === 'rules'}
className={`rounded-lg px-3 py-2 text-xs font-bold transition-colors cursor-pointer ${settingsSection === 'rules' ? 'bg-brand-primary/15 text-brand-primary' : 'text-dark-muted hover:text-dark-text'}`}
>
Rendimentos
</button>
<button
type="button"
onClick={() => setSettingsSection('corrections')}
aria-pressed={settingsSection === 'corrections'}
className={`rounded-lg px-3 py-2 text-xs font-bold transition-colors cursor-pointer ${settingsSection === 'corrections' ? 'bg-brand-primary/15 text-brand-primary' : 'text-dark-muted hover:text-dark-text'}`}
>
Correções
</button>
</div>
{settingsSection === 'rules' && (
<div className="grid grid-cols-1 gap-3 md:grid-cols-4"> <div className="grid grid-cols-1 gap-3 md:grid-cols-4">
{CUT_FAMILY_RULES.map(rule => ( {CUT_FAMILY_RULES.map(rule => (
<label key={rule.key} className="rounded-xl border border-dark-border bg-dark-input p-3"> <label key={rule.key} className="rounded-xl border border-dark-border bg-dark-input p-3">
@@ -512,7 +536,9 @@ const Cutting = () => {
</label> </label>
))} ))}
</div> </div>
)}
{settingsSection === 'corrections' && (
<div className="rounded-xl border border-dark-border"> <div className="rounded-xl border border-dark-border">
<div className="flex flex-col gap-3 border-b border-dark-border bg-dark-header p-4 lg:flex-row lg:items-center lg:justify-between"> <div className="flex flex-col gap-3 border-b border-dark-border bg-dark-header p-4 lg:flex-row lg:items-center lg:justify-between">
<div> <div>
@@ -631,6 +657,7 @@ const Cutting = () => {
</div> </div>
)} )}
</div> </div>
)}
</div> </div>
</div> </div>
)} )}
@@ -706,7 +733,11 @@ const Cutting = () => {
</span> </span>
<button <button
type="button" type="button"
onClick={() => setIsSettingsOpen(true)} onClick={() => {
setCorrectionIssueFilter('all');
setCorrectionPage(1);
openSettingsSection('corrections');
}}
className="inline-flex items-center justify-center gap-2 rounded-xl border border-dark-border bg-dark-input px-3 py-2 text-xs font-bold text-dark-text transition-colors hover:border-brand-primary cursor-pointer" className="inline-flex items-center justify-center gap-2 rounded-xl border border-dark-border bg-dark-input px-3 py-2 text-xs font-bold text-dark-text transition-colors hover:border-brand-primary cursor-pointer"
> >
<Settings2 className="h-4 w-4 text-brand-primary" /> <Settings2 className="h-4 w-4 text-brand-primary" />
@@ -716,26 +747,16 @@ const Cutting = () => {
</div> </div>
<div className="grid grid-cols-1 gap-3 md:grid-cols-4"> <div className="grid grid-cols-1 gap-3 md:grid-cols-4">
{issueSummaries.map(summary => ( {issueSummaries.map(summary => (
<button <div
key={summary.issue} key={summary.issue}
type="button" className="rounded-xl border border-dark-border bg-dark-input/60 p-3"
onClick={() => {
setCutFilter('issues');
setIsSettingsOpen(true);
if (summary.issue !== 'missing_yield_rule') {
setCorrectionIssueFilter(summary.issue);
}
setCurrentPage(1);
setCorrectionPage(1);
}}
className="rounded-xl border border-dark-border bg-dark-input/60 p-3 text-left transition-colors hover:border-amber-400/50 cursor-pointer"
> >
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<span className="text-xs font-bold text-dark-text">{issueLabels[summary.issue]}</span> <span className="text-xs font-bold text-dark-text">{issueLabels[summary.issue]}</span>
<span className="text-sm font-bold text-amber-300">{formatNumber(summary.count)}</span> <span className="text-sm font-bold text-amber-300">{formatNumber(summary.count)}</span>
</div> </div>
<p className="mt-1 line-clamp-2 text-[11px] font-medium leading-relaxed text-dark-muted">{issueHelp[summary.issue]}</p> <p className="mt-1 line-clamp-2 text-[11px] font-medium leading-relaxed text-dark-muted">{issueHelp[summary.issue]}</p>
</button> </div>
))} ))}
</div> </div>
</div> </div>
@@ -752,15 +773,9 @@ const Cutting = () => {
</div> </div>
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 2xl:grid-cols-5"> <div className="grid grid-cols-1 gap-3 md:grid-cols-2 2xl:grid-cols-5">
{cutPlan.familySummaries.map(summary => ( {cutPlan.familySummaries.map(summary => (
<button <div
key={summary.family.key} key={summary.family.key}
type="button" className="rounded-xl border border-dark-border bg-dark-input/50 p-3"
onClick={() => {
setFamilyFilter(summary.family.key);
setCutFilter('need');
setCurrentPage(1);
}}
className="rounded-xl border border-dark-border bg-dark-input/50 p-3 text-left transition-colors hover:border-brand-primary cursor-pointer"
> >
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
<div className="min-w-0"> <div className="min-w-0">
@@ -775,7 +790,7 @@ const Cutting = () => {
<p className="mt-1 text-xs font-semibold text-dark-muted"> <p className="mt-1 text-xs font-semibold text-dark-muted">
{formatNumber(summary.colorCount)} cores · {summary.estimatedRolls === null ? 'sem rendimento' : `${formatNumber(summary.estimatedRolls)} rolos`} {formatNumber(summary.colorCount)} cores · {summary.estimatedRolls === null ? 'sem rendimento' : `${formatNumber(summary.estimatedRolls)} rolos`}
</p> </p>
</button> </div>
))} ))}
</div> </div>
</div> </div>
@@ -801,6 +816,7 @@ const Cutting = () => {
onChange={(event) => { onChange={(event) => {
setTargetCoverageDays(Number(event.target.value)); setTargetCoverageDays(Number(event.target.value));
setCurrentPage(1); setCurrentPage(1);
setCorrectionPage(1);
}} }}
className="h-11 rounded-xl border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text focus:border-brand-primary focus:outline-none cursor-pointer" className="h-11 rounded-xl border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text focus:border-brand-primary focus:outline-none cursor-pointer"
aria-label="Dias de cobertura alvo" aria-label="Dias de cobertura alvo"

View File

@@ -25,7 +25,7 @@ export default defineConfig(({ command }) => ({
tailwindcss() tailwindcss()
], ],
server: { server: {
port: 3001, port: 3002,
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:3004', target: 'http://localhost:3004',
@@ -34,7 +34,7 @@ export default defineConfig(({ command }) => ({
} }
}, },
preview: { preview: {
port: 3000, port: 3002,
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:3004', target: 'http://localhost:3004',