dimensionalpulsar commited on
Commit
76823f0
·
verified ·
1 Parent(s): 18f4249

Upload 48 files

Browse files
src/components/admin/EmployeeSchedules.jsx ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { db } from '../../firebase/config';
3
+ import { ref, onValue, set } from 'firebase/database';
4
+ import { Clock, Save, User as UserIcon } from 'lucide-react';
5
+
6
+ export default function EmployeeSchedules() {
7
+ const [employees, setEmployees] = useState([]);
8
+ const [schedules, setSchedules] = useState({});
9
+
10
+ useEffect(() => {
11
+ // Fetch Employees (Users with role 'mesero' or 'admin')
12
+ onValue(ref(db, 'users'), (snapshot) => {
13
+ const data = snapshot.val();
14
+ if (data) {
15
+ setEmployees(Object.keys(data).map(k => ({ email: k.replace(',', '.'), ...data[k] })));
16
+ }
17
+ });
18
+
19
+ // Fetch Schedules
20
+ onValue(ref(db, 'schedules'), (snapshot) => {
21
+ if (snapshot.exists()) setSchedules(snapshot.val());
22
+ });
23
+ }, []);
24
+
25
+ const handleUpdateSchedule = async (email, field, value) => {
26
+ const safeEmail = email.replace(/\./g, ',');
27
+ await set(ref(db, `schedules/${safeEmail}/${field}`), value);
28
+ };
29
+
30
+ return (
31
+ <div className="animate-fade-in" style={{ padding: '0 1rem' }}>
32
+ <header style={{ marginBottom: '2rem' }}>
33
+ <h2 className="text-gradient" style={{ fontSize: '2rem', fontWeight: '800', display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
34
+ <Clock size={28} /> Control de Horarios
35
+ </h2>
36
+ <p style={{ color: 'var(--text-muted)' }}>Asignación de turnos y disponibilidad de personal</p>
37
+ </header>
38
+
39
+ <div className="glass-panel" style={{ overflow: 'hidden' }}>
40
+ <table style={{ width: '100%', borderCollapse: 'collapse', textAlign: 'left' }}>
41
+ <thead>
42
+ <tr style={{ borderBottom: '1px solid var(--border-subtle)', background: 'rgba(255,255,255,0.02)' }}>
43
+ <th style={{ padding: '1.25rem', color: 'var(--text-muted)', fontWeight: '500' }}>Empleado</th>
44
+ <th style={{ padding: '1.25rem', color: 'var(--text-muted)', fontWeight: '500' }}>Rol</th>
45
+ <th style={{ padding: '1.25rem', color: 'var(--text-muted)', fontWeight: '500' }}>Entrada</th>
46
+ <th style={{ padding: '1.25rem', color: 'var(--text-muted)', fontWeight: '500' }}>Salida</th>
47
+ <th style={{ padding: '1.25rem', color: 'var(--text-muted)', fontWeight: '500' }}>Estado</th>
48
+ </tr>
49
+ </thead>
50
+ <tbody>
51
+ {employees.map((emp) => {
52
+ const safeEmail = emp.email.replace(/\./g, ',');
53
+ const sched = schedules[safeEmail] || { start: '09:00', end: '17:00' };
54
+
55
+ return (
56
+ <tr key={emp.email} style={{ borderBottom: '1px solid var(--border-subtle)' }} className="table-row-hover">
57
+ <td style={{ padding: '1.25rem' }}>
58
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
59
+ <div style={{ width: '32px', height: '32px', borderRadius: '50%', background: 'var(--primary)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
60
+ <UserIcon size={16} color="#fff" />
61
+ </div>
62
+ <span style={{ fontWeight: '600' }}>{emp.email}</span>
63
+ </div>
64
+ </td>
65
+ <td style={{ padding: '1.25rem' }}>
66
+ <span style={{ fontSize: '0.75rem', textTransform: 'uppercase', background: 'rgba(255,255,255,0.05)', padding: '4px 10px', borderRadius: '12px', color: 'var(--text-muted)' }}>
67
+ {emp.role}
68
+ </span>
69
+ </td>
70
+ <td style={{ padding: '1.25rem' }}>
71
+ <input
72
+ type="time"
73
+ value={sched.start}
74
+ onChange={(e) => handleUpdateSchedule(emp.email, 'start', e.target.value)}
75
+ style={inputStyle}
76
+ />
77
+ </td>
78
+ <td style={{ padding: '1.25rem' }}>
79
+ <input
80
+ type="time"
81
+ value={sched.end}
82
+ onChange={(e) => handleUpdateSchedule(emp.email, 'end', e.target.value)}
83
+ style={inputStyle}
84
+ />
85
+ </td>
86
+ <td style={{ padding: '1.25rem' }}>
87
+ <span style={{ color: 'var(--success)', fontSize: '0.8rem', fontWeight: '600' }}>Activo</span>
88
+ </td>
89
+ </tr>
90
+ );
91
+ })}
92
+ </tbody>
93
+ </table>
94
+ </div>
95
+ </div>
96
+ );
97
+ }
98
+
99
+ const inputStyle = {
100
+ padding: '0.5rem',
101
+ borderRadius: '6px',
102
+ background: 'rgba(255,255,255,0.05)',
103
+ border: '1px solid var(--border-subtle)',
104
+ color: '#fff',
105
+ outline: 'none',
106
+ fontSize: '0.9rem'
107
+ };
src/components/admin/Reports.jsx CHANGED
@@ -68,6 +68,8 @@ export default function Reports() {
68
  data: sortedProducts.map(p => p[1])
69
  });
70
  }
 
 
71
  onValue(ref(db, 'expenses'), (snapshot) => {
72
  const data = snapshot.val();
73
  setExpenses(data ? Object.keys(data).map(k => ({ id: k, ...data[k] })) : []);
@@ -151,6 +153,50 @@ export default function Reports() {
151
  </div>
152
  </div>
153
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  </div>
155
  );
156
  }
 
 
 
68
  data: sortedProducts.map(p => p[1])
69
  });
70
  }
71
+ });
72
+
73
  onValue(ref(db, 'expenses'), (snapshot) => {
74
  const data = snapshot.val();
75
  setExpenses(data ? Object.keys(data).map(k => ({ id: k, ...data[k] })) : []);
 
153
  </div>
154
  </div>
155
  </div>
156
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))', gap: '2rem', marginTop: '2rem' }}>
157
+ {/* Arqueo de Caja */}
158
+ <div className="glass-card">
159
+ <h3 style={{ marginBottom: '1.5rem', fontSize: '1rem', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
160
+ <Receipt size={18} /> Arqueo de Caja (Hoy)
161
+ </h3>
162
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
163
+ {Object.entries(todaySummary).map(([method, amount]) => (
164
+ <div key={method} style={{ display: 'flex', justifyContent: 'space-between', padding: '1rem', background: 'rgba(255,255,255,0.02)', borderRadius: '8px' }}>
165
+ <span style={{ color: 'var(--text-muted)' }}>{method}</span>
166
+ <span style={{ fontWeight: '700' }}>${amount.toFixed(2)}</span>
167
+ </div>
168
+ ))}
169
+ <div style={{ padding: '1rem', marginTop: '0.5rem', background: 'rgba(76,217,100,0.1)', borderRadius: '8px', display: 'flex', justifyContent: 'space-between', border: '1px solid var(--success)' }}>
170
+ <span style={{ fontWeight: '700' }}>Corte Total Bruto:</span>
171
+ <span style={{ fontWeight: '900', color: 'var(--success)' }}>${Object.values(todaySummary).reduce((a,b) => a + b, 0).toFixed(2)}</span>
172
+ </div>
173
+ </div>
174
+ </div>
175
+
176
+ {/* Registro de Gastos */}
177
+ <div className="glass-card">
178
+ <h3 style={{ marginBottom: '1.5rem', fontSize: '1rem', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
179
+ <DollarSign size={18} /> Registro de Egresos
180
+ </h3>
181
+ <form onSubmit={handleAddExpense} style={{ display: 'flex', gap: '0.5rem', marginBottom: '1.5rem' }}>
182
+ <input type="text" placeholder="Descripción" value={newExpense.desc} onChange={e => setNewExpense({...newExpense, desc: e.target.value})} style={{...inputStyle, flex: 2}} />
183
+ <input type="number" placeholder="$" value={newExpense.amount} onChange={e => setNewExpense({...newExpense, amount: e.target.value})} style={{...inputStyle, flex: 1}} />
184
+ <button type="submit" className="btn-primary" style={{ padding: '0 1rem' }}><Plus size={20}/></button>
185
+ </form>
186
+ <div style={{ maxHeight: '200px', overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
187
+ {expenses.length === 0 ? <p style={{ color: 'var(--text-muted)', fontSize: '0.8rem', textAlign: 'center' }}>Sin gastos registrados</p> :
188
+ expenses.map(exp => (
189
+ <div key={exp.id} style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem', padding: '0.5rem', borderBottom: '1px solid rgba(255,255,255,0.03)' }}>
190
+ <span>{exp.desc}</span>
191
+ <span style={{ color: 'var(--primary)', fontWeight: '600' }}>-${exp.amount.toFixed(2)}</span>
192
+ </div>
193
+ ))
194
+ }
195
+ </div>
196
+ </div>
197
+ </div>
198
  </div>
199
  );
200
  }
201
+
202
+ const inputStyle = { width: '100%', padding: '0.7rem', borderRadius: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid var(--border-subtle)', color: '#fff', outline: 'none', fontSize: '0.9rem' };
src/pages/AdminDashboard.jsx CHANGED
@@ -1,7 +1,7 @@
1
  import React, { useState } from 'react';
2
  import { useAuth } from '../context/AuthContext';
3
  import { useNavigate } from 'react-router-dom';
4
- import { LayoutDashboard, Utensils, Box, PieChart, LogOut, DollarSign, UtensilsCrossed, Users } from 'lucide-react';
5
  import DashboardOverview from '../components/admin/DashboardOverview';
6
  import MenuEditor from '../components/admin/MenuEditor';
7
  import InventoryControl from '../components/admin/InventoryControl';
@@ -9,6 +9,7 @@ import Reports from '../components/admin/Reports';
9
  import FinanceManager from '../components/admin/FinanceManager';
10
  import UserManager from '../components/admin/UserManager';
11
  import TableManager from '../components/admin/TableManager';
 
12
 
13
  export default function AdminDashboard() {
14
  const { logout, currentUser } = useAuth();
@@ -27,6 +28,7 @@ export default function AdminDashboard() {
27
  { id: 'reports', label: 'Reportes y Analítica', icon: <PieChart size={20} /> },
28
  { id: 'cash', label: 'Caja y Finanzas', icon: <DollarSign size={20} /> },
29
  { id: 'users', label: 'Equipo y Roles', icon: <Users size={20} /> },
 
30
  { id: 'tables', label: 'Diseño Salón', icon: <LayoutDashboard size={20} /> },
31
  { id: 'kitchen', label: 'Pantalla Cocina', icon: <UtensilsCrossed size={20} />, action: () => window.open('/kitchen', '_blank') },
32
  { id: 'menu_public', label: 'Carta Digital', icon: <Utensils size={20} />, action: () => window.open('/menu', '_blank') }
@@ -80,6 +82,7 @@ export default function AdminDashboard() {
80
  {activeTab === 'reports' && <Reports />}
81
  {activeTab === 'cash' && <FinanceManager />}
82
  {activeTab === 'users' && <UserManager />}
 
83
  {activeTab === 'tables' && <TableManager />}
84
  </div>
85
  </main>
 
1
  import React, { useState } from 'react';
2
  import { useAuth } from '../context/AuthContext';
3
  import { useNavigate } from 'react-router-dom';
4
+ import { LayoutDashboard, Utensils, Box, PieChart, LogOut, DollarSign, UtensilsCrossed, Users, Clock } from 'lucide-react';
5
  import DashboardOverview from '../components/admin/DashboardOverview';
6
  import MenuEditor from '../components/admin/MenuEditor';
7
  import InventoryControl from '../components/admin/InventoryControl';
 
9
  import FinanceManager from '../components/admin/FinanceManager';
10
  import UserManager from '../components/admin/UserManager';
11
  import TableManager from '../components/admin/TableManager';
12
+ import EmployeeSchedules from '../components/admin/EmployeeSchedules';
13
 
14
  export default function AdminDashboard() {
15
  const { logout, currentUser } = useAuth();
 
28
  { id: 'reports', label: 'Reportes y Analítica', icon: <PieChart size={20} /> },
29
  { id: 'cash', label: 'Caja y Finanzas', icon: <DollarSign size={20} /> },
30
  { id: 'users', label: 'Equipo y Roles', icon: <Users size={20} /> },
31
+ { id: 'horarios', label: 'Horarios Personal', icon: <Clock size={20} /> },
32
  { id: 'tables', label: 'Diseño Salón', icon: <LayoutDashboard size={20} /> },
33
  { id: 'kitchen', label: 'Pantalla Cocina', icon: <UtensilsCrossed size={20} />, action: () => window.open('/kitchen', '_blank') },
34
  { id: 'menu_public', label: 'Carta Digital', icon: <Utensils size={20} />, action: () => window.open('/menu', '_blank') }
 
82
  {activeTab === 'reports' && <Reports />}
83
  {activeTab === 'cash' && <FinanceManager />}
84
  {activeTab === 'users' && <UserManager />}
85
+ {activeTab === 'horarios' && <EmployeeSchedules />}
86
  {activeTab === 'tables' && <TableManager />}
87
  </div>
88
  </main>
src/pages/CustomerMenu.jsx CHANGED
@@ -1,14 +1,16 @@
1
  import React, { useState, useEffect } from 'react';
2
  import { useNavigate } from 'react-router-dom';
3
  import { db } from '../firebase/config';
4
- import { ref, onValue } from 'firebase/database';
5
- import { Utensils, Star, Clock, MapPin, ChefHat, Instagram, Facebook, ArrowRight } from 'lucide-react';
6
 
7
  export default function CustomerMenu() {
8
  const [menuItems, setMenuItems] = useState([]);
9
  const [categories, setCategories] = useState([]);
10
  const [activeCategory, setActiveCategory] = useState('Todos');
11
  const [menuTheme, setMenuTheme] = useState('dark'); // 'dark' or 'light'
 
 
12
  const navigate = useNavigate();
13
 
14
  useEffect(() => {
@@ -175,6 +177,63 @@ export default function CustomerMenu() {
175
  </div>
176
  </section>
177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  {/* Footer */}
179
  <footer style={{
180
  padding: '80px 20px', borderTop: `1px solid ${themeColors.border}`,
 
1
  import React, { useState, useEffect } from 'react';
2
  import { useNavigate } from 'react-router-dom';
3
  import { db } from '../firebase/config';
4
+ import { ref, onValue, push, set } from 'firebase/database';
5
+ import { Utensils, Star, Clock, MapPin, ChefHat, Instagram, Facebook, ArrowRight, MessageSquare, Send } from 'lucide-react';
6
 
7
  export default function CustomerMenu() {
8
  const [menuItems, setMenuItems] = useState([]);
9
  const [categories, setCategories] = useState([]);
10
  const [activeCategory, setActiveCategory] = useState('Todos');
11
  const [menuTheme, setMenuTheme] = useState('dark'); // 'dark' or 'light'
12
+ const [feedback, setFeedback] = useState({ rating: 5, comment: '' });
13
+ const [submitted, setSubmitted] = useState(false);
14
  const navigate = useNavigate();
15
 
16
  useEffect(() => {
 
177
  </div>
178
  </section>
179
 
180
+ {/* Feedback Section */}
181
+ <section style={{ maxWidth: '800px', margin: '100px auto', padding: '40px', textAlign: 'center' }}>
182
+ <div className="glass-card" style={{ padding: '3rem', border: `1px solid ${themeColors.border}`, background: themeColors.card }}>
183
+ {submitted ? (
184
+ <div className="animate-fade-in">
185
+ <Star size={50} color={themeColors.accent} fill={themeColors.accent} style={{ marginBottom: '1rem' }} />
186
+ <h2 style={{ fontSize: '2rem', fontWeight: '800' }}>¡Gracias por tu Feedback!</h2>
187
+ <p style={{ color: themeColors.muted, marginTop: '1rem' }}>Tu opinión nos ayuda a mejorar cada día.</p>
188
+ </div>
189
+ ) : (
190
+ <>
191
+ <h2 style={{ fontSize: '2rem', fontWeight: '800', marginBottom: '1rem' }}>¿Qué te pareció tu visita?</h2>
192
+ <p style={{ color: themeColors.muted, marginBottom: '2rem' }}>Déjanos tu comentario para seguir brindando la mejor calidad.</p>
193
+
194
+ <div style={{ display: 'flex', justifyContent: 'center', gap: '10px', marginBottom: '2rem' }}>
195
+ {[1,2,3,4,5].map(star => (
196
+ <button
197
+ key={star}
198
+ onClick={() => setFeedback({...feedback, rating: star})}
199
+ style={{ background: 'none', border: 'none', cursor: 'pointer', transition: 'transform 0.2s' }}
200
+ onMouseEnter={e => e.currentTarget.style.transform = 'scale(1.2)'}
201
+ onMouseLeave={e => e.currentTarget.style.transform = 'scale(1)'}
202
+ >
203
+ <Star size={32} color={star <= feedback.rating ? themeColors.accent : themeColors.muted} fill={star <= feedback.rating ? themeColors.accent : 'transparent'} />
204
+ </button>
205
+ ))}
206
+ </div>
207
+
208
+ <textarea
209
+ placeholder="Escribe tu comentario aquí..."
210
+ value={feedback.comment}
211
+ onChange={(e) => setFeedback({...feedback, comment: e.target.value})}
212
+ style={{
213
+ width: '100%', height: '120px', borderRadius: '15px', padding: '1.5rem',
214
+ background: isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)',
215
+ border: `1px solid ${themeColors.border}`, color: themeColors.text,
216
+ outline: 'none', fontSize: '1rem', marginBottom: '1.5rem'
217
+ }}
218
+ />
219
+
220
+ <button
221
+ onClick={async () => {
222
+ if (!feedback.comment) return;
223
+ const fRef = push(ref(db, 'feedback'));
224
+ await set(fRef, { ...feedback, timestamp: Date.now() });
225
+ setSubmitted(true);
226
+ }}
227
+ className="btn-primary"
228
+ style={{ padding: '15px 40px', borderRadius: '30px', display: 'flex', alignItems: 'center', gap: '10px', margin: '0 auto' }}
229
+ >
230
+ <Send size={18} /> Enviar Comentario
231
+ </button>
232
+ </>
233
+ )}
234
+ </div>
235
+ </section>
236
+
237
  {/* Footer */}
238
  <footer style={{
239
  padding: '80px 20px', borderTop: `1px solid ${themeColors.border}`,
src/pages/KitchenView.jsx CHANGED
@@ -9,6 +9,7 @@ export default function KitchenView() {
9
  const { logout } = useAuth();
10
  const navigate = useNavigate();
11
  const [orders, setOrders] = useState([]);
 
12
  const lastOrderCount = useRef(0);
13
 
14
  useEffect(() => {
@@ -30,6 +31,9 @@ export default function KitchenView() {
30
  setOrders([]);
31
  }
32
  });
 
 
 
33
  }, []);
34
 
35
  const playNotificationSound = () => {
@@ -81,15 +85,18 @@ export default function KitchenView() {
81
  <span style={{ fontSize: '1.2rem', fontWeight: '800', color: order.status === 'preparing' ? 'var(--warning)' : 'var(--primary)' }}>
82
  {order.table} {order.type === 'Llevar' && '🛍️'} {order.type === 'Delivery' && '🚀'}
83
  </span>
84
- <span style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>
85
- {Math.floor((Date.now() - order.timestamp) / 60000)} min atrás
86
  </span>
87
  </div>
88
 
89
  <div style={{ marginBottom: '1.5rem', minHeight: '80px' }}>
90
  {order.items.map((item, idx) => (
91
  <div key={idx} style={{ display: 'flex', justifyContent: 'space-between', padding: '0.5rem 0', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
92
- <span style={{ fontWeight: '600' }}><span style={{ color: 'var(--primary)', fontWeight: '800' }}>{item.qty}</span> {item.name}</span>
 
 
 
93
  </div>
94
  ))}
95
  </div>
 
9
  const { logout } = useAuth();
10
  const navigate = useNavigate();
11
  const [orders, setOrders] = useState([]);
12
+ const [currentTime, setCurrentTime] = useState(Date.now());
13
  const lastOrderCount = useRef(0);
14
 
15
  useEffect(() => {
 
31
  setOrders([]);
32
  }
33
  });
34
+
35
+ const timer = setInterval(() => setCurrentTime(Date.now()), 60000);
36
+ return () => clearInterval(timer);
37
  }, []);
38
 
39
  const playNotificationSound = () => {
 
85
  <span style={{ fontSize: '1.2rem', fontWeight: '800', color: order.status === 'preparing' ? 'var(--warning)' : 'var(--primary)' }}>
86
  {order.table} {order.type === 'Llevar' && '🛍️'} {order.type === 'Delivery' && '🚀'}
87
  </span>
88
+ <span style={{ fontSize: '0.8rem', color: (currentTime - order.timestamp) > 900000 ? 'var(--primary)' : 'var(--text-muted)', fontWeight: '700' }}>
89
+ <Clock size={14} /> {Math.floor((currentTime - order.timestamp) / 60000)} min
90
  </span>
91
  </div>
92
 
93
  <div style={{ marginBottom: '1.5rem', minHeight: '80px' }}>
94
  {order.items.map((item, idx) => (
95
  <div key={idx} style={{ display: 'flex', justifyContent: 'space-between', padding: '0.5rem 0', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
96
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
97
+ <span style={{ fontWeight: '600' }}><span style={{ color: 'var(--primary)', fontWeight: '800' }}>{item.qty}</span> {item.name}</span>
98
+ {item.note && <span style={{ fontSize: '0.75rem', color: 'var(--warning)', fontStyle: 'italic' }}>* {item.note}</span>}
99
+ </div>
100
  </div>
101
  ))}
102
  </div>