Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | 1x 25x 25x 25x 25x 25x 24x 10x 25x 10x 10x 10x 10x 10x 10x 10x 25x 25x 25x 6x 24x | import React, { useState, useEffect } from "react";
import {
Container,
Typography,
Grid,
Box,
TextField,
InputAdornment,
Chip,
Stack,
} from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";
import Product from "./Product";
import { useLanguage } from "../contexts/LanguageContext";
import { getProducts } from "../db/api";
const Catalog = ({ onAddToCart = () => {}, search = "", setSearch = () => {} }) => {
const { t } = useLanguage();
const [products, setProducts] = useState([]);
const [selectedCategory, setSelectedCategory] = useState("");
const [isLoading, setIsLoading] = useState(true);
// Set initial category when translation is ready
useEffect(() => {
if (!selectedCategory && t("catalog.all_categories")) {
setSelectedCategory(t("catalog.all_categories"));
}
}, [t, selectedCategory]);
useEffect(() => {
const fetchProducts = async () => {
setIsLoading(true);
try {
const data = await getProducts();
setProducts(Array.isArray(data) ? data : []);
} catch (error) {
console.error("Failed to load products from SQLite", error);
} finally {
setIsLoading(false);
}
};
fetchProducts();
}, []);
const categories = [t("catalog.all_categories"), ...new Set(products.map((p) => p.category))];
const filtered = products.filter((p) => {
const matchSearch = p.name.toLowerCase().includes(search.toLowerCase());
const matchCategory =
selectedCategory === t("catalog.all_categories") || p.category === selectedCategory;
return matchSearch && matchCategory;
});
return (
<Container maxWidth="lg">
<Box id="catalog-header-wrapper" sx={{ mb: 4 }}>
<Typography variant="h4" component="h1" gutterBottom>
{t("catalog.title")}
</Typography>
<Typography variant="body1" sx={{ color: "#fff", mb: 3 }}>
{t("catalog.products_found", { count: filtered.length, plural: filtered.length !== 1 ? "s" : "" })}
</Typography>
<Box id="catalog-search-filters-wrapper" sx={{ display: "flex", gap: 2, flexWrap: "wrap", alignItems: "center", mb: 2 }}>
<TextField
size="small"
placeholder={t("catalog.search_placeholder")}
value={search}
onChange={(e) => setSearch(e.target.value)}
sx={{ minWidth: 240 }}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon fontSize="small" />
</InputAdornment>
),
}}
/>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
{categories.map((cat) => (
<Chip
key={cat}
label={cat}
clickable
color={selectedCategory === cat ? "primary" : "default"}
onClick={() => setSelectedCategory(cat)}
variant={selectedCategory === cat ? "filled" : "outlined"}
/>
))}
</Stack>
</Box>
</Box>
<Grid container spacing={3}>
{isLoading ? (
<Box id="catalog-loading-wrapper" sx={{ width: "100%", textAlign: "center", py: 8 }}>
<Typography variant="h6" color="text.secondary">
Carregando produtos...
</Typography>
</Box>
) : (
filtered.map((product) => (
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }} key={product.id}>
<Product product={product} onAddToCart={onAddToCart} />
</Grid>
))
)}
</Grid>
{!isLoading && filtered.length === 0 && (
<Box id="catalog-empty-wrapper" sx={{ textAlign: "center", py: 8 }}>
<Typography variant="h6" color="text.secondary">
{t("catalog.no_products")}
</Typography>
</Box>
)}
</Container>
);
};
export default Catalog;
|