359 lines
10 KiB
TypeScript
359 lines
10 KiB
TypeScript
import React, { createContext, useContext, useReducer, useEffect, ReactNode } from 'react';
|
|
|
|
export interface Bakery {
|
|
id: string;
|
|
name: string;
|
|
logo?: string;
|
|
role: 'owner' | 'manager' | 'baker' | 'staff';
|
|
status: 'active' | 'inactive';
|
|
address?: string;
|
|
description?: string;
|
|
settings?: {
|
|
timezone: string;
|
|
currency: string;
|
|
language: string;
|
|
};
|
|
permissions?: string[];
|
|
}
|
|
|
|
interface BakeryState {
|
|
bakeries: Bakery[];
|
|
currentBakery: Bakery | null;
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
}
|
|
|
|
type BakeryAction =
|
|
| { type: 'SET_LOADING'; payload: boolean }
|
|
| { type: 'SET_ERROR'; payload: string | null }
|
|
| { type: 'SET_BAKERIES'; payload: Bakery[] }
|
|
| { type: 'SET_CURRENT_BAKERY'; payload: Bakery }
|
|
| { type: 'ADD_BAKERY'; payload: Bakery }
|
|
| { type: 'UPDATE_BAKERY'; payload: { id: string; updates: Partial<Bakery> } }
|
|
| { type: 'REMOVE_BAKERY'; payload: string };
|
|
|
|
const initialState: BakeryState = {
|
|
bakeries: [],
|
|
currentBakery: null,
|
|
isLoading: false,
|
|
error: null,
|
|
};
|
|
|
|
// Mock bakeries data
|
|
const mockBakeries: Bakery[] = [
|
|
{
|
|
id: '1',
|
|
name: 'Panadería San Miguel',
|
|
logo: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=100&h=100&fit=crop&crop=center',
|
|
role: 'owner',
|
|
status: 'active',
|
|
address: 'Calle Mayor 123, Madrid, 28013',
|
|
description: 'Panadería tradicional familiar con más de 30 años de experiencia.',
|
|
settings: {
|
|
timezone: 'Europe/Madrid',
|
|
currency: 'EUR',
|
|
language: 'es',
|
|
},
|
|
permissions: ['*'],
|
|
},
|
|
{
|
|
id: '2',
|
|
name: 'Panadería La Artesana',
|
|
logo: 'https://images.unsplash.com/photo-1509440159596-0249088772ff?w=100&h=100&fit=crop&crop=center',
|
|
role: 'manager',
|
|
status: 'active',
|
|
address: 'Avenida de la Paz 45, Barcelona, 08001',
|
|
description: 'Panadería artesanal especializada en panes tradicionales catalanes.',
|
|
settings: {
|
|
timezone: 'Europe/Madrid',
|
|
currency: 'EUR',
|
|
language: 'ca',
|
|
},
|
|
permissions: ['inventory:*', 'production:*', 'sales:*', 'reports:read'],
|
|
},
|
|
{
|
|
id: '3',
|
|
name: 'Pan y Masa',
|
|
logo: 'https://images.unsplash.com/photo-1524704654690-b56c05c78a00?w=100&h=100&fit=crop&crop=center',
|
|
role: 'baker',
|
|
status: 'active',
|
|
address: 'Plaza Central 8, Valencia, 46001',
|
|
description: 'Panadería moderna con enfoque en productos orgánicos.',
|
|
settings: {
|
|
timezone: 'Europe/Madrid',
|
|
currency: 'EUR',
|
|
language: 'es',
|
|
},
|
|
permissions: ['production:*', 'inventory:read', 'inventory:update'],
|
|
},
|
|
{
|
|
id: '4',
|
|
name: 'Horno Dorado',
|
|
role: 'staff',
|
|
status: 'inactive',
|
|
address: 'Calle del Sol 67, Sevilla, 41001',
|
|
description: 'Panadería tradicional andaluza.',
|
|
settings: {
|
|
timezone: 'Europe/Madrid',
|
|
currency: 'EUR',
|
|
language: 'es',
|
|
},
|
|
permissions: ['inventory:read', 'sales:read'],
|
|
},
|
|
];
|
|
|
|
function bakeryReducer(state: BakeryState, action: BakeryAction): BakeryState {
|
|
switch (action.type) {
|
|
case 'SET_LOADING':
|
|
return { ...state, isLoading: action.payload };
|
|
|
|
case 'SET_ERROR':
|
|
return { ...state, error: action.payload, isLoading: false };
|
|
|
|
case 'SET_BAKERIES':
|
|
return {
|
|
...state,
|
|
bakeries: action.payload,
|
|
currentBakery: state.currentBakery || action.payload[0] || null,
|
|
isLoading: false,
|
|
error: null
|
|
};
|
|
|
|
case 'SET_CURRENT_BAKERY':
|
|
// Save to localStorage for persistence
|
|
localStorage.setItem('selectedBakery', action.payload.id);
|
|
return { ...state, currentBakery: action.payload };
|
|
|
|
case 'ADD_BAKERY':
|
|
return {
|
|
...state,
|
|
bakeries: [...state.bakeries, action.payload]
|
|
};
|
|
|
|
case 'UPDATE_BAKERY':
|
|
return {
|
|
...state,
|
|
bakeries: state.bakeries.map(bakery =>
|
|
bakery.id === action.payload.id
|
|
? { ...bakery, ...action.payload.updates }
|
|
: bakery
|
|
),
|
|
currentBakery: state.currentBakery?.id === action.payload.id
|
|
? { ...state.currentBakery, ...action.payload.updates }
|
|
: state.currentBakery
|
|
};
|
|
|
|
case 'REMOVE_BAKERY':
|
|
const remainingBakeries = state.bakeries.filter(b => b.id !== action.payload);
|
|
return {
|
|
...state,
|
|
bakeries: remainingBakeries,
|
|
currentBakery: state.currentBakery?.id === action.payload
|
|
? remainingBakeries[0] || null
|
|
: state.currentBakery
|
|
};
|
|
|
|
default:
|
|
return state;
|
|
}
|
|
}
|
|
|
|
interface BakeryContextType extends BakeryState {
|
|
selectBakery: (bakery: Bakery) => void;
|
|
addBakery: (bakery: Omit<Bakery, 'id'>) => Promise<void>;
|
|
updateBakery: (id: string, updates: Partial<Bakery>) => Promise<void>;
|
|
removeBakery: (id: string) => Promise<void>;
|
|
refreshBakeries: () => Promise<void>;
|
|
hasPermission: (permission: string) => boolean;
|
|
canAccess: (resource: string, action: string) => boolean;
|
|
}
|
|
|
|
const BakeryContext = createContext<BakeryContextType | undefined>(undefined);
|
|
|
|
interface BakeryProviderProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
export function BakeryProvider({ children }: BakeryProviderProps) {
|
|
const [state, dispatch] = useReducer(bakeryReducer, initialState);
|
|
|
|
// Load bakeries on mount
|
|
useEffect(() => {
|
|
loadBakeries();
|
|
}, []);
|
|
|
|
// Load saved bakery selection
|
|
useEffect(() => {
|
|
if (state.bakeries.length > 0 && !state.currentBakery) {
|
|
const savedBakeryId = localStorage.getItem('selectedBakery');
|
|
const savedBakery = savedBakeryId
|
|
? state.bakeries.find(b => b.id === savedBakeryId)
|
|
: null;
|
|
|
|
if (savedBakery) {
|
|
dispatch({ type: 'SET_CURRENT_BAKERY', payload: savedBakery });
|
|
} else if (state.bakeries[0]) {
|
|
dispatch({ type: 'SET_CURRENT_BAKERY', payload: state.bakeries[0] });
|
|
}
|
|
}
|
|
}, [state.bakeries, state.currentBakery]);
|
|
|
|
const loadBakeries = async () => {
|
|
try {
|
|
dispatch({ type: 'SET_LOADING', payload: true });
|
|
|
|
// Simulate API call delay
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
// In a real app, this would be an API call
|
|
dispatch({ type: 'SET_BAKERIES', payload: mockBakeries });
|
|
} catch (error) {
|
|
dispatch({
|
|
type: 'SET_ERROR',
|
|
payload: error instanceof Error ? error.message : 'Error loading bakeries'
|
|
});
|
|
}
|
|
};
|
|
|
|
const selectBakery = (bakery: Bakery) => {
|
|
dispatch({ type: 'SET_CURRENT_BAKERY', payload: bakery });
|
|
};
|
|
|
|
const addBakery = async (bakeryData: Omit<Bakery, 'id'>) => {
|
|
try {
|
|
dispatch({ type: 'SET_LOADING', payload: true });
|
|
|
|
// Simulate API call
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
const newBakery: Bakery = {
|
|
...bakeryData,
|
|
id: Date.now().toString(), // In real app, this would come from the API
|
|
};
|
|
|
|
dispatch({ type: 'ADD_BAKERY', payload: newBakery });
|
|
dispatch({ type: 'SET_LOADING', payload: false });
|
|
} catch (error) {
|
|
dispatch({
|
|
type: 'SET_ERROR',
|
|
payload: error instanceof Error ? error.message : 'Error adding bakery'
|
|
});
|
|
}
|
|
};
|
|
|
|
const updateBakery = async (id: string, updates: Partial<Bakery>) => {
|
|
try {
|
|
dispatch({ type: 'SET_LOADING', payload: true });
|
|
|
|
// Simulate API call
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
dispatch({ type: 'UPDATE_BAKERY', payload: { id, updates } });
|
|
dispatch({ type: 'SET_LOADING', payload: false });
|
|
} catch (error) {
|
|
dispatch({
|
|
type: 'SET_ERROR',
|
|
payload: error instanceof Error ? error.message : 'Error updating bakery'
|
|
});
|
|
}
|
|
};
|
|
|
|
const removeBakery = async (id: string) => {
|
|
try {
|
|
dispatch({ type: 'SET_LOADING', payload: true });
|
|
|
|
// Simulate API call
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
dispatch({ type: 'REMOVE_BAKERY', payload: id });
|
|
dispatch({ type: 'SET_LOADING', payload: false });
|
|
} catch (error) {
|
|
dispatch({
|
|
type: 'SET_ERROR',
|
|
payload: error instanceof Error ? error.message : 'Error removing bakery'
|
|
});
|
|
}
|
|
};
|
|
|
|
const refreshBakeries = async () => {
|
|
await loadBakeries();
|
|
};
|
|
|
|
const hasPermission = (permission: string): boolean => {
|
|
if (!state.currentBakery) return false;
|
|
|
|
const permissions = state.currentBakery.permissions || [];
|
|
|
|
// Admin/owner has all permissions
|
|
if (permissions.includes('*')) return true;
|
|
|
|
return permissions.includes(permission);
|
|
};
|
|
|
|
const canAccess = (resource: string, action: string): boolean => {
|
|
if (!state.currentBakery) return false;
|
|
|
|
// Check specific permission
|
|
if (hasPermission(`${resource}:${action}`)) return true;
|
|
|
|
// Check wildcard permissions
|
|
if (hasPermission(`${resource}:*`)) return true;
|
|
|
|
// Role-based access fallback
|
|
switch (state.currentBakery.role) {
|
|
case 'owner':
|
|
return true;
|
|
case 'manager':
|
|
return ['inventory', 'production', 'sales', 'reports'].includes(resource);
|
|
case 'baker':
|
|
return ['production', 'inventory'].includes(resource) &&
|
|
['read', 'update'].includes(action);
|
|
case 'staff':
|
|
return ['inventory', 'sales'].includes(resource) &&
|
|
action === 'read';
|
|
default:
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const value: BakeryContextType = {
|
|
...state,
|
|
selectBakery,
|
|
addBakery,
|
|
updateBakery,
|
|
removeBakery,
|
|
refreshBakeries,
|
|
hasPermission,
|
|
canAccess,
|
|
};
|
|
|
|
return (
|
|
<BakeryContext.Provider value={value}>
|
|
{children}
|
|
</BakeryContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useBakery(): BakeryContextType {
|
|
const context = useContext(BakeryContext);
|
|
if (context === undefined) {
|
|
throw new Error('useBakery must be used within a BakeryProvider');
|
|
}
|
|
return context;
|
|
}
|
|
|
|
// Convenience hooks
|
|
export const useCurrentBakery = () => {
|
|
const { currentBakery } = useBakery();
|
|
return currentBakery;
|
|
};
|
|
|
|
export const useBakeries = () => {
|
|
const { bakeries } = useBakery();
|
|
return bakeries;
|
|
};
|
|
|
|
export const useBakeryPermissions = () => {
|
|
const { hasPermission, canAccess } = useBakery();
|
|
return { hasPermission, canAccess };
|
|
}; |