Simplify cutting controls
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m45s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m45s
This commit is contained in:
@@ -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,144 +497,167 @@ 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="grid grid-cols-1 gap-3 md:grid-cols-4">
|
<div className="inline-flex rounded-xl border border-dark-border bg-dark-input p-1">
|
||||||
{CUT_FAMILY_RULES.map(rule => (
|
<button
|
||||||
<label key={rule.key} className="rounded-xl border border-dark-border bg-dark-input p-3">
|
type="button"
|
||||||
<span className="mb-2 flex items-center justify-between gap-2">
|
onClick={() => setSettingsSection('rules')}
|
||||||
<span className="text-xs font-bold text-dark-text">{rule.materialLabel}</span>
|
aria-pressed={settingsSection === 'rules'}
|
||||||
<span className="text-[11px] font-semibold text-dark-muted">{rule.label}</span>
|
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'}`}
|
||||||
</span>
|
>
|
||||||
<input
|
Rendimentos
|
||||||
type="number"
|
</button>
|
||||||
min="1"
|
<button
|
||||||
step="1"
|
type="button"
|
||||||
value={cuttingSettings.familyYields[rule.key] || ''}
|
onClick={() => setSettingsSection('corrections')}
|
||||||
onChange={(event) => updateFamilyYield(rule.key, event.target.value)}
|
aria-pressed={settingsSection === 'corrections'}
|
||||||
placeholder="un./rolo"
|
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'}`}
|
||||||
className="h-10 w-full rounded-lg border border-dark-border bg-dark-card px-3 text-sm font-bold text-dark-text outline-none transition-colors focus:border-brand-primary"
|
>
|
||||||
/>
|
Correções
|
||||||
</label>
|
</button>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-xl border border-dark-border">
|
{settingsSection === 'rules' && (
|
||||||
<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="grid grid-cols-1 gap-3 md:grid-cols-4">
|
||||||
<div>
|
{CUT_FAMILY_RULES.map(rule => (
|
||||||
<h3 className="text-xs font-bold uppercase tracking-widest text-dark-muted">Correções de produto</h3>
|
<label key={rule.key} className="rounded-xl border border-dark-border bg-dark-input p-3">
|
||||||
<p className="mt-1 text-xs font-semibold text-dark-muted">
|
<span className="mb-2 flex items-center justify-between gap-2">
|
||||||
Use quando o nome do SKU não deixa claro família, cor ou tamanho para montar o corte.
|
<span className="text-xs font-bold text-dark-text">{rule.materialLabel}</span>
|
||||||
</p>
|
<span className="text-[11px] font-semibold text-dark-muted">{rule.label}</span>
|
||||||
</div>
|
</span>
|
||||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
|
<input
|
||||||
<span className="text-xs font-bold text-dark-muted">
|
type="number"
|
||||||
{formatNumber(correctionRows.length)} produtos
|
min="1"
|
||||||
</span>
|
step="1"
|
||||||
<select
|
value={cuttingSettings.familyYields[rule.key] || ''}
|
||||||
value={correctionIssueFilter}
|
onChange={(event) => updateFamilyYield(rule.key, event.target.value)}
|
||||||
onChange={(event) => {
|
placeholder="un./rolo"
|
||||||
setCorrectionIssueFilter(event.target.value as CorrectionIssueFilter);
|
className="h-10 w-full rounded-lg border border-dark-border bg-dark-card px-3 text-sm font-bold text-dark-text outline-none transition-colors focus:border-brand-primary"
|
||||||
setCorrectionPage(1);
|
/>
|
||||||
}}
|
</label>
|
||||||
className="h-9 rounded-lg border border-dark-border bg-dark-input px-3 text-xs font-bold text-dark-text outline-none focus:border-brand-primary cursor-pointer"
|
))}
|
||||||
>
|
|
||||||
{correctionFilterOptions.map(option => (
|
|
||||||
<option key={option.value} value={option.value}>{option.label}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{correctionRows.length ? (
|
{settingsSection === 'corrections' && (
|
||||||
<>
|
<div className="rounded-xl border border-dark-border">
|
||||||
<div className="overflow-x-auto">
|
<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="min-w-[760px]">
|
<div>
|
||||||
<div className="grid grid-cols-[120px_1.4fr_150px_150px_120px_80px] gap-3 border-b border-dark-border px-4 py-3 text-[10px] font-bold uppercase tracking-wider text-dark-muted">
|
<h3 className="text-xs font-bold uppercase tracking-widest text-dark-muted">Correções de produto</h3>
|
||||||
<span>ID</span>
|
<p className="mt-1 text-xs font-semibold text-dark-muted">
|
||||||
<span>Produto</span>
|
Use quando o nome do SKU não deixa claro família, cor ou tamanho para montar o corte.
|
||||||
<span>Família</span>
|
</p>
|
||||||
<span>Cor</span>
|
</div>
|
||||||
<span>Tamanho</span>
|
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
|
||||||
<span className="text-right">Ações</span>
|
<span className="text-xs font-bold text-dark-muted">
|
||||||
</div>
|
{formatNumber(correctionRows.length)} produtos
|
||||||
<div className="divide-y divide-dark-border">
|
</span>
|
||||||
{paginatedCorrectionRows.map(row => {
|
<select
|
||||||
const override = cuttingSettings.productOverrides[row.id] || {};
|
value={correctionIssueFilter}
|
||||||
return (
|
onChange={(event) => {
|
||||||
<div key={row.id} className="grid grid-cols-[120px_1.4fr_150px_150px_120px_80px] items-center gap-3 px-4 py-3">
|
setCorrectionIssueFilter(event.target.value as CorrectionIssueFilter);
|
||||||
<span className="font-mono text-[11px] text-dark-muted">#{row.id}</span>
|
setCorrectionPage(1);
|
||||||
<div className="min-w-0">
|
}}
|
||||||
<div className="truncate text-xs font-bold text-dark-text" title={row.name}>{row.name}</div>
|
className="h-9 rounded-lg border border-dark-border bg-dark-input px-3 text-xs font-bold text-dark-text outline-none focus:border-brand-primary cursor-pointer"
|
||||||
<div className="mt-1 flex flex-wrap gap-1.5">
|
>
|
||||||
{row.issues.filter(issue => issue !== 'missing_yield_rule').map(issue => (
|
{correctionFilterOptions.map(option => (
|
||||||
<span key={issue} className="rounded-full border border-amber-400/25 bg-amber-400/10 px-2 py-0.5 text-[10px] font-bold text-amber-300">
|
<option key={option.value} value={option.value}>{option.label}</option>
|
||||||
{issueLabels[issue]}
|
))}
|
||||||
</span>
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{correctionRows.length ? (
|
||||||
|
<>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<div className="min-w-[760px]">
|
||||||
|
<div className="grid grid-cols-[120px_1.4fr_150px_150px_120px_80px] gap-3 border-b border-dark-border px-4 py-3 text-[10px] font-bold uppercase tracking-wider text-dark-muted">
|
||||||
|
<span>ID</span>
|
||||||
|
<span>Produto</span>
|
||||||
|
<span>Família</span>
|
||||||
|
<span>Cor</span>
|
||||||
|
<span>Tamanho</span>
|
||||||
|
<span className="text-right">Ações</span>
|
||||||
|
</div>
|
||||||
|
<div className="divide-y divide-dark-border">
|
||||||
|
{paginatedCorrectionRows.map(row => {
|
||||||
|
const override = cuttingSettings.productOverrides[row.id] || {};
|
||||||
|
return (
|
||||||
|
<div key={row.id} className="grid grid-cols-[120px_1.4fr_150px_150px_120px_80px] items-center gap-3 px-4 py-3">
|
||||||
|
<span className="font-mono text-[11px] text-dark-muted">#{row.id}</span>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="truncate text-xs font-bold text-dark-text" title={row.name}>{row.name}</div>
|
||||||
|
<div className="mt-1 flex flex-wrap gap-1.5">
|
||||||
|
{row.issues.filter(issue => issue !== 'missing_yield_rule').map(issue => (
|
||||||
|
<span key={issue} className="rounded-full border border-amber-400/25 bg-amber-400/10 px-2 py-0.5 text-[10px] font-bold text-amber-300">
|
||||||
|
{issueLabels[issue]}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
value={override.familyKey || ''}
|
||||||
|
onChange={(event) => updateProductOverride(row.id, { familyKey: event.target.value as CutFamilyKey | '' })}
|
||||||
|
className="h-9 rounded-lg border border-dark-border bg-dark-input px-2 text-xs font-bold text-dark-text outline-none focus:border-brand-primary cursor-pointer"
|
||||||
|
>
|
||||||
|
<option value="">Auto</option>
|
||||||
|
{familyOptions.filter(option => option.value !== 'all').map(option => (
|
||||||
|
<option key={option.value} value={option.value}>{option.label}</option>
|
||||||
))}
|
))}
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={override.color || ''}
|
||||||
|
onChange={(event) => updateProductOverride(row.id, { color: event.target.value })}
|
||||||
|
placeholder={row.color || 'Cor'}
|
||||||
|
className="h-9 rounded-lg border border-dark-border bg-dark-input px-2 text-xs font-bold text-dark-text outline-none focus:border-brand-primary"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={override.size || ''}
|
||||||
|
onChange={(event) => updateProductOverride(row.id, { size: event.target.value })}
|
||||||
|
placeholder={row.size || 'Tam.'}
|
||||||
|
className="h-9 rounded-lg border border-dark-border bg-dark-input px-2 text-xs font-bold text-dark-text outline-none focus:border-brand-primary"
|
||||||
|
/>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => clearProductOverride(row.id)}
|
||||||
|
className="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-dark-border bg-dark-input text-dark-muted transition-colors hover:border-brand-primary hover:text-brand-primary cursor-pointer"
|
||||||
|
title="Limpar correção"
|
||||||
|
>
|
||||||
|
<RotateCcw className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<select
|
);
|
||||||
value={override.familyKey || ''}
|
})}
|
||||||
onChange={(event) => updateProductOverride(row.id, { familyKey: event.target.value as CutFamilyKey | '' })}
|
</div>
|
||||||
className="h-9 rounded-lg border border-dark-border bg-dark-input px-2 text-xs font-bold text-dark-text outline-none focus:border-brand-primary cursor-pointer"
|
|
||||||
>
|
|
||||||
<option value="">Auto</option>
|
|
||||||
{familyOptions.filter(option => option.value !== 'all').map(option => (
|
|
||||||
<option key={option.value} value={option.value}>{option.label}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={override.color || ''}
|
|
||||||
onChange={(event) => updateProductOverride(row.id, { color: event.target.value })}
|
|
||||||
placeholder={row.color || 'Cor'}
|
|
||||||
className="h-9 rounded-lg border border-dark-border bg-dark-input px-2 text-xs font-bold text-dark-text outline-none focus:border-brand-primary"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={override.size || ''}
|
|
||||||
onChange={(event) => updateProductOverride(row.id, { size: event.target.value })}
|
|
||||||
placeholder={row.size || 'Tam.'}
|
|
||||||
className="h-9 rounded-lg border border-dark-border bg-dark-input px-2 text-xs font-bold text-dark-text outline-none focus:border-brand-primary"
|
|
||||||
/>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => clearProductOverride(row.id)}
|
|
||||||
className="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-dark-border bg-dark-input text-dark-muted transition-colors hover:border-brand-primary hover:text-brand-primary cursor-pointer"
|
|
||||||
title="Limpar correção"
|
|
||||||
>
|
|
||||||
<RotateCcw className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<PaginationControls
|
<PaginationControls
|
||||||
totalItems={correctionRows.length}
|
totalItems={correctionRows.length}
|
||||||
currentPage={safeCorrectionPage}
|
currentPage={safeCorrectionPage}
|
||||||
totalPages={correctionTotalPages}
|
totalPages={correctionTotalPages}
|
||||||
pageSize={correctionItemsPerPage}
|
pageSize={correctionItemsPerPage}
|
||||||
pageSizeOptions={[12]}
|
pageSizeOptions={[12]}
|
||||||
itemLabel="produtos"
|
itemLabel="produtos"
|
||||||
pageSizeLabel="por página"
|
pageSizeLabel="por página"
|
||||||
startIndex={correctionStartIndex}
|
startIndex={correctionStartIndex}
|
||||||
endIndex={Math.min(correctionStartIndex + correctionItemsPerPage, correctionRows.length)}
|
endIndex={Math.min(correctionStartIndex + correctionItemsPerPage, correctionRows.length)}
|
||||||
onPageChange={setCorrectionPage}
|
onPageChange={setCorrectionPage}
|
||||||
onPageSizeChange={() => setCorrectionPage(1)}
|
onPageSizeChange={() => setCorrectionPage(1)}
|
||||||
className="border-t border-dark-border px-4 py-3"
|
className="border-t border-dark-border px-4 py-3"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="px-4 py-8 text-center">
|
<div className="px-4 py-8 text-center">
|
||||||
<p className="text-sm font-bold text-dark-text">Nenhuma correção de produto pendente.</p>
|
<p className="text-sm font-bold text-dark-text">Nenhuma correção de produto pendente.</p>
|
||||||
<p className="mt-1 text-xs font-semibold text-dark-muted">Os dados restantes, se existirem, são de rendimento por família.</p>
|
<p className="mt-1 text-xs font-semibold text-dark-muted">Os dados restantes, se existirem, são de rendimento por família.</p>
|
||||||
</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"
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user