Files
delivery-tracker/frontend/src/stores/deliveryStore.ts
Egor Pozharov d1efebbb34
Some checks failed
Build and Push Docker Images / build-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (push) Has been cancelled
add WebSocket support for real-time delivery updates with JWT authentication and automatic reconnection
2026-05-21 15:52:05 +06:00

224 lines
6.8 KiB
TypeScript

import { create } from 'zustand';
import { deliveriesApi } from '../api';
import { useToastStore } from './toastStore';
import type { Delivery, DeliveryStatus } from '../types';
interface DeliveryState {
// Data
deliveries: Delivery[];
deliveryCounts: Record<string, number>;
currentDate: string | null;
// Loading states
isLoading: boolean;
isLoadingCounts: boolean;
error: string | null;
// Actions
fetchDeliveriesByDate: (date: string) => Promise<void>;
fetchDeliveryCounts: () => Promise<void>;
addDelivery: (delivery: Omit<Delivery, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>;
updateDelivery: (id: string, updates: Omit<Delivery, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>;
deleteDelivery: (id: string) => Promise<void>;
toggleStatus: (id: string, currentStatus: DeliveryStatus) => Promise<void>;
getDeliveriesByDate: (date: string) => Delivery[];
getDeliveriesByDateRange: (startDate: string, endDate: string) => Delivery[];
getDeliveryCountsByDate: () => Record<string, number>;
clearError: () => void;
// WebSocket event handlers
handleWsDeliveryCreated: (delivery: Delivery) => void;
handleWsDeliveryUpdated: (delivery: Delivery) => void;
handleWsStatusChanged: (id: string, status: string) => void;
handleWsDeliveryDeleted: (id: string) => void;
}
export const useDeliveryStore = create<DeliveryState>()((set, get) => ({
// Initial state
deliveries: [],
deliveryCounts: {},
currentDate: null,
isLoading: false,
isLoadingCounts: false,
error: null,
// Fetch deliveries for a specific date
fetchDeliveriesByDate: async (date: string) => {
set({ isLoading: true, error: null, currentDate: date });
try {
const deliveries = await deliveriesApi.getByDate(date);
set({ deliveries, isLoading: false });
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to fetch deliveries';
set({
error: message,
isLoading: false,
});
useToastStore.getState().addToast(message, 'error');
}
},
// Fetch delivery counts for calendar
fetchDeliveryCounts: async () => {
set({ isLoadingCounts: true, error: null });
try {
const counts = await deliveriesApi.getCounts();
set({ deliveryCounts: counts, isLoadingCounts: false });
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to fetch counts';
set({
error: message,
isLoadingCounts: false,
});
useToastStore.getState().addToast(message, 'error');
}
},
// Add new delivery
addDelivery: async (delivery) => {
set({ isLoading: true, error: null });
try {
await deliveriesApi.create(delivery);
// Refresh deliveries for that date
await get().fetchDeliveriesByDate(delivery.date);
// Refresh counts
await get().fetchDeliveryCounts();
set({ isLoading: false });
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to create delivery';
set({
error: message,
isLoading: false,
});
useToastStore.getState().addToast(message, 'error');
throw err;
}
},
// Update delivery
updateDelivery: async (id, updates) => {
set({ isLoading: true, error: null });
try {
await deliveriesApi.update(id, updates);
// Refresh deliveries for that date
await get().fetchDeliveriesByDate(updates.date);
// Refresh counts (in case date changed)
await get().fetchDeliveryCounts();
set({ isLoading: false });
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to update delivery';
set({
error: message,
isLoading: false,
});
useToastStore.getState().addToast(message, 'error');
throw err;
}
},
// Delete delivery
deleteDelivery: async (id) => {
set({ isLoading: true, error: null });
try {
await deliveriesApi.delete(id);
// Remove from local state
set((state) => ({
deliveries: state.deliveries.filter((d) => d.id !== id),
isLoading: false,
}));
// Refresh counts
await get().fetchDeliveryCounts();
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to delete delivery';
set({
error: message,
isLoading: false,
});
useToastStore.getState().addToast(message, 'error');
throw err;
}
},
// Toggle delivery status
toggleStatus: async (id, currentStatus) => {
set({ isLoading: true, error: null });
try {
const newStatus = currentStatus === 'new' ? 'delivered' : 'new';
await deliveriesApi.updateStatus(id, newStatus);
// Update local state
set((state) => ({
deliveries: state.deliveries.map((d) =>
d.id === id
? { ...d, status: newStatus, updatedAt: Date.now() }
: d
),
isLoading: false,
}));
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to update status';
set({
error: message,
isLoading: false,
});
useToastStore.getState().addToast(message, 'error');
throw err;
}
},
// Getters (local filtering)
getDeliveriesByDate: (date) => {
return get().deliveries.filter((d) => d.date === date);
},
getDeliveriesByDateRange: (startDate, endDate) => {
return get().deliveries.filter((d) => {
const date = d.date;
return date >= startDate && date <= endDate;
});
},
getDeliveryCountsByDate: () => {
return get().deliveryCounts;
},
clearError: () => set({ error: null }),
// WebSocket event handlers (update local state without refetching)
handleWsDeliveryCreated: (delivery: Delivery) => {
const { currentDate, deliveries } = get();
if (currentDate && delivery.date === currentDate) {
if (!deliveries.some(d => d.id === delivery.id)) {
set({ deliveries: [...deliveries, delivery] });
}
}
},
handleWsDeliveryUpdated: (delivery: Delivery) => {
const { currentDate, deliveries } = get();
const exists = deliveries.some(d => d.id === delivery.id);
if (exists) {
if (delivery.date === currentDate) {
set({ deliveries: deliveries.map(d => d.id === delivery.id ? delivery : d) });
} else {
set({ deliveries: deliveries.filter(d => d.id !== delivery.id) });
}
} else if (currentDate && delivery.date === currentDate) {
set({ deliveries: [...deliveries, delivery] });
}
},
handleWsStatusChanged: (id: string, status: string) => {
set(state => ({
deliveries: state.deliveries.map(d =>
d.id === id ? { ...d, status: status as DeliveryStatus, updatedAt: Date.now() } : d
),
}));
},
handleWsDeliveryDeleted: (id: string) => {
set(state => ({
deliveries: state.deliveries.filter(d => d.id !== id),
}));
},
}));