18 KiB
18 KiB
Zustand Stores Specification
Version: 1.0.0 Fecha: 2025-12-05
Global Stores
AuthStore
interface AuthState {
// State
user: User | null;
token: string | null;
isAuthenticated: boolean;
isLoading: boolean;
// Actions
login: (credentials: LoginDto) => Promise<void>;
logout: () => void;
refreshToken: () => Promise<void>;
updateUser: (data: Partial<User>) => void;
}
export const useAuthStore = create<AuthState>()(
devtools(
persist(
(set, get) => ({
user: null,
token: null,
isAuthenticated: false,
isLoading: false,
login: async (credentials) => {
set({ isLoading: true });
try {
const response = await authApi.login(credentials);
set({
user: response.user,
token: response.token,
isAuthenticated: true,
isLoading: false
});
} catch (error) {
set({ isLoading: false });
throw error;
}
},
logout: () => {
set({
user: null,
token: null,
isAuthenticated: false
});
},
refreshToken: async () => {
const response = await authApi.refresh();
set({ token: response.token });
},
updateUser: (data) => {
set((state) => ({
user: state.user ? { ...state.user, ...data } : null
}));
}
}),
{
name: 'auth-store',
partialize: (state) => ({
token: state.token,
user: state.user
})
}
)
)
);
TenantStore
interface TenantState {
// State
currentTenant: Tenant | null;
availableTenants: Tenant[];
// Actions
setCurrentTenant: (tenant: Tenant) => void;
setAvailableTenants: (tenants: Tenant[]) => void;
switchTenant: (tenantId: string) => Promise<void>;
}
export const useTenantStore = create<TenantState>()(
devtools(
persist(
(set, get) => ({
currentTenant: null,
availableTenants: [],
setCurrentTenant: (tenant) => set({ currentTenant: tenant }),
setAvailableTenants: (tenants) => set({ availableTenants: tenants }),
switchTenant: async (tenantId) => {
const tenant = get().availableTenants.find(t => t.id === tenantId);
if (tenant) {
set({ currentTenant: tenant });
// Reload data for new tenant
window.location.reload();
}
}
}),
{
name: 'tenant-store'
}
)
)
);
UIStore
interface UIState {
// Sidebar
sidebarCollapsed: boolean;
sidebarPinned: boolean;
// Theme
theme: 'light' | 'dark' | 'system';
primaryColor: string;
// Modal state
activeModal: string | null;
modalData: any;
// Actions
toggleSidebar: () => void;
setSidebarCollapsed: (collapsed: boolean) => void;
setSidebarPinned: (pinned: boolean) => void;
setTheme: (theme: 'light' | 'dark' | 'system') => void;
openModal: (name: string, data?: any) => void;
closeModal: () => void;
}
export const useUIStore = create<UIState>()(
devtools(
persist(
(set) => ({
sidebarCollapsed: false,
sidebarPinned: true,
theme: 'system',
primaryColor: '#3b82f6',
activeModal: null,
modalData: null,
toggleSidebar: () =>
set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),
setSidebarCollapsed: (collapsed) =>
set({ sidebarCollapsed: collapsed }),
setSidebarPinned: (pinned) =>
set({ sidebarPinned: pinned }),
setTheme: (theme) =>
set({ theme }),
openModal: (name, data) =>
set({ activeModal: name, modalData: data }),
closeModal: () =>
set({ activeModal: null, modalData: null })
}),
{
name: 'ui-store',
partialize: (state) => ({
sidebarCollapsed: state.sidebarCollapsed,
sidebarPinned: state.sidebarPinned,
theme: state.theme,
primaryColor: state.primaryColor
})
}
)
)
);
NotificationStore
interface Notification {
id: string;
type: 'info' | 'success' | 'warning' | 'error';
title: string;
message?: string;
duration?: number;
action?: {
label: string;
onClick: () => void;
};
}
interface NotificationState {
notifications: Notification[];
unreadCount: number;
addNotification: (notification: Omit<Notification, 'id'>) => void;
removeNotification: (id: string) => void;
clearAll: () => void;
markAllAsRead: () => void;
}
export const useNotificationStore = create<NotificationState>()(
devtools((set) => ({
notifications: [],
unreadCount: 0,
addNotification: (notification) =>
set((state) => ({
notifications: [
...state.notifications,
{ ...notification, id: crypto.randomUUID() }
],
unreadCount: state.unreadCount + 1
})),
removeNotification: (id) =>
set((state) => ({
notifications: state.notifications.filter((n) => n.id !== id)
})),
clearAll: () =>
set({ notifications: [], unreadCount: 0 }),
markAllAsRead: () =>
set({ unreadCount: 0 })
}))
);
Feature Stores
ConstructionStore
interface ConstructionState {
// Filters
projectFilters: ProjectFilters;
developmentFilters: DevelopmentFilters;
// View preferences
projectViewMode: 'list' | 'grid' | 'kanban';
budgetViewMode: 'table' | 'chart';
ganttViewMode: 'day' | 'week' | 'month';
// Selected items
selectedProjectId: string | null;
selectedDevelopmentId: string | null;
// Actions
setProjectFilters: (filters: Partial<ProjectFilters>) => void;
resetProjectFilters: () => void;
setDevelopmentFilters: (filters: Partial<DevelopmentFilters>) => void;
setProjectViewMode: (mode: 'list' | 'grid' | 'kanban') => void;
setBudgetViewMode: (mode: 'table' | 'chart') => void;
setGanttViewMode: (mode: 'day' | 'week' | 'month') => void;
setSelectedProject: (id: string | null) => void;
setSelectedDevelopment: (id: string | null) => void;
}
const initialProjectFilters: ProjectFilters = {
status: undefined,
search: '',
clientId: undefined,
managerId: undefined,
dateRange: undefined,
page: 1,
limit: 20
};
export const useConstructionStore = create<ConstructionState>()(
devtools(
persist(
(set) => ({
projectFilters: initialProjectFilters,
developmentFilters: {},
projectViewMode: 'list',
budgetViewMode: 'table',
ganttViewMode: 'week',
selectedProjectId: null,
selectedDevelopmentId: null,
setProjectFilters: (filters) =>
set((state) => ({
projectFilters: { ...state.projectFilters, ...filters }
})),
resetProjectFilters: () =>
set({ projectFilters: initialProjectFilters }),
setDevelopmentFilters: (filters) =>
set((state) => ({
developmentFilters: { ...state.developmentFilters, ...filters }
})),
setProjectViewMode: (mode) =>
set({ projectViewMode: mode }),
setBudgetViewMode: (mode) =>
set({ budgetViewMode: mode }),
setGanttViewMode: (mode) =>
set({ ganttViewMode: mode }),
setSelectedProject: (id) =>
set({ selectedProjectId: id }),
setSelectedDevelopment: (id) =>
set({ selectedDevelopmentId: id })
}),
{
name: 'construction-store',
partialize: (state) => ({
projectViewMode: state.projectViewMode,
budgetViewMode: state.budgetViewMode,
ganttViewMode: state.ganttViewMode
})
}
)
)
);
ComplianceStore
interface ComplianceState {
// Filters
complianceFilters: ComplianceFilters;
auditFilters: AuditFilters;
// View
dashboardView: 'summary' | 'detailed';
// Selected
selectedProgramId: string | null;
selectedAuditId: string | null;
// Actions
setComplianceFilters: (filters: Partial<ComplianceFilters>) => void;
setAuditFilters: (filters: Partial<AuditFilters>) => void;
setDashboardView: (view: 'summary' | 'detailed') => void;
setSelectedProgram: (id: string | null) => void;
setSelectedAudit: (id: string | null) => void;
}
export const useComplianceStore = create<ComplianceState>()(
devtools(
persist(
(set) => ({
complianceFilters: {},
auditFilters: {},
dashboardView: 'summary',
selectedProgramId: null,
selectedAuditId: null,
setComplianceFilters: (filters) =>
set((state) => ({
complianceFilters: { ...state.complianceFilters, ...filters }
})),
setAuditFilters: (filters) =>
set((state) => ({
auditFilters: { ...state.auditFilters, ...filters }
})),
setDashboardView: (view) =>
set({ dashboardView: view }),
setSelectedProgram: (id) =>
set({ selectedProgramId: id }),
setSelectedAudit: (id) =>
set({ selectedAuditId: id })
}),
{
name: 'compliance-store'
}
)
)
);
FinanceStore
interface FinanceState {
// Filters
transactionFilters: TransactionFilters;
reportFilters: ReportFilters;
// View
dashboardPeriod: 'month' | 'quarter' | 'year';
chartType: 'bar' | 'line' | 'area';
// Selected
selectedAccountId: string | null;
// Actions
setTransactionFilters: (filters: Partial<TransactionFilters>) => void;
setReportFilters: (filters: Partial<ReportFilters>) => void;
setDashboardPeriod: (period: 'month' | 'quarter' | 'year') => void;
setChartType: (type: 'bar' | 'line' | 'area') => void;
setSelectedAccount: (id: string | null) => void;
}
export const useFinanceStore = create<FinanceState>()(
devtools(
persist(
(set) => ({
transactionFilters: {},
reportFilters: {},
dashboardPeriod: 'month',
chartType: 'bar',
selectedAccountId: null,
setTransactionFilters: (filters) =>
set((state) => ({
transactionFilters: { ...state.transactionFilters, ...filters }
})),
setReportFilters: (filters) =>
set((state) => ({
reportFilters: { ...state.reportFilters, ...filters }
})),
setDashboardPeriod: (period) =>
set({ dashboardPeriod: period }),
setChartType: (type) =>
set({ chartType: type }),
setSelectedAccount: (id) =>
set({ selectedAccountId: id })
}),
{
name: 'finance-store'
}
)
)
);
AssetsStore
interface AssetsState {
// Filters
assetFilters: AssetFilters;
maintenanceFilters: MaintenanceFilters;
workOrderFilters: WorkOrderFilters;
// View
assetViewMode: 'list' | 'grid';
mapCenter: LatLng | null;
mapZoom: number;
// Selected
selectedAssetId: string | null;
// Actions
setAssetFilters: (filters: Partial<AssetFilters>) => void;
setMaintenanceFilters: (filters: Partial<MaintenanceFilters>) => void;
setWorkOrderFilters: (filters: Partial<WorkOrderFilters>) => void;
setAssetViewMode: (mode: 'list' | 'grid') => void;
setMapCenter: (center: LatLng) => void;
setMapZoom: (zoom: number) => void;
setSelectedAsset: (id: string | null) => void;
}
export const useAssetsStore = create<AssetsState>()(
devtools(
persist(
(set) => ({
assetFilters: {},
maintenanceFilters: {},
workOrderFilters: {},
assetViewMode: 'list',
mapCenter: null,
mapZoom: 12,
selectedAssetId: null,
setAssetFilters: (filters) =>
set((state) => ({
assetFilters: { ...state.assetFilters, ...filters }
})),
setMaintenanceFilters: (filters) =>
set((state) => ({
maintenanceFilters: { ...state.maintenanceFilters, ...filters }
})),
setWorkOrderFilters: (filters) =>
set((state) => ({
workOrderFilters: { ...state.workOrderFilters, ...filters }
})),
setAssetViewMode: (mode) =>
set({ assetViewMode: mode }),
setMapCenter: (center) =>
set({ mapCenter: center }),
setMapZoom: (zoom) =>
set({ mapZoom: zoom }),
setSelectedAsset: (id) =>
set({ selectedAssetId: id })
}),
{
name: 'assets-store'
}
)
)
);
DocumentsStore
interface DocumentsState {
// Navigation
currentFolderId: string | null;
folderPath: Folder[];
// Filters
documentFilters: DocumentFilters;
// View
viewMode: 'list' | 'grid';
sortBy: 'name' | 'date' | 'size';
sortOrder: 'asc' | 'desc';
// Selection
selectedDocuments: string[];
// Upload
uploadQueue: UploadItem[];
// Actions
setCurrentFolder: (id: string | null) => void;
setFolderPath: (path: Folder[]) => void;
navigateToFolder: (folder: Folder) => void;
navigateUp: () => void;
setDocumentFilters: (filters: Partial<DocumentFilters>) => void;
setViewMode: (mode: 'list' | 'grid') => void;
setSortBy: (sortBy: 'name' | 'date' | 'size') => void;
setSortOrder: (order: 'asc' | 'desc') => void;
selectDocument: (id: string) => void;
deselectDocument: (id: string) => void;
selectAllDocuments: (ids: string[]) => void;
clearSelection: () => void;
addToUploadQueue: (item: UploadItem) => void;
removeFromUploadQueue: (id: string) => void;
clearUploadQueue: () => void;
}
export const useDocumentsStore = create<DocumentsState>()(
devtools(
persist(
(set, get) => ({
currentFolderId: null,
folderPath: [],
documentFilters: {},
viewMode: 'list',
sortBy: 'name',
sortOrder: 'asc',
selectedDocuments: [],
uploadQueue: [],
setCurrentFolder: (id) =>
set({ currentFolderId: id }),
setFolderPath: (path) =>
set({ folderPath: path }),
navigateToFolder: (folder) => {
const currentPath = get().folderPath;
const index = currentPath.findIndex(f => f.id === folder.id);
if (index >= 0) {
// Navigate to existing folder in path
set({
currentFolderId: folder.id,
folderPath: currentPath.slice(0, index + 1)
});
} else {
// Navigate to new folder
set({
currentFolderId: folder.id,
folderPath: [...currentPath, folder]
});
}
},
navigateUp: () => {
const path = get().folderPath;
if (path.length > 1) {
set({
currentFolderId: path[path.length - 2].id,
folderPath: path.slice(0, -1)
});
} else {
set({
currentFolderId: null,
folderPath: []
});
}
},
setDocumentFilters: (filters) =>
set((state) => ({
documentFilters: { ...state.documentFilters, ...filters }
})),
setViewMode: (mode) =>
set({ viewMode: mode }),
setSortBy: (sortBy) =>
set({ sortBy }),
setSortOrder: (order) =>
set({ sortOrder: order }),
selectDocument: (id) =>
set((state) => ({
selectedDocuments: [...state.selectedDocuments, id]
})),
deselectDocument: (id) =>
set((state) => ({
selectedDocuments: state.selectedDocuments.filter(d => d !== id)
})),
selectAllDocuments: (ids) =>
set({ selectedDocuments: ids }),
clearSelection: () =>
set({ selectedDocuments: [] }),
addToUploadQueue: (item) =>
set((state) => ({
uploadQueue: [...state.uploadQueue, item]
})),
removeFromUploadQueue: (id) =>
set((state) => ({
uploadQueue: state.uploadQueue.filter(i => i.id !== id)
})),
clearUploadQueue: () =>
set({ uploadQueue: [] })
}),
{
name: 'documents-store',
partialize: (state) => ({
viewMode: state.viewMode,
sortBy: state.sortBy,
sortOrder: state.sortOrder
})
}
)
)
);
Store Patterns
Selectors
// Memoized selectors for performance
export const selectActiveProjects = (state: ConstructionState) =>
state.projectFilters.status === 'active';
export const selectFilteredProjects = createSelector(
[(state) => state.projects, (state) => state.projectFilters],
(projects, filters) => {
return projects.filter(p => {
if (filters.status && p.status !== filters.status) return false;
if (filters.search && !p.name.includes(filters.search)) return false;
return true;
});
}
);
Actions with Side Effects
// Using middleware for side effects
const logMiddleware = (config) => (set, get, api) =>
config(
(...args) => {
console.log(' applying', args);
set(...args);
console.log(' new state', get());
},
get,
api
);
Hydration
// Hydrate store from server
export const hydrateStore = async () => {
const userData = await fetchUserData();
useAuthStore.setState({ user: userData });
const tenantData = await fetchTenantData();
useTenantStore.setState({ currentTenant: tenantData });
};
Ultima actualizacion: 2025-12-05