Add editable registrations
This commit is contained in:
@@ -11,7 +11,9 @@ const {
|
|||||||
listCategories,
|
listCategories,
|
||||||
listConsumptionReferences,
|
listConsumptionReferences,
|
||||||
listProducts,
|
listProducts,
|
||||||
updateCategory
|
updateCategory,
|
||||||
|
updateConsumptionReference,
|
||||||
|
updateProduct
|
||||||
} = require('../services/catalogService');
|
} = require('../services/catalogService');
|
||||||
const { importTinyProductCompositionExport } = require('../services/productionOrderService');
|
const { importTinyProductCompositionExport } = require('../services/productionOrderService');
|
||||||
|
|
||||||
@@ -74,6 +76,14 @@ router.post('/catalog/products', verifyToken, async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.put('/catalog/products/:id', verifyToken, async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
res.json(await updateProduct(req.params.id, req.body || {}));
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
router.delete('/catalog/products/:id', verifyToken, async (req, res, next) => {
|
router.delete('/catalog/products/:id', verifyToken, async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
await deleteProduct(req.params.id);
|
await deleteProduct(req.params.id);
|
||||||
@@ -99,6 +109,14 @@ router.post('/catalog/consumption-references', verifyToken, async (req, res, nex
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.put('/catalog/consumption-references/:id', verifyToken, async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
res.json(await updateConsumptionReference(req.params.id, req.body || {}));
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
router.post('/catalog/product-compositions/import', verifyToken, async (req, res, next) => {
|
router.post('/catalog/product-compositions/import', verifyToken, async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
res.status(201).json(await importTinyProductCompositionExport(req.body || {}));
|
res.status(201).json(await importTinyProductCompositionExport(req.body || {}));
|
||||||
|
|||||||
@@ -205,6 +205,56 @@ const createProduct = async (payload) => {
|
|||||||
return products.find(product => product.id === result.rows[0].id);
|
return products.find(product => product.id === result.rows[0].id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateProduct = async (id, payload) => {
|
||||||
|
const type = normalizeProductType(payload.type);
|
||||||
|
const sku = normalizeText(payload.sku).toUpperCase();
|
||||||
|
const name = normalizeText(payload.name);
|
||||||
|
if (!sku || !name) {
|
||||||
|
const error = new Error('SKU e nome do produto são obrigatórios.');
|
||||||
|
error.statusCode = 400;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
const result = await pool.query(`
|
||||||
|
UPDATE catalog_products
|
||||||
|
SET type = $2,
|
||||||
|
sku = $3,
|
||||||
|
name = $4,
|
||||||
|
category_id = $5,
|
||||||
|
composition = $6,
|
||||||
|
notes = $7,
|
||||||
|
gramature = $8,
|
||||||
|
material_yield = $9,
|
||||||
|
width_cm = $10,
|
||||||
|
color = $11,
|
||||||
|
subcategory = $12,
|
||||||
|
sizes = $13,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING id;
|
||||||
|
`, [
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
sku,
|
||||||
|
name,
|
||||||
|
Number(payload.categoryId) || null,
|
||||||
|
normalizeText(payload.composition) || null,
|
||||||
|
normalizeText(payload.notes) || null,
|
||||||
|
normalizeNumber(payload.gramature),
|
||||||
|
normalizeNumber(payload.materialYield),
|
||||||
|
normalizeNumber(payload.widthCm),
|
||||||
|
normalizeText(payload.color) || null,
|
||||||
|
normalizeText(payload.subcategory) || null,
|
||||||
|
normalizeStringArray(payload.sizes)
|
||||||
|
]);
|
||||||
|
if (!result.rows.length) {
|
||||||
|
const error = new Error('Produto não encontrado.');
|
||||||
|
error.statusCode = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
const products = await listProducts();
|
||||||
|
return products.find(product => product.id === result.rows[0].id);
|
||||||
|
};
|
||||||
|
|
||||||
const deleteProduct = async (id) => {
|
const deleteProduct = async (id) => {
|
||||||
await pool.query('DELETE FROM catalog_products WHERE id = $1', [id]);
|
await pool.query('DELETE FROM catalog_products WHERE id = $1', [id]);
|
||||||
};
|
};
|
||||||
@@ -275,6 +325,66 @@ const createConsumptionReference = async (payload) => {
|
|||||||
return references.find(reference => reference.id === result.rows[0].id);
|
return references.find(reference => reference.id === result.rows[0].id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateConsumptionReference = async (id, payload) => {
|
||||||
|
const productId = Number(payload.productId);
|
||||||
|
if (!productId) {
|
||||||
|
const error = new Error('Produto é obrigatório.');
|
||||||
|
error.statusCode = 400;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
const sizeYields = normalizeNumericMap(payload.sizeYields);
|
||||||
|
const sizeAreas = normalizeNumericMap(payload.sizeAreas);
|
||||||
|
const generalYield = normalizeNumber(payload.generalYield);
|
||||||
|
const calculatedYield = Object.values(sizeYields).length
|
||||||
|
? Object.values(sizeYields).reduce((total, value) => total + value, 0) / Object.values(sizeYields).length
|
||||||
|
: null;
|
||||||
|
const consumptionQuantity = normalizeNumber(payload.consumptionQuantity);
|
||||||
|
if (!generalYield && !calculatedYield && !consumptionQuantity) {
|
||||||
|
const error = new Error('Informe o rendimento ou consumo por peça.');
|
||||||
|
error.statusCode = 400;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
const result = await pool.query(`
|
||||||
|
UPDATE consumption_references
|
||||||
|
SET product_id = $2,
|
||||||
|
material_product_id = $3,
|
||||||
|
color = $4,
|
||||||
|
general_yield = $5,
|
||||||
|
size_yields = $6::jsonb,
|
||||||
|
size_areas = $7::jsonb,
|
||||||
|
gramature = $8,
|
||||||
|
efficiency_percent = $9,
|
||||||
|
rib_g_per_piece = $10,
|
||||||
|
material_cost_per_kg = $11,
|
||||||
|
consumption_quantity = $12,
|
||||||
|
consumption_unit = $13,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING id;
|
||||||
|
`, [
|
||||||
|
id,
|
||||||
|
productId,
|
||||||
|
Number(payload.materialProductId) || null,
|
||||||
|
normalizeText(payload.color) || null,
|
||||||
|
generalYield || calculatedYield,
|
||||||
|
JSON.stringify(sizeYields),
|
||||||
|
JSON.stringify(sizeAreas),
|
||||||
|
normalizeNumber(payload.gramature),
|
||||||
|
normalizeNumber(payload.efficiencyPercent),
|
||||||
|
normalizeNumber(payload.ribGPerPiece),
|
||||||
|
normalizeNumber(payload.materialCostPerKg),
|
||||||
|
consumptionQuantity,
|
||||||
|
normalizeText(payload.consumptionUnit) || null
|
||||||
|
]);
|
||||||
|
if (!result.rows.length) {
|
||||||
|
const error = new Error('Referência de consumo não encontrada.');
|
||||||
|
error.statusCode = 404;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
const references = await listConsumptionReferences();
|
||||||
|
return references.find(reference => reference.id === result.rows[0].id);
|
||||||
|
};
|
||||||
|
|
||||||
const deleteConsumptionReference = async (id) => {
|
const deleteConsumptionReference = async (id) => {
|
||||||
await pool.query('DELETE FROM consumption_references WHERE id = $1', [id]);
|
await pool.query('DELETE FROM consumption_references WHERE id = $1', [id]);
|
||||||
};
|
};
|
||||||
@@ -300,5 +410,7 @@ module.exports = {
|
|||||||
listCategories,
|
listCategories,
|
||||||
listConsumptionReferences,
|
listConsumptionReferences,
|
||||||
listProducts,
|
listProducts,
|
||||||
updateCategory
|
updateCategory,
|
||||||
|
updateConsumptionReference,
|
||||||
|
updateProduct
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -367,6 +367,17 @@ export const saveCatalogProduct = async (payload: CatalogProductPayload): Promis
|
|||||||
return data as CatalogProduct;
|
return data as CatalogProduct;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateCatalogProduct = async (id: number, payload: CatalogProductPayload): Promise<CatalogProduct> => {
|
||||||
|
const response = await authFetch(`/catalog/products/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
const data = await response.json().catch(() => null);
|
||||||
|
if (!response.ok) throw new Error(data?.error || 'Não foi possível atualizar o produto.');
|
||||||
|
return data as CatalogProduct;
|
||||||
|
};
|
||||||
|
|
||||||
export const deleteCatalogProduct = async (id: number): Promise<void> => {
|
export const deleteCatalogProduct = async (id: number): Promise<void> => {
|
||||||
const response = await authFetch(`/catalog/products/${id}`, { method: 'DELETE' });
|
const response = await authFetch(`/catalog/products/${id}`, { method: 'DELETE' });
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -390,6 +401,17 @@ export const saveConsumptionReference = async (payload: ConsumptionReferencePayl
|
|||||||
return data as ConsumptionReference;
|
return data as ConsumptionReference;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateConsumptionReference = async (id: number, payload: ConsumptionReferencePayload): Promise<ConsumptionReference> => {
|
||||||
|
const response = await authFetch(`/catalog/consumption-references/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
const data = await response.json().catch(() => null);
|
||||||
|
if (!response.ok) throw new Error(data?.error || 'Não foi possível atualizar a referência de consumo.');
|
||||||
|
return data as ConsumptionReference;
|
||||||
|
};
|
||||||
|
|
||||||
export const deleteConsumptionReference = async (id: number): Promise<void> => {
|
export const deleteConsumptionReference = async (id: number): Promise<void> => {
|
||||||
const response = await authFetch(`/catalog/consumption-references/${id}`, { method: 'DELETE' });
|
const response = await authFetch(`/catalog/consumption-references/${id}`, { method: 'DELETE' });
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import {
|
|||||||
saveCatalogProduct,
|
saveCatalogProduct,
|
||||||
saveConsumptionReference,
|
saveConsumptionReference,
|
||||||
startOlistCompositionSync,
|
startOlistCompositionSync,
|
||||||
updateCatalogCategory
|
updateCatalogCategory,
|
||||||
|
updateCatalogProduct,
|
||||||
|
updateConsumptionReference
|
||||||
} from '../dataService';
|
} from '../dataService';
|
||||||
import PaginationControls from '../components/PaginationControls';
|
import PaginationControls from '../components/PaginationControls';
|
||||||
import type { CatalogCategory, CatalogProduct, CatalogProductType, CatalogSummary, ConsumptionReference, OlistStatus } from '../types';
|
import type { CatalogCategory, CatalogProduct, CatalogProductType, CatalogSummary, ConsumptionReference, OlistStatus } from '../types';
|
||||||
@@ -131,7 +133,9 @@ const Registrations = () => {
|
|||||||
|
|
||||||
const [categoryForm, setCategoryForm] = useState({ name: '', description: '' });
|
const [categoryForm, setCategoryForm] = useState({ name: '', description: '' });
|
||||||
const [editingCategory, setEditingCategory] = useState<CatalogCategory | null>(null);
|
const [editingCategory, setEditingCategory] = useState<CatalogCategory | null>(null);
|
||||||
|
const [editingProduct, setEditingProduct] = useState<CatalogProduct | null>(null);
|
||||||
const [productForm, setProductForm] = useState(defaultProductForm);
|
const [productForm, setProductForm] = useState(defaultProductForm);
|
||||||
|
const [editingReference, setEditingReference] = useState<ConsumptionReference | null>(null);
|
||||||
const [referenceForm, setReferenceForm] = useState({
|
const [referenceForm, setReferenceForm] = useState({
|
||||||
productId: '',
|
productId: '',
|
||||||
materialProductId: '',
|
materialProductId: '',
|
||||||
@@ -329,23 +333,16 @@ const Registrations = () => {
|
|||||||
}, editingCategory ? 'Categoria atualizada.' : 'Categoria salva.');
|
}, editingCategory ? 'Categoria atualizada.' : 'Categoria salva.');
|
||||||
|
|
||||||
const saveProduct = () => runAction(async () => {
|
const saveProduct = () => runAction(async () => {
|
||||||
await saveCatalogProduct({
|
const payload = {
|
||||||
...productForm,
|
...productForm,
|
||||||
categoryId: productForm.categoryId ? Number(productForm.categoryId) : null,
|
categoryId: productForm.categoryId ? Number(productForm.categoryId) : null,
|
||||||
sizes: productForm.type === 'finished_product' ? productForm.sizes : []
|
sizes: productForm.type === 'finished_product' ? productForm.sizes : []
|
||||||
});
|
};
|
||||||
setProductForm(current => ({
|
if (editingProduct) await updateCatalogProduct(editingProduct.id, payload);
|
||||||
...current,
|
else await saveCatalogProduct(payload);
|
||||||
sku: '',
|
setProductForm(defaultProductForm);
|
||||||
name: '',
|
setEditingProduct(null);
|
||||||
composition: '',
|
}, editingProduct ? 'Produto atualizado.' : 'Produto salvo.');
|
||||||
notes: '',
|
|
||||||
gramature: '',
|
|
||||||
materialYield: '',
|
|
||||||
widthCm: '',
|
|
||||||
color: ''
|
|
||||||
}));
|
|
||||||
}, 'Produto salvo.');
|
|
||||||
|
|
||||||
const calculateSizeYields = () => {
|
const calculateSizeYields = () => {
|
||||||
const gramature = Number(referenceForm.gramature.replace(',', '.'));
|
const gramature = Number(referenceForm.gramature.replace(',', '.'));
|
||||||
@@ -374,22 +371,20 @@ const Registrations = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const saveReference = () => runAction(async () => {
|
const saveReference = () => runAction(async () => {
|
||||||
await saveConsumptionReference({
|
const payload = {
|
||||||
...referenceForm,
|
...referenceForm,
|
||||||
productId: Number(referenceForm.productId),
|
productId: Number(referenceForm.productId),
|
||||||
materialProductId: referenceForm.materialProductId ? Number(referenceForm.materialProductId) : null,
|
materialProductId: referenceForm.materialProductId ? Number(referenceForm.materialProductId) : null,
|
||||||
sizeAreas: numericMapFromStrings(sizeAreas),
|
sizeAreas: numericMapFromStrings(sizeAreas),
|
||||||
sizeYields: numericMapFromStrings(sizeYields)
|
sizeYields: numericMapFromStrings(sizeYields)
|
||||||
});
|
};
|
||||||
setReferenceForm(current => ({
|
if (editingReference) await updateConsumptionReference(editingReference.id, payload);
|
||||||
...current,
|
else await saveConsumptionReference(payload);
|
||||||
productId: '',
|
setReferenceForm({ productId: '', materialProductId: '', color: '', generalYield: '', gramature: '', efficiencyPercent: '85', ribGPerPiece: '', materialCostPerKg: '' });
|
||||||
materialProductId: '',
|
setSizeAreas(defaultSizeAreas);
|
||||||
color: '',
|
|
||||||
generalYield: ''
|
|
||||||
}));
|
|
||||||
setSizeYields({});
|
setSizeYields({});
|
||||||
}, 'Referencia salva.');
|
setEditingReference(null);
|
||||||
|
}, editingReference ? 'Referencia atualizada.' : 'Referencia salva.');
|
||||||
|
|
||||||
const importCompositionFile = async (file: File | null) => {
|
const importCompositionFile = async (file: File | null) => {
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
@@ -451,11 +446,61 @@ const Registrations = () => {
|
|||||||
`Produto ${product.sku} excluido.`
|
`Produto ${product.sku} excluido.`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const editProduct = (product: CatalogProduct) => {
|
||||||
|
setEditingProduct(product);
|
||||||
|
setProductForm({
|
||||||
|
type: product.type,
|
||||||
|
sku: product.sku,
|
||||||
|
name: product.name,
|
||||||
|
categoryId: product.categoryId ? String(product.categoryId) : '',
|
||||||
|
composition: product.composition || '',
|
||||||
|
notes: product.notes || '',
|
||||||
|
gramature: product.gramature?.toString() || '',
|
||||||
|
materialYield: product.materialYield?.toString() || '',
|
||||||
|
widthCm: product.widthCm?.toString() || '',
|
||||||
|
color: product.color || '',
|
||||||
|
subcategory: product.subcategory || 'malha',
|
||||||
|
sizes: product.sizes || []
|
||||||
|
});
|
||||||
|
setStatus('idle');
|
||||||
|
setFeedback('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelProductEdit = () => {
|
||||||
|
setEditingProduct(null);
|
||||||
|
setProductForm(defaultProductForm);
|
||||||
|
};
|
||||||
|
|
||||||
const removeReference = (reference: ConsumptionReference) => runAction(
|
const removeReference = (reference: ConsumptionReference) => runAction(
|
||||||
() => deleteConsumptionReference(reference.id),
|
() => deleteConsumptionReference(reference.id),
|
||||||
`Referencia ${reference.productSku} excluida.`
|
`Referencia ${reference.productSku} excluida.`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const editReference = (reference: ConsumptionReference) => {
|
||||||
|
setEditingReference(reference);
|
||||||
|
setReferenceForm({
|
||||||
|
productId: String(reference.productId),
|
||||||
|
materialProductId: reference.materialProductId ? String(reference.materialProductId) : '',
|
||||||
|
color: reference.color || '',
|
||||||
|
generalYield: reference.generalYield?.toString() || '',
|
||||||
|
gramature: reference.gramature?.toString() || '',
|
||||||
|
efficiencyPercent: reference.efficiencyPercent?.toString() || '85',
|
||||||
|
ribGPerPiece: reference.ribGPerPiece?.toString() || '',
|
||||||
|
materialCostPerKg: reference.materialCostPerKg?.toString() || ''
|
||||||
|
});
|
||||||
|
setSizeAreas(Object.fromEntries(Object.entries(reference.sizeAreas || {}).map(([size, value]) => [size, String(value)])));
|
||||||
|
setSizeYields(Object.fromEntries(Object.entries(reference.sizeYields || {}).map(([size, value]) => [size, String(value)])));
|
||||||
|
setStatus('idle');
|
||||||
|
setFeedback('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelReferenceEdit = () => {
|
||||||
|
setEditingReference(null);
|
||||||
|
setReferenceForm({ productId: '', materialProductId: '', color: '', generalYield: '', gramature: '', efficiencyPercent: '85', ribGPerPiece: '', materialCostPerKg: '' });
|
||||||
|
setSizeAreas(defaultSizeAreas);
|
||||||
|
setSizeYields({});
|
||||||
|
};
|
||||||
|
|
||||||
const toggleProductSize = (size: string) => {
|
const toggleProductSize = (size: string) => {
|
||||||
setProductForm(current => ({
|
setProductForm(current => ({
|
||||||
...current,
|
...current,
|
||||||
@@ -621,15 +666,15 @@ const Registrations = () => {
|
|||||||
.join(' · ') || 'Sem detalhes'}
|
.join(' · ') || 'Sem detalhes'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<div className="flex shrink-0 items-center gap-2">
|
||||||
type="button"
|
<button type="button" onClick={() => editProduct(product)} aria-label={`Editar produto ${product.sku}`} title="Editar produto" 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/50 hover:text-brand-primary cursor-pointer">
|
||||||
onClick={() => void removeProduct(product)}
|
<Pencil className="h-4 w-4" />
|
||||||
className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-dark-border bg-dark-input text-dark-muted transition-colors hover:border-red-400/40 hover:text-red-300 cursor-pointer"
|
</button>
|
||||||
title="Excluir produto"
|
<button type="button" onClick={() => void removeProduct(product)} 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-red-400/40 hover:text-red-300 cursor-pointer" title="Excluir produto">
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
{!filteredProducts.length && (
|
{!filteredProducts.length && (
|
||||||
<div className={emptyStateClassName}>Nenhum produto cadastrado.</div>
|
<div className={emptyStateClassName}>Nenhum produto cadastrado.</div>
|
||||||
@@ -651,7 +696,10 @@ const Registrations = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={formPanelClassName}>
|
<div className={formPanelClassName}>
|
||||||
<h2 className="text-sm font-bold text-dark-text">Cadastrar produto / materia-prima</h2>
|
<div className="flex items-center justify-between gap-3">
|
||||||
|
<h2 className="text-sm font-bold text-dark-text">{editingProduct ? 'Editar produto / materia-prima' : 'Cadastrar produto / materia-prima'}</h2>
|
||||||
|
{editingProduct && <button type="button" onClick={cancelProductEdit} className="text-xs font-bold text-dark-muted transition-colors hover:text-dark-text cursor-pointer">Cancelar</button>}
|
||||||
|
</div>
|
||||||
<div className="mt-4 space-y-3">
|
<div className="mt-4 space-y-3">
|
||||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||||
<label className={labelClassName}>
|
<label className={labelClassName}>
|
||||||
@@ -781,7 +829,7 @@ const Registrations = () => {
|
|||||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-xl bg-brand-primary px-4 text-sm font-bold text-brand-contrast transition-colors hover:bg-brand-primary/90 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"
|
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-xl bg-brand-primary px-4 text-sm font-bold text-brand-contrast transition-colors hover:bg-brand-primary/90 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"
|
||||||
>
|
>
|
||||||
<Save className="h-4 w-4" />
|
<Save className="h-4 w-4" />
|
||||||
Salvar produto
|
{editingProduct ? 'Salvar alterações' : 'Salvar produto'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -917,11 +965,16 @@ const Registrations = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" onClick={() => void removeReference(reference)} 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-red-400/40 hover:text-red-300 cursor-pointer">
|
<div className="flex items-center gap-2">
|
||||||
|
<button type="button" onClick={() => editReference(reference)} aria-label={`Editar referência ${reference.productSku}`} title="Editar referência" 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/50 hover:text-brand-primary cursor-pointer">
|
||||||
|
<Pencil className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => void removeReference(reference)} title="Excluir referência" 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-red-400/40 hover:text-red-300 cursor-pointer">
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
{!catalog.consumptionReferences.length && (
|
{!catalog.consumptionReferences.length && (
|
||||||
<div className={emptyStateClassName}>
|
<div className={emptyStateClassName}>
|
||||||
@@ -945,12 +998,15 @@ const Registrations = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={formPanelClassName}>
|
<div className={formPanelClassName}>
|
||||||
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-sm font-bold text-dark-text">Nova referencia de consumo</h2>
|
<h2 className="text-sm font-bold text-dark-text">{editingReference ? 'Editar referencia de consumo' : 'Nova referencia de consumo'}</h2>
|
||||||
<p className="mt-1 text-xs font-semibold text-dark-muted">
|
<p className="mt-1 text-xs font-semibold text-dark-muted">
|
||||||
Defina pç/kg por produto, malha e cor para o calculo do corte.
|
Defina pç/kg por produto, malha e cor para o calculo do corte.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
{editingReference && <button type="button" onClick={cancelReferenceEdit} className="text-xs font-bold text-dark-muted transition-colors hover:text-dark-text cursor-pointer">Cancelar</button>}
|
||||||
|
</div>
|
||||||
<div className="mt-4 space-y-4">
|
<div className="mt-4 space-y-4">
|
||||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||||
<label className={labelClassName}>
|
<label className={labelClassName}>
|
||||||
@@ -1021,7 +1077,7 @@ const Registrations = () => {
|
|||||||
|
|
||||||
<button type="button" onClick={() => void saveReference()} disabled={status === 'saving'} className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-xl bg-brand-primary px-4 text-sm font-bold text-brand-contrast transition-colors hover:bg-brand-primary/90 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer">
|
<button type="button" onClick={() => void saveReference()} disabled={status === 'saving'} className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-xl bg-brand-primary px-4 text-sm font-bold text-brand-contrast transition-colors hover:bg-brand-primary/90 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer">
|
||||||
<Save className="h-4 w-4" />
|
<Save className="h-4 w-4" />
|
||||||
Salvar referencia
|
{editingReference ? 'Salvar alterações' : 'Salvar referencia'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user