fix(products): ordenar produtos do maior para o menor preco, permitir limpar input de preco/stock sem ficar negativo, e impedir adicionar mais quantidade ao carrinho do que o stock disponivel

This commit is contained in:
2026-06-22 17:09:17 +01:00
parent 6c93af609f
commit 7b8e8d6150
18 changed files with 1216 additions and 10 deletions

View File

@@ -0,0 +1,26 @@
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://jqklhhpyykzrktikjnmb.supabase.co'
const supabaseAnonKey =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Impxa2xoaHB5eWt6cmt0aWtqbm1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjgzODQ0MDgsImV4cCI6MjA4Mzk2MDQwOH0.QsPuBnyUtRPSavlqKj3IGR9c8juT02LY_hSi-j3c6M0'
const supabase = createClient(supabaseUrl, supabaseAnonKey)
async function test() {
console.log('Testing notifications table insert...')
const { data, error } = await supabase.from('notifications').insert([
{
user_id: '00000000-0000-0000-0000-000000000000',
message: 'test message'
}
]).select()
if (error) {
console.error('Insert error:', error)
} else {
console.log('Insert success:', data)
}
}
test()

19
web/check_profiles.js Normal file
View File

@@ -0,0 +1,19 @@
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = 'https://jqklhhpyykzrktikjnmb.supabase.co';
const supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Impxa2xoaHB5eWt6cmt0aWtqbm1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjgzODQ0MDgsImV4cCI6MjA4Mzk2MDQwOH0.QsPuBnyUtRPSavlqKj3IGR9c8juT02LY_hSi-j3c6M0';
const supabase = createClient(supabaseUrl, supabaseAnonKey);
async function check() {
const { data: testData, error: testError } = await supabase.from('profiles').select('*').limit(1);
if (testError) {
console.log("SELECT ERROR:", testError.message);
} else {
console.log("COLUMNS EXIST IN PROFILES:", testData && testData.length > 0 ? Object.keys(testData[0]) : "No rows");
console.log("SAMPLE ROW:", testData);
}
process.exit(0);
}
check();

View File

@@ -13,7 +13,7 @@ export const ProductList = ({
onAdd?: (id: string) => void;
}) => (
<div className="grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-6">
{products.map((p) => {
{[...products].sort((a, b) => b.price - a.price).map((p) => {
const lowStock = p.stock <= 3;
return (
<Card key={p.id} className="relative overflow-hidden border-none glass-card rounded-2xl sm:rounded-[2rem] group hover:shadow-2xl transition-all duration-300 flex flex-col">

View File

@@ -500,6 +500,34 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
};
const addToCart: AppContextValue['addToCart'] = (item) => {
if (item.type === 'product') {
const shop = state.shops.find((shp) => shp.id === item.shopId);
const prod = shop?.products.find((p) => p.id === item.refId);
if (prod) {
const maxStock = prod.stock;
const existingItem = state.cart.find((c) => c.refId === item.refId && c.type === 'product' && c.shopId === item.shopId);
const currentQty = existingItem ? existingItem.qty : 0;
if (currentQty >= maxStock) {
addToast(`Não é possível adicionar mais unidades. Apenas ${maxStock} unidades em stock.`, 'info');
return;
}
if (currentQty + item.qty > maxStock) {
const allowedQty = maxStock - currentQty;
setState((s) => {
const cart = [...s.cart];
const idx = cart.findIndex((c) => c.refId === item.refId && c.type === item.type && c.shopId === item.shopId);
if (idx >= 0) cart[idx].qty = maxStock;
else cart.push({ ...item, qty: allowedQty });
return { ...s, cart };
});
addToast(`Adicionado apenas ${allowedQty} unidades. Limite de stock atingido.`, 'info');
return;
}
}
}
setState((s) => {
const cart = [...s.cart];
const idx = cart.findIndex((c) => c.refId === item.refId && c.type === item.type && c.shopId === item.shopId);

View File

@@ -141,8 +141,8 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
const [svcDuration, setSvcDuration] = useState<number>(30);
const [prodName, setProdName] = useState('');
const [prodPrice, setProdPrice] = useState<number>(30);
const [prodStock, setProdStock] = useState<number>(10);
const [prodPrice, setProdPrice] = useState<string>('30');
const [prodStock, setProdStock] = useState<string>('10');
const [barberName, setBarberName] = useState('');
const [barberSpecs, setBarberSpecs] = useState('');
@@ -296,10 +296,20 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
const addNewProduct = () => {
if (!prodName.trim()) return;
addProduct(shop.id, { name: prodName, price: Number(prodPrice) || 0, stock: Number(prodStock) || 0 });
const priceNum = parseFloat(prodPrice);
const stockNum = parseInt(prodStock, 10);
if (isNaN(priceNum) || priceNum < 0) {
alert('O preço deve ser um número positivo.');
return;
}
if (isNaN(stockNum) || stockNum < 0) {
alert('O stock deve ser um número positivo.');
return;
}
addProduct(shop.id, { name: prodName, price: priceNum, stock: stockNum });
setProdName('');
setProdPrice(30);
setProdStock(10);
setProdPrice('30');
setProdStock('10');
};
const addNewBarber = () => {
@@ -1116,7 +1126,7 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
</div>
)}
<div className="space-y-3 mb-6">
{shop.products.map((p) => (
{[...shop.products].sort((a, b) => b.price - a.price).map((p) => (
<div
key={p.id}
className={`flex items-center justify-between p-4 border rounded-lg ${p.stock <= 3 ? 'border-indigo-300 bg-indigo-50' : 'border-slate-200'
@@ -1150,8 +1160,33 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
<h3 className="text-base font-semibold text-slate-900 mb-3">Adicionar novo produto</h3>
<div className="grid md:grid-cols-4 gap-3">
<Input label="Nome" placeholder="Ex: Pomada" value={prodName} onChange={(e) => setProdName(e.target.value)} />
<Input label="Preço" type="number" placeholder="30" value={prodPrice} onChange={(e) => setProdPrice(Number(e.target.value))} />
<Input label="Stock inicial" type="number" placeholder="10" value={prodStock} onChange={(e) => setProdStock(Number(e.target.value))} />
<Input
label="Preço"
type="number"
placeholder="30"
min="0"
step="0.01"
value={prodPrice}
onChange={(e) => {
const val = e.target.value;
if (val === '' || parseFloat(val) >= 0) {
setProdPrice(val);
}
}}
/>
<Input
label="Stock inicial"
type="number"
placeholder="10"
min="0"
value={prodStock}
onChange={(e) => {
const val = e.target.value;
if (val === '' || parseInt(val, 10) >= 0) {
setProdStock(val);
}
}}
/>
<div className="flex items-end">
<Button onClick={addNewProduct} className="w-full">
<PlusIcon size={16} className="mr-2" />