Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 43 additions & 24 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,45 +276,64 @@ function App() {
}

return (
<div className="min-h-screen bg-gray-100 font-sans pb-24">
<header className="bg-white sticky top-0 z-30 shadow-sm">
<div className="bg-red-900 text-white p-1 text-center text-[10px] md:text-xs font-medium uppercase tracking-wider">
O melhor churrasco da região • Peça agora
</div>
<div className="p-4 flex justify-between items-center max-w-lg mx-auto">
<div className="min-h-screen bg-[#f5f6fa] font-sans pb-28">
<header className="bg-gradient-to-b from-red-700 via-red-600 to-red-500 text-white pb-10 shadow-md">
<div className="max-w-4xl mx-auto px-4 pt-4 flex justify-between items-center">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-yellow-400 rounded-lg flex items-center justify-center text-red-900 font-black text-xl shadow-sm">
<div className="w-12 h-12 rounded-full bg-white/20 flex items-center justify-center text-xl font-black shadow-lg">
ED
</div>
<div>
<h1 className="font-bold text-gray-800 leading-none">Datony</h1>
<span className="text-xs text-gray-500">Espetinhos Premium</span>
<p className="text-xs uppercase tracking-[0.2em] text-red-100 font-semibold">Espetinho Datony</p>
<h1 className="text-2xl font-black leading-tight">O melhor da região</h1>
</div>
</div>
<div className="flex items-center gap-3 text-gray-400">
<div className="flex items-center gap-3 text-white/80">
<div className="hidden sm:flex items-center gap-2 bg-white/10 px-3 py-1 rounded-full text-xs font-semibold">
<span className="w-2 h-2 rounded-full bg-emerald-300 animate-pulse" /> Aberto agora
</div>
<button
onClick={() => setView('grill')}
className="w-10 h-10 rounded-full bg-white/10 backdrop-blur flex items-center justify-center hover:bg-white/20 transition"
title="Fila do churrasqueiro"
>
<ChefHat size={20} />
</button>
<button
onClick={() => {
setView('login');
setLoginError('');
}}
className="hover:text-red-600 transition-colors"
className="w-10 h-10 rounded-full bg-white/10 backdrop-blur flex items-center justify-center hover:bg-white/20 transition"
title="Área administrativa"
>
<User size={20} />
</button>
<button onClick={() => setView('grill')} className="hover:text-red-600 transition-colors">
<ChefHat size={20} />
</button>
</div>
</div>

<div className="max-w-4xl mx-auto px-4 mt-4">
<div className="bg-white/10 backdrop-blur-xl rounded-2xl p-4 flex items-center justify-between border border-white/20">
<div>
<p className="text-sm text-red-100">Faça seu pedido e retire rápido</p>
<p className="text-lg font-bold">Espetinhos artesanais e bebidas geladas</p>
</div>
<div className="hidden md:flex gap-2">
<div className="px-3 py-2 rounded-xl bg-white/10 text-sm font-semibold">Pagamento na retirada</div>
<div className="px-3 py-2 rounded-xl bg-white/10 text-sm font-semibold">Programe pelo WhatsApp</div>
</div>
</div>
</div>
</header>

<main className="max-w-lg mx-auto p-4">
<main className="max-w-4xl mx-auto px-4 -mt-8 space-y-6">
{view === 'login' && (
<form onSubmit={handleLogin} className="bg-white rounded-xl shadow-sm p-6 space-y-4">
<div>
<h2 className="text-lg font-bold text-gray-800">Acesso do administrador</h2>
<p className="text-sm text-gray-500">Use usuário e senha definidos no banco (PGUSER/PGPASSWORD).</p>
<form onSubmit={handleLogin} className="bg-white rounded-2xl shadow-lg p-6 space-y-4 border border-gray-100">
<div className="flex items-center gap-2 text-red-600 font-bold">
<User size={18} />
<h2 className="text-lg">Acesso do administrador</h2>
</div>
<p className="text-sm text-gray-500">Use usuário e senha definidos no banco (PGUSER/PGPASSWORD).</p>

{loginError && <div className="text-sm text-red-600 bg-red-50 border border-red-100 p-3 rounded-lg">{loginError}</div>}

Expand All @@ -327,7 +346,7 @@ function App() {
type="text"
value={loginForm.username}
onChange={(e) => setLoginForm((prev) => ({ ...prev, username: e.target.value }))}
className="w-full border rounded-lg p-3 focus:ring-2 focus:ring-red-500 focus:outline-none"
className="w-full border border-gray-200 rounded-lg p-3 focus:ring-2 focus:ring-red-500 focus:outline-none"
placeholder="postgres"
/>
</div>
Expand All @@ -341,22 +360,22 @@ function App() {
type="password"
value={loginForm.password}
onChange={(e) => setLoginForm((prev) => ({ ...prev, password: e.target.value }))}
className="w-full border rounded-lg p-3 focus:ring-2 focus:ring-red-500 focus:outline-none"
className="w-full border border-gray-200 rounded-lg p-3 focus:ring-2 focus:ring-red-500 focus:outline-none"
placeholder="senha do banco"
/>
</div>

<div className="flex gap-3">
<button
type="submit"
className="flex-1 bg-red-600 text-white py-3 rounded-lg font-semibold hover:bg-red-700 transition-colors"
className="flex-1 bg-red-600 text-white py-3 rounded-lg font-semibold hover:bg-red-700 transition-colors shadow"
>
Entrar
</button>
<button
type="button"
onClick={() => setView('menu')}
className="flex-1 border border-gray-300 text-gray-700 py-3 rounded-lg font-semibold hover:bg-gray-50 transition-colors"
className="flex-1 border border-gray-200 text-gray-700 py-3 rounded-lg font-semibold hover:bg-gray-50 transition-colors"
>
Cancelar
</button>
Expand Down Expand Up @@ -391,7 +410,7 @@ function App() {
</main>

{view === 'menu' && Object.keys(cart).length > 0 && (
<div className="fixed bottom-6 left-6 right-6 z-40 max-w-lg mx-auto">
<div className="fixed bottom-6 left-4 right-4 sm:left-6 sm:right-6 z-40 max-w-4xl mx-auto">
<button
onClick={() => setView('cart')}
className="w-full bg-gray-900 text-white p-4 rounded-2xl shadow-2xl flex justify-between items-center transform hover:scale-[1.02] transition-all"
Expand Down
14 changes: 12 additions & 2 deletions src/components/Admin/GrillQueue.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ export const GrillQueue = () => {
const [queue, setQueue] = useState([]);
const [loading, setLoading] = useState(false);

const getOrderTimestamp = (order) => {
if (order.createdAt?.seconds) return order.createdAt.seconds * 1000;
const time = order.createdAt || order.timestamp || order.dateString;
const parsed = time ? new Date(time).getTime() : 0;
return Number.isNaN(parsed) ? 0 : parsed;
};

const loadQueue = async () => {
setLoading(true);
const data = await orderService.fetchQueue();
setQueue(data);
const orderedQueue = data.sort((a, b) => getOrderTimestamp(a) - getOrderTimestamp(b));
setQueue(orderedQueue);
setLoading(false);
};

Expand Down Expand Up @@ -43,7 +51,9 @@ export const GrillQueue = () => {
<div key={order.id} className="bg-white p-4 rounded-xl shadow-sm border border-gray-100">
<div className="flex justify-between items-start">
<div>
<p className="text-sm text-gray-500">{formatDateTime(order.createdAt)}</p>
<p className="text-xs text-gray-500 flex items-center gap-1">
<Clock size={14} className="text-yellow-600" /> Recebido em {formatDateTime(order.createdAt) || '---'}
</p>
<h3 className="text-lg font-bold text-gray-800">{order.name || 'Cliente'}</h3>
<p className="text-xs text-gray-500 uppercase">{order.type}</p>
</div>
Expand Down
165 changes: 96 additions & 69 deletions src/components/Client/MenuView.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useMemo, useState } from 'react';
import { Plus, Minus, Image as ImageIcon, SlidersHorizontal } from 'lucide-react';
import { Plus, Minus, CupSoda, Beef, Martini, SlidersHorizontal } from 'lucide-react';
import { formatCurrency } from '../../utils/format';

export const MenuView = ({ products, cart, onUpdateCart, onProceed }) => {
Expand All @@ -20,17 +20,20 @@ export const MenuView = ({ products, cart, onUpdateCart, onProceed }) => {

const sortedProducts = useMemo(() => {
const espetos = [];
const bebidas = [];
const others = [];

products.forEach((item) => {
if (item.category?.toLowerCase().includes('espeto')) {
espetos.push(item);
} else if (item.category?.toLowerCase().includes('bebida')) {
bebidas.push(item);
} else {
others.push(item);
}
});

return [...espetos, ...others];
return [...espetos, ...bebidas, ...others];
}, [products]);

const filteredProducts = useMemo(() => {
Expand All @@ -49,102 +52,126 @@ export const MenuView = ({ products, cart, onUpdateCart, onProceed }) => {
);
}, [selectedCategory, sortedProducts]);

const groupedProducts = useMemo(() => {
const groups = new Map();
filteredProducts.forEach((item) => {
const label = item.category
? item.category.charAt(0).toUpperCase() + item.category.slice(1)
: 'Outros';
if (!groups.has(label)) {
groups.set(label, []);
}
groups.get(label).push(item);
});
return Array.from(groups.entries());
}, [filteredProducts]);

const groupIcon = (category) => {
if (category.toLowerCase().includes('espeto')) return <Beef size={18} />;
if (category.toLowerCase().includes('bebida')) return <Martini size={18} />;
return <CupSoda size={18} />;
};

return (
<div className="animate-in fade-in space-y-4">
<div className="flex items-center justify-between gap-2 overflow-x-auto pb-3 no-scrollbar">
<div className="flex gap-2">
<div className="animate-in fade-in space-y-5">
<div className="bg-white rounded-2xl shadow-sm border border-red-100 p-4">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs text-red-500 font-semibold uppercase">Cardápio</p>
<h2 className="text-lg font-black text-gray-800">Escolha seus favoritos</h2>
</div>
<div className="flex items-center gap-1 text-xs text-gray-400">
<SlidersHorizontal size={16} />
Filtros
</div>
</div>
<div className="mt-3 flex items-center gap-2 overflow-x-auto no-scrollbar pb-1">
{categories.map((category) => {
const isActive = category === selectedCategory;
return (
<button
key={category}
onClick={() => setSelectedCategory(category)}
className={`px-4 py-2 rounded-full text-sm font-semibold whitespace-nowrap transition-colors ${
className={`px-4 py-2 rounded-full text-sm font-semibold whitespace-nowrap transition-colors border ${
isActive
? 'bg-red-600 text-white shadow-md shadow-red-200'
: 'bg-white text-gray-600 border border-gray-200 hover:border-gray-300'
? 'bg-red-600 text-white border-red-600 shadow-md shadow-red-200'
: 'bg-red-50 text-red-700 border-red-100 hover:border-red-200'
}`}
>
{category}
</button>
);
})}
</div>
<div className="flex items-center gap-1 text-xs text-gray-400">
<SlidersHorizontal size={16} />
Filtros
</div>
</div>

<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{filteredProducts.map((item) => (
<div
key={item.id}
className="bg-white p-3 rounded-xl shadow-sm border border-gray-100 flex gap-3 transition-all active:scale-[0.99] hover:shadow-md"
>
<div className="w-24 h-24 bg-gray-100 rounded-lg flex-shrink-0 overflow-hidden">
{item.imageUrl ? (
<img src={item.imageUrl} alt={item.name} className="w-full h-full object-cover" />
) : (
<div className="w-full h-full flex items-center justify-center text-gray-300">
<ImageIcon size={24} />
</div>
)}
<div className="space-y-4">
{groupedProducts.map(([category, items]) => (
<section key={category} className="space-y-2">
<div className="flex items-center gap-2 text-red-700 font-bold text-lg">
<span className="w-8 h-8 rounded-full bg-red-50 text-red-600 flex items-center justify-center">
{groupIcon(category)}
</span>
{category}
</div>
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm divide-y">
{items.map((item) => (
<div key={item.id} className="flex items-center gap-3 p-3">
<div className="flex-1">
<div className="flex items-center gap-2">
<h3 className="font-semibold text-gray-900 text-base">{item.name}</h3>
{item.category && (
<span className="text-[10px] px-2 py-1 rounded-full bg-gray-100 text-gray-500 uppercase">
{item.category}
</span>
)}
</div>
<p className="text-sm text-gray-500 mt-1 line-clamp-2">{item.desc || 'Feito na brasa na hora para você.'}</p>
</div>

<div className="flex-1 flex flex-col justify-between">
<div>
<div className="flex items-center justify-between gap-2">
<h3 className="font-bold text-gray-800 line-clamp-1">{item.name}</h3>
{item.category && (
<span className="text-[10px] uppercase tracking-wide font-bold text-red-500 bg-red-50 px-2 py-1 rounded-full">
{item.category}
</span>
)}
</div>
<p className="text-xs text-gray-500 mt-1 line-clamp-2 leading-relaxed">{item.desc}</p>
</div>
<div className="flex justify-between items-end mt-2 gap-2">
<span className="font-bold text-gray-900 text-lg">{formatCurrency(item.price)}</span>

{cart[item.id] ? (
<div className="flex items-center bg-gray-100 rounded-lg p-1 gap-2 shadow-inner">
<button
onClick={() => onUpdateCart(item, -1)}
className="w-7 h-7 flex items-center justify-center bg-white rounded text-red-600 shadow-sm"
>
<Minus size={14} />
</button>
<span className="font-bold text-sm min-w-[20px] text-center">{cart[item.id].qty}</span>
<button
onClick={() => onUpdateCart(item, 1)}
className="w-7 h-7 flex items-center justify-center bg-green-600 rounded text-white shadow-sm"
>
<Plus size={14} />
</button>
<div className="flex flex-col items-end gap-2 min-w-[120px]">
<span className="text-sm font-extrabold text-red-600">{formatCurrency(item.price)}</span>
{cart[item.id] ? (
<div className="flex items-center gap-2 bg-red-50 rounded-full px-2 py-1 border border-red-100">
<button
onClick={() => onUpdateCart(item, -1)}
className="w-8 h-8 flex items-center justify-center rounded-full bg-white text-red-600 border border-red-200"
>
<Minus size={14} />
</button>
<span className="font-bold text-sm min-w-[20px] text-center">{cart[item.id].qty}</span>
<button
onClick={() => onUpdateCart(item, 1)}
className="w-8 h-8 flex items-center justify-center rounded-full bg-red-600 text-white shadow"
>
<Plus size={14} />
</button>
</div>
) : (
<button
onClick={() => onUpdateCart(item, 1)}
className="w-10 h-10 rounded-full bg-red-600 text-white flex items-center justify-center shadow-md hover:bg-red-700 transition"
aria-label={`Adicionar ${item.name}`}
>
<Plus size={18} />
</button>
)}
</div>
) : (
<button
onClick={() => onUpdateCart(item, 1)}
className="bg-red-50 text-red-600 px-3 py-1.5 rounded-lg text-sm font-bold hover:bg-red-100 transition-colors w-full text-center"
>
Adicionar
</button>
)}
</div>
</div>
))}
</div>
</div>
</section>
))}
</div>

{Object.keys(cart).length > 0 && (
<div className="fixed bottom-6 left-4 right-4 sm:left-6 sm:right-6 z-40 max-w-xl mx-auto">
<div className="fixed bottom-6 left-4 right-4 sm:left-6 sm:right-6 z-40 max-w-4xl mx-auto">
<button
onClick={onProceed}
className="w-full bg-gray-900 text-white p-4 rounded-2xl shadow-2xl flex justify-between items-center transform hover:scale-[1.02] transition-all"
className="w-full bg-red-600 text-white p-4 rounded-2xl shadow-xl flex justify-between items-center transform hover:scale-[1.02] transition-all"
>
<div className="flex items-center gap-3">
<span className="bg-red-600 px-3 py-1 rounded-lg text-sm font-bold">
<span className="bg-white/20 px-3 py-1 rounded-lg text-sm font-bold">
{Object.values(cart).reduce((acc, item) => acc + item.qty, 0)}
</span>
<span className="font-bold">Ver sacola</span>
Expand Down