first commit
This commit is contained in:
323
src/context/AppContext.tsx
Normal file
323
src/context/AppContext.tsx
Normal file
@@ -0,0 +1,323 @@
|
||||
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { Appointment, Barber, BarberShop, CartItem, Order, Product, Service, User } from '../types';
|
||||
import { mockShops, mockUsers } from '../data/mock';
|
||||
import { storage } from '../lib/storage';
|
||||
|
||||
type State = {
|
||||
user?: User;
|
||||
users: User[];
|
||||
shops: BarberShop[];
|
||||
appointments: Appointment[];
|
||||
orders: Order[];
|
||||
cart: CartItem[];
|
||||
};
|
||||
|
||||
type AppContextValue = State & {
|
||||
login: (email: string, password: string) => boolean;
|
||||
logout: () => void;
|
||||
register: (payload: Omit<User, 'id' | 'shopId'> & { shopName?: string }) => boolean;
|
||||
addToCart: (item: CartItem) => void;
|
||||
removeFromCart: (refId: string) => void;
|
||||
clearCart: () => void;
|
||||
createAppointment: (input: Omit<Appointment, 'id' | 'status' | 'total'>) => Appointment | null;
|
||||
placeOrder: (customerId: string, shopId?: string) => Order | null;
|
||||
updateAppointmentStatus: (id: string, status: Appointment['status']) => void;
|
||||
updateOrderStatus: (id: string, status: Order['status']) => void;
|
||||
addService: (shopId: string, service: Omit<Service, 'id'>) => void;
|
||||
updateService: (shopId: string, service: Service) => void;
|
||||
deleteService: (shopId: string, serviceId: string) => void;
|
||||
addProduct: (shopId: string, product: Omit<Product, 'id'>) => void;
|
||||
updateProduct: (shopId: string, product: Product) => void;
|
||||
deleteProduct: (shopId: string, productId: string) => void;
|
||||
addBarber: (shopId: string, barber: Omit<Barber, 'id'>) => void;
|
||||
updateBarber: (shopId: string, barber: Barber) => void;
|
||||
deleteBarber: (shopId: string, barberId: string) => void;
|
||||
};
|
||||
|
||||
const initialState: State = {
|
||||
user: undefined,
|
||||
users: mockUsers,
|
||||
shops: mockShops,
|
||||
appointments: [],
|
||||
orders: [],
|
||||
cart: [],
|
||||
};
|
||||
|
||||
const AppContext = createContext<AppContextValue | undefined>(undefined);
|
||||
|
||||
export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [state, setState] = useState<State>(initialState);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const saved = await storage.get('smart-agenda', initialState);
|
||||
setState(saved);
|
||||
} catch (err) {
|
||||
console.error('Error loading data:', err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading) {
|
||||
storage.set('smart-agenda', state);
|
||||
}
|
||||
}, [state, isLoading]);
|
||||
|
||||
const login = (email: string, password: string) => {
|
||||
const found = state.users.find((u) => u.email === email && u.password === password);
|
||||
if (found) {
|
||||
setState((s) => ({ ...s, user: found }));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const logout = () => setState((s) => ({ ...s, user: undefined }));
|
||||
|
||||
const register: AppContextValue['register'] = ({ shopName, ...payload }) => {
|
||||
const exists = state.users.some((u) => u.email === payload.email);
|
||||
if (exists) return false;
|
||||
|
||||
if (payload.role === 'barbearia') {
|
||||
const shopId = nanoid();
|
||||
const shop: BarberShop = {
|
||||
id: shopId,
|
||||
name: shopName || `Barbearia ${payload.name}`,
|
||||
address: 'Endereço a definir',
|
||||
rating: 0,
|
||||
barbers: [],
|
||||
services: [],
|
||||
products: [],
|
||||
};
|
||||
const user: User = { ...payload, id: nanoid(), role: 'barbearia', shopId };
|
||||
setState((s) => ({
|
||||
...s,
|
||||
user,
|
||||
users: [...s.users, user],
|
||||
shops: [...s.shops, shop],
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
const user: User = { ...payload, id: nanoid(), role: 'cliente' };
|
||||
setState((s) => ({
|
||||
...s,
|
||||
user,
|
||||
users: [...s.users, user],
|
||||
}));
|
||||
return true;
|
||||
};
|
||||
|
||||
const addToCart: AppContextValue['addToCart'] = (item) => {
|
||||
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 += item.qty;
|
||||
else cart.push(item);
|
||||
return { ...s, cart };
|
||||
});
|
||||
};
|
||||
|
||||
const removeFromCart: AppContextValue['removeFromCart'] = (refId) => {
|
||||
setState((s) => ({ ...s, cart: s.cart.filter((c) => c.refId !== refId) }));
|
||||
};
|
||||
|
||||
const clearCart = () => setState((s) => ({ ...s, cart: [] }));
|
||||
|
||||
const createAppointment: AppContextValue['createAppointment'] = (input) => {
|
||||
const shop = state.shops.find((s) => s.id === input.shopId);
|
||||
if (!shop) return null;
|
||||
const svc = shop.services.find((s) => s.id === input.serviceId);
|
||||
if (!svc) return null;
|
||||
|
||||
const exists = state.appointments.find(
|
||||
(ap) => ap.barberId === input.barberId && ap.date === input.date && ap.status !== 'cancelado'
|
||||
);
|
||||
if (exists) return null;
|
||||
|
||||
const appointment: Appointment = {
|
||||
...input,
|
||||
id: nanoid(),
|
||||
status: 'pendente',
|
||||
total: svc.price,
|
||||
};
|
||||
|
||||
setState((s) => ({ ...s, appointments: [...s.appointments, appointment] }));
|
||||
return appointment;
|
||||
};
|
||||
|
||||
const placeOrder: AppContextValue['placeOrder'] = (customerId, onlyShopId) => {
|
||||
if (!state.cart.length) return null;
|
||||
const grouped = state.cart.reduce<Record<string, CartItem[]>>((acc, item) => {
|
||||
acc[item.shopId] = acc[item.shopId] || [];
|
||||
acc[item.shopId].push(item);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const entries = Object.entries(grouped).filter(([shopId]) => (onlyShopId ? shopId === onlyShopId : true));
|
||||
|
||||
const newOrders: Order[] = entries.map(([shopId, items]) => {
|
||||
const total = items.reduce((sum, item) => {
|
||||
const shop = state.shops.find((s) => s.id === item.shopId);
|
||||
if (!shop) return sum;
|
||||
const price =
|
||||
item.type === 'service'
|
||||
? shop.services.find((s) => s.id === item.refId)?.price ?? 0
|
||||
: shop.products.find((p) => p.id === item.refId)?.price ?? 0;
|
||||
return sum + price * item.qty;
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
id: nanoid(),
|
||||
shopId,
|
||||
customerId,
|
||||
items,
|
||||
total,
|
||||
status: 'pendente',
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
});
|
||||
|
||||
setState((s) => ({ ...s, orders: [...s.orders, ...newOrders], cart: [] }));
|
||||
return newOrders[0] ?? null;
|
||||
};
|
||||
|
||||
const updateAppointmentStatus: AppContextValue['updateAppointmentStatus'] = (id, status) => {
|
||||
setState((s) => ({
|
||||
...s,
|
||||
appointments: s.appointments.map((a) => (a.id === id ? { ...a, status } : a)),
|
||||
}));
|
||||
};
|
||||
|
||||
const updateOrderStatus: AppContextValue['updateOrderStatus'] = (id, status) => {
|
||||
setState((s) => ({
|
||||
...s,
|
||||
orders: s.orders.map((o) => (o.id === id ? { ...o, status } : o)),
|
||||
}));
|
||||
};
|
||||
|
||||
const addService: AppContextValue['addService'] = (shopId, service) => {
|
||||
const entry: Service = { ...service, id: nanoid() };
|
||||
setState((s) => ({
|
||||
...s,
|
||||
shops: s.shops.map((shop) => (shop.id === shopId ? { ...shop, services: [...shop.services, entry] } : shop)),
|
||||
}));
|
||||
};
|
||||
|
||||
const updateService: AppContextValue['updateService'] = (shopId, service) => {
|
||||
setState((s) => ({
|
||||
...s,
|
||||
shops: s.shops.map((shop) =>
|
||||
shop.id === shopId ? { ...shop, services: shop.services.map((sv) => (sv.id === service.id ? service : sv)) } : shop
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
const deleteService: AppContextValue['deleteService'] = (shopId, serviceId) => {
|
||||
setState((s) => ({
|
||||
...s,
|
||||
shops: s.shops.map((shop) =>
|
||||
shop.id === shopId ? { ...shop, services: shop.services.filter((sv) => sv.id !== serviceId) } : shop
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
const addProduct: AppContextValue['addProduct'] = (shopId, product) => {
|
||||
const entry: Product = { ...product, id: nanoid() };
|
||||
setState((s) => ({
|
||||
...s,
|
||||
shops: s.shops.map((shop) => (shop.id === shopId ? { ...shop, products: [...shop.products, entry] } : shop)),
|
||||
}));
|
||||
};
|
||||
|
||||
const updateProduct: AppContextValue['updateProduct'] = (shopId, product) => {
|
||||
setState((s) => ({
|
||||
...s,
|
||||
shops: s.shops.map((shop) =>
|
||||
shop.id === shopId ? { ...shop, products: shop.products.map((p) => (p.id === product.id ? product : p)) } : shop
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
const deleteProduct: AppContextValue['deleteProduct'] = (shopId, productId) => {
|
||||
setState((s) => ({
|
||||
...s,
|
||||
shops: s.shops.map((shop) =>
|
||||
shop.id === shopId ? { ...shop, products: shop.products.filter((p) => p.id !== productId) } : shop
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
const addBarber: AppContextValue['addBarber'] = (shopId, barber) => {
|
||||
const entry: Barber = { ...barber, id: nanoid() };
|
||||
setState((s) => ({
|
||||
...s,
|
||||
shops: s.shops.map((shop) => (shop.id === shopId ? { ...shop, barbers: [...shop.barbers, entry] } : shop)),
|
||||
}));
|
||||
};
|
||||
|
||||
const updateBarber: AppContextValue['updateBarber'] = (shopId, barber) => {
|
||||
setState((s) => ({
|
||||
...s,
|
||||
shops: s.shops.map((shop) =>
|
||||
shop.id === shopId ? { ...shop, barbers: shop.barbers.map((b) => (b.id === barber.id ? barber : b)) } : shop
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
const deleteBarber: AppContextValue['deleteBarber'] = (shopId, barberId) => {
|
||||
setState((s) => ({
|
||||
...s,
|
||||
shops: s.shops.map((shop) =>
|
||||
shop.id === shopId ? { ...shop, barbers: shop.barbers.filter((b) => b.id !== barberId) } : shop
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
const value: AppContextValue = useMemo(
|
||||
() => ({
|
||||
...state,
|
||||
login,
|
||||
logout,
|
||||
register,
|
||||
addToCart,
|
||||
removeFromCart,
|
||||
clearCart,
|
||||
createAppointment,
|
||||
placeOrder,
|
||||
updateAppointmentStatus,
|
||||
updateOrderStatus,
|
||||
addService,
|
||||
updateService,
|
||||
deleteService,
|
||||
addProduct,
|
||||
updateProduct,
|
||||
deleteProduct,
|
||||
addBarber,
|
||||
updateBarber,
|
||||
deleteBarber,
|
||||
}),
|
||||
[state]
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return null; // Ou um componente de loading
|
||||
}
|
||||
|
||||
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
|
||||
};
|
||||
|
||||
export const useApp = () => {
|
||||
const ctx = useContext(AppContext);
|
||||
if (!ctx) throw new Error('useApp deve ser usado dentro de AppProvider');
|
||||
return ctx;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user