Overview
DADDO’s sales tracking system allows you to record transactions, automatically update inventory, track sales by product and variant, and view comprehensive sales history with confirmation workflows.
Creating Sales
Create Sale Action
The createSell action (src/Redux/actions/Sells/createSell.js) handles sale creation:
export const createSell = ( products ) => async ( dispatch ) => {
dispatch ({ type: CREATE_SELL_REQUEST });
try {
const response = await api . post ( "/sells" , { products });
dispatch ({ type: CREATE_SELL_SUCCESS , payload: response . data });
return response . data ;
} catch ( error ) {
dispatch ({
type: CREATE_SELL_FAILURE ,
payload: error . response ?. data ?. error || "Error al crear venta" ,
});
throw error ;
}
};
Sales are created with an array of products, each containing product ID, variant ID (if applicable), quantity, and final price.
Create Sale Component
The Create_Sell component (src/Views/createSell.jsx) provides a comprehensive interface for recording sales:
Component State
const Create_Sell = () => {
const dispatch = useDispatch ();
const navigate = useNavigate ();
const token = localStorage . getItem ( "token" );
const { categories } = useSelector (( state ) => state . categories );
const { products } = useSelector (( state ) => state . filteredProducts );
const [ selectedCategory , setSelectedCategory ] = useState ( "" );
const [ selectedProduct , setSelectedProduct ] = useState ( "" );
const [ selectedVariant , setSelectedVariant ] = useState ( "" );
const [ quantity , setQuantity ] = useState ( 1 );
const [ sellPrice , setSellPrice ] = useState ( "" );
const [ sellProducts , setSellProducts ] = useState ([]);
const [ search , setSearch ] = useState ( "" );
// ...
};
Product Selection Workflow
Step 1: Category Filter
Users start by selecting a category to narrow down product choices:
useEffect (() => {
dispatch ( getCategories ( token ));
}, [ dispatch , token ]);
useEffect (() => {
if ( selectedCategory ) {
dispatch ( getFilteredProducts ({ category: selectedCategory }));
}
}, [ dispatch , selectedCategory ]);
< select
value = { selectedCategory }
onChange = { ( e ) => setSelectedCategory ( e . target . value ) }
className = "w-full sm:w-auto bg-gray-900 border text-white border-purple-500 rounded-md px-4 py-2"
>
< option value = "" > - Selecciona una categoría - </ option >
{ categories . map (( cat ) => (
< option key = { cat . id } value = { cat . id } >
{ cat . name }
</ option >
)) }
</ select >
Step 2: Product Selection
Once a category is selected, available products are displayed:
< select
value = { selectedProduct }
onChange = { ( e ) => {
setSelectedProduct ( e . target . value );
setSelectedVariant ( "" );
} }
className = "w-full bg-gray-900 border border-purple-500 rounded-md px-4 py-2 text-gray-200"
>
< option value = "" > Selecciona un producto </ option >
{ Array . isArray ( products ) &&
products . map (( prod ) => (
< option key = { prod . id } value = { prod . id } >
{ prod . name }
</ option >
)) }
</ select >
Product Preview:
{ selectedProduct && (
< div className = "mt-3 flex items-center space-x-3" >
< div className = "flex flex-col" >
< span className = "text-gray-200 font-medium" >
{ products . find (( p ) => p . id === selectedProduct )?. name }
</ span >
< span className = "text-gray-400 text-sm" >
Stock disponible: { " " }
{ (() => {
const prod = products . find (( p ) => p . id === selectedProduct );
return prod ?. variants ?. length > 0
? "Depende de la variante"
: prod ?. stock ?? 0 ;
})() }
</ span >
</ div >
< img
src = {
products . find (( p ) => p . id === selectedProduct )?. images ?.[ 0 ]?. url ||
"https://via.placeholder.com/60x60?text=Sin+imagen"
}
className = "w-16 h-16 object-cover rounded-md border border-gray-700"
/>
</ div >
)}
Step 3: Variant Selection (if applicable)
If the product has variants, users must select one:
{ selectedProduct &&
products . find (( p ) => p . id === selectedProduct )?. variants ?. length > 0 && (
< select
value = { selectedVariant }
onChange = { ( e ) => setSelectedVariant ( e . target . value ) }
className = "w-full bg-gray-900 border border-purple-500 rounded-md px-4 py-2 text-gray-200"
>
< option value = "" > Selecciona una variante </ option >
{ products
. find (( p ) => p . id === selectedProduct )
?. variants . map (( v ) => (
< option key = { v . id } value = { v . id } >
{ v . color || v . size
? ` ${ v . color || "" } ${ v . size || "" } ` . trim ()
: `Variante sin nombre` }{ " " }
(Stock: { v . stock } )
</ option >
)) }
</ select >
)
}
Step 4: Quantity and Price
Users specify quantity and can override the default price:
useEffect (() => {
if ( selectedProduct ) {
const prod = products . find (( p ) => p . id === selectedProduct );
if ( prod ) setSellPrice ( prod . price );
} else {
setSellPrice ( "" );
}
}, [ selectedProduct , products ]);
{ selectedProduct && (
< div className = "flex items-center space-x-4 mt-4" >
< input
type = "number"
min = "1"
value = { quantity }
onChange = { ( e ) => setQuantity ( Number ( e . target . value )) }
className = "w-20 bg-gray-900 rounded-lg px-2 py-1 text-gray-200"
/>
< div className = "flex items-center space-x-2" >
< span className = "text-gray-300 font-medium" > Precio: </ span >
< input
type = "number"
min = "0"
value = { sellPrice }
onChange = { ( e ) => setSellPrice ( e . target . value ) }
className = "w-24 bg-gray-900 rounded-lg px-2 py-1 text-gray-200"
/>
</ div >
< button
type = "button"
onClick = { handleAddProduct }
className = "bg-purple-800 text-white px-2 py-1 rounded-lg hover:bg-purple-600"
>
Agregar
</ button >
</ div >
)}
Adding Products to Sale
Stock Validation
The system validates stock availability before adding products:
const handleAddProduct = () => {
if ( ! selectedProduct ) {
toast . error ( "Selecciona un producto válido" );
return ;
}
if ( quantity <= 0 ) {
toast . error ( "Cantidad debe ser mayor a 0" );
return ;
}
if ( ! sellPrice || sellPrice <= 0 ) {
toast . error ( "Ingresa un precio válido" );
return ;
}
const product = products . find (( p ) => p . id === selectedProduct );
const variant = product . variants ?. find (( v ) => v . id === selectedVariant ) || null ;
const availableStock = variant ? variant . stock : product . stock ;
const key = variant ? ` ${ selectedProduct } - ${ variant . id } ` : selectedProduct ;
const finalUnitPrice = Number ( sellPrice );
const existing = sellProducts . find (( p ) => p . key === key );
const totalQuantity = existing ? existing . quantity + quantity : quantity ;
if ( totalQuantity > availableStock ) {
toast . error ( `Stock insuficiente. Disponible: ${ availableStock } ` );
return ;
}
// Add product...
};
Building Sale Item
Each product added to the sale includes complete information:
const newItem = {
key ,
ProductId: selectedProduct ,
variantId: variant ? variant . id : null ,
quantity ,
finalPrice: finalUnitPrice ,
name: product . name ,
variantLabel: variant
? ` ${ variant . color || "" } ${ variant . size || "" } ` . trim ()
: null ,
};
if ( existing ) {
setSellProducts (
sellProducts . map (( p ) =>
p . key === key
? {
... p ,
quantity: totalQuantity ,
finalPrice: finalUnitPrice ,
}
: p
)
);
} else {
setSellProducts ([ ... sellProducts , newItem ]);
}
Unique Key Combines product ID and variant ID to track individual items
Quantity Update Automatically adds quantities if same product is added multiple times
Price Override Allows custom pricing per sale transaction
Variant Tracking Distinguishes between different variants of the same product
Sales Cart Display
Product List
Added products are displayed in a cart-style list:
{ sellProducts . length > 0 && (
< div className = "bg-gray-900 p-4 rounded-lg shadow-sm overflow-x-auto" >
< h3 className = "text-lg font-semibold text-gray-200 mb-2" >
Productos seleccionados
</ h3 >
< ul className = "space-y-2" >
{ sellProducts . map (( p ) => (
< li
key = { p . key }
className = "flex justify-between text-purple-500 items-center bg-gray-900 border border-gray-700 p-2 rounded shadow"
>
< span >
{ p . name }{ " " }
{ p . variantLabel && (
< span className = "text-gray-400 text-sm" >
( { p . variantLabel } )
</ span >
) }{ " " }
- { p . quantity } pz - $ { p . finalPrice }
</ span >
< button
type = "button"
onClick = { () => handleRemoveProduct ( p . key ) }
className = "text-red-500 font-bold hover:text-red-700"
>
✕
</ button >
</ li >
)) }
</ ul >
</ div >
)}
Remove Products
const handleRemoveProduct = ( key ) => {
setSellProducts ( sellProducts . filter (( p ) => p . key !== key ));
};
Submitting the Sale
Sale Creation
Once all products are added, the sale can be submitted:
const handleSubmit = async ( e ) => {
e . preventDefault ();
if ( sellProducts . length === 0 ) {
setMessage ( "Debes agregar al menos un producto a la venta" );
setMessageType ( "error" );
return ;
}
try {
await dispatch ( createSell ( sellProducts , token ));
setMessage ( "Venta registrada con éxito" );
setMessageType ( "success" );
setTimeout (() => navigate ( "/sells" ), 1500 );
} catch ( error ) {
setMessage ( error ?. response ?. data ?. error || "Error al registrar venta" );
setMessageType ( "error" );
}
};
Sales permanently reduce inventory stock. Ensure all quantities and products are correct before submitting.
Viewing Sales History
Get User Sales
The getUserSells action retrieves all sales for the current user:
import { getUserSells } from "../Redux/actions/Sells/getUserSells" ;
useEffect (() => {
dispatch ( getUserSells ( token ));
}, [ dispatch , token ]);
Sales Table Component
Sales are displayed in a comprehensive table (src/components/sales/SalesTable.jsx):
Sale ID and date
Total amount
Number of products
Confirmation status
Action buttons (view details, confirm, delete)
Sale Confirmation
Confirm Sale Action
Sales can be confirmed to finalize the transaction:
import { confirmSell } from "../Redux/actions/Sells/confirmSell" ;
const handleConfirm = ( sellId ) => {
dispatch ( confirmSell ( sellId , token ));
toast . success ( "Venta confirmada" );
};
Confirmed sales are typically used to mark when payment has been received or the order is complete.
Sale Details
Get Sale by ID
Detailed information about a specific sale:
import { getSellById } from "../Redux/actions/Sells/getSellById" ;
const viewSaleDetails = ( sellId ) => {
dispatch ( getSellById ( sellId , token ));
};
Sale details include:
Complete product list with quantities
Variant information
Individual product prices
Total sale amount
Creation timestamp
Confirmation status
Deleting Sales
Delete Sale Action
Unconfirmed sales can be deleted:
import { deleteSell } from "../Redux/actions/Sells/deleteSell" ;
const handleDelete = async ( sellId ) => {
if ( window . confirm ( "¿Eliminar esta venta?" )) {
await dispatch ( deleteSell ( sellId , token ));
dispatch ( getUserSells ( token )); // Refresh list
toast . success ( "Venta eliminada" );
}
};
Deleting a sale does NOT restore inventory. Stock adjustments must be made manually if needed.
Search Functionality
The sales form includes product search:
const [ search , setSearch ] = useState ( "" );
useEffect (() => {
if ( search . length > 2 ) {
dispatch ( getFilteredProducts ({ search }));
}
}, [ dispatch , search ]);
Search activates after entering at least 3 characters to avoid excessive API calls.
Sale Data Structure
Each sale record contains:
{
id : "sale_id" ,
userId : "user_id" ,
products : [
{
ProductId: "product_id" ,
variantId: "variant_id" || null ,
quantity: 5 ,
finalPrice: 29.99 ,
name: "Product Name" ,
variantLabel: "Red / Large"
}
],
totalAmount : 149.95 ,
confirmed : false ,
createdAt : "2024-01-15T10:30:00Z"
}
Best Practices
Double-check quantities before submission
Verify variant selection for products with options
Use custom pricing for discounts or special offers
Check stock availability before processing
Confirm sales after payment is received
Monitor stock levels regularly
Update product stock when receiving inventory
Be cautious when deleting sales (stock not restored)
Review sales history to identify popular products
Use confirmation workflow for payment tracking
Review sales history regularly
Track confirmed vs. pending sales
Monitor sales by product and category
Use sales data for inventory planning
Identify best-selling products and variants