diff --git a/client/src/components/NewSaleForm.tsx b/client/src/components/NewSaleForm.tsx index 0b81921..f470698 100644 --- a/client/src/components/NewSaleForm.tsx +++ b/client/src/components/NewSaleForm.tsx @@ -8,6 +8,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { useToast } from '@/hooks/use-toast'; +import { useAuth } from '@/hooks/useAuth'; +import { useStoreContext } from '@/hooks/useStoreContext'; import { apiRequest } from '@/lib/queryClient'; import { validateRequiredFields, validateEAN13, ARTICLE_CATEGORY_MAPPING, IDENTITY_TYPES } from '@/lib/validation'; import { Package, User, Users, Save, X } from 'lucide-react'; @@ -32,6 +34,8 @@ interface FormData { export default function NewSaleForm() { const { toast } = useToast(); const queryClient = useQueryClient(); + const { user } = useAuth(); + const { selectedStoreId } = useStoreContext(); const [isSubmitting, setIsSubmitting] = useState(false); const form = useForm({ @@ -58,9 +62,15 @@ export default function NewSaleForm() { const createSaleMutation = useMutation({ mutationFn: async (saleData: any) => { + const dataToSend = { + ...saleData, + // Include storeId for admin users + ...(user?.role === 'admin' && selectedStoreId && { storeId: selectedStoreId }) + }; + const response = await apiRequest('/api/sales', { method: 'POST', - body: JSON.stringify(saleData), + body: JSON.stringify(dataToSend), }); return response.json(); }, diff --git a/client/src/components/SalesHistory.tsx b/client/src/components/SalesHistory.tsx index edbf60d..912b45f 100644 --- a/client/src/components/SalesHistory.tsx +++ b/client/src/components/SalesHistory.tsx @@ -6,6 +6,8 @@ import { Input } from '@/components/ui/input'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { useToast } from '@/hooks/use-toast'; +import { useStoreContext } from '@/hooks/useStoreContext'; +import { useAuth } from '@/hooks/useAuth'; import { apiRequest } from '@/lib/queryClient'; import type { Sale } from '@shared/schema'; import { History, Download, Trash2, Eye, Filter } from 'lucide-react'; @@ -17,21 +19,30 @@ interface SalesHistoryProps { export default function SalesHistory({ canDelete = false }: SalesHistoryProps) { const { toast } = useToast(); const queryClient = useQueryClient(); + const { user } = useAuth(); + const { selectedStoreId } = useStoreContext(); const [selectedSale, setSelectedSale] = useState(null); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); // Query for sales const { data: salesData, isLoading } = useQuery({ - queryKey: ['/api/sales', startDate, endDate], + queryKey: ['/api/sales', startDate, endDate, selectedStoreId], queryFn: async () => { const params = new URLSearchParams(); if (startDate) params.append('startDate', startDate); if (endDate) params.append('endDate', endDate); + + // Add storeId for admin users + if (user?.role === 'admin' && selectedStoreId) { + params.append('storeId', selectedStoreId.toString()); + } + const queryString = params.toString(); const response = await apiRequest(`/api/sales${queryString ? `?${queryString}` : ''}`); return response.json(); }, + enabled: !!selectedStoreId, // Only run query when store is selected }); const sales = Array.isArray(salesData) ? salesData : []; diff --git a/client/src/hooks/useStoreContext.tsx b/client/src/hooks/useStoreContext.tsx new file mode 100644 index 0000000..849cbc2 --- /dev/null +++ b/client/src/hooks/useStoreContext.tsx @@ -0,0 +1,70 @@ +import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { apiRequest } from '@/lib/queryClient'; +import type { Store } from '@shared/schema'; + +interface StoreContextType { + selectedStoreId: number | null; + setSelectedStoreId: (storeId: number | null) => void; + stores: Store[]; + selectedStore: Store | null; + isLoadingStores: boolean; +} + +const StoreContext = createContext(undefined); + +interface StoreProviderProps { + children: ReactNode; + userStoreId?: number | null; + isAdmin?: boolean; +} + +export function StoreProvider({ children, userStoreId, isAdmin }: StoreProviderProps) { + const [selectedStoreId, setSelectedStoreId] = useState(null); + + // Query all stores for admin, or just get user's store for non-admin + const { data: stores = [], isLoading: isLoadingStores } = useQuery({ + queryKey: isAdmin ? ['/api/admin/stores'] : ['/api/stores'], + queryFn: async () => { + const endpoint = isAdmin ? '/api/admin/stores' : '/api/stores'; + const response = await apiRequest(endpoint); + return response.json(); + }, + enabled: !!isAdmin || !!userStoreId, + }); + + // Set initial selected store + useEffect(() => { + if (stores.length > 0 && selectedStoreId === null) { + if (isAdmin) { + // Admin: select first store by default + setSelectedStoreId(stores[0].id); + } else if (userStoreId) { + // Non-admin: select user's store + setSelectedStoreId(userStoreId); + } + } + }, [stores, selectedStoreId, isAdmin, userStoreId]); + + const selectedStore = stores.find(store => store.id === selectedStoreId) || null; + + return ( + + {children} + + ); +} + +export function useStoreContext() { + const context = useContext(StoreContext); + if (context === undefined) { + throw new Error('useStoreContext must be used within a StoreProvider'); + } + return context; +} \ No newline at end of file diff --git a/client/src/pages/Dashboard.tsx b/client/src/pages/Dashboard.tsx index 4c644df..6e0495e 100644 --- a/client/src/pages/Dashboard.tsx +++ b/client/src/pages/Dashboard.tsx @@ -1,18 +1,21 @@ import { useState } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useAuth } from "@/hooks/useAuth"; +import { StoreProvider, useStoreContext } from "@/hooks/useStoreContext"; import NewSaleForm from "@/components/NewSaleForm"; import SalesHistory from "@/components/SalesHistory"; import Administration from "@/components/Administration"; -import { Package, History, Settings, LogOut, User } from "lucide-react"; +import { Package, History, Settings, LogOut, User, Building2 } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; -export default function Dashboard() { +function DashboardContent() { const { user, logout } = useAuth(); const { toast } = useToast(); const [activeTab, setActiveTab] = useState("new-sale"); + const { selectedStoreId, setSelectedStoreId, stores, selectedStore, isLoadingStores } = useStoreContext(); const handleLogout = async () => { try { @@ -32,6 +35,7 @@ export default function Dashboard() { const canAccessAdmin = user?.role === "admin"; const canDeleteSales = user?.role === "admin" || user?.role === "manager"; + const isAdmin = user?.role === "admin"; return (
@@ -50,6 +54,36 @@ export default function Dashboard() {
+ {/* Store Selector for Admin */} + {isAdmin && !isLoadingStores && ( +
+ + +
+ )} + + {/* Current Store Display for Non-Admin */} + {!isAdmin && selectedStore && ( +
+ + {selectedStore.name} +
+ )} +

{user?.firstName} {user?.lastName} @@ -67,6 +101,30 @@ export default function Dashboard() { {/* Main Content */}

+ {selectedStore && ( +
+ + +
+
+ +
+
+

{selectedStore.name}

+
+ {selectedStore.address &&

{selectedStore.address}

} +
+ {selectedStore.phone && 📞 {selectedStore.phone}} + {selectedStore.email && ✉️ {selectedStore.email}} +
+
+
+
+
+
+
+ )} + @@ -102,4 +160,17 @@ export default function Dashboard() {
); +} + +export default function Dashboard() { + const { user } = useAuth(); + + return ( + + + + ); } \ No newline at end of file diff --git a/server/routes.ts b/server/routes.ts index e15bd67..f46dad9 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -117,6 +117,26 @@ export async function registerRoutes(app: Express): Promise { } }); + // Store routes for users + app.get('/api/stores', requireAuth, async (req, res) => { + try { + const user = await storage.getUser(req.session.userId!); + if (!user || !user.storeId) { + return res.status(404).json({ error: "User store not found" }); + } + + const store = await storage.getStore(user.storeId); + if (!store) { + return res.status(404).json({ error: "Store not found" }); + } + + res.json([store]); // Return as array to match admin endpoint format + } catch (error) { + console.error('Get user stores error:', error); + res.status(500).json({ error: "Failed to get stores" }); + } + }); + // Sales routes app.get('/api/sales', requireAuth, async (req, res) => { try { @@ -125,9 +145,23 @@ export async function registerRoutes(app: Express): Promise { return res.status(404).json({ error: "User not found" }); } - const { startDate, endDate } = req.query; + const { startDate, endDate, storeId } = req.query; + + // Determine which store to query + let targetStoreId: number; + if (user.role === 'admin' && storeId) { + // Admin can query any store + targetStoreId = parseInt(storeId as string); + } else { + // Non-admin users can only query their own store + if (!user.storeId) { + return res.status(400).json({ error: "User has no assigned store" }); + } + targetStoreId = user.storeId; + } + const sales = await storage.getSalesByStore( - user.storeId!, + targetStoreId, startDate as string, endDate as string ); @@ -145,9 +179,22 @@ export async function registerRoutes(app: Express): Promise { return res.status(404).json({ error: "User not found" }); } + // Determine which store to use for the sale + let targetStoreId: number; + if (user.role === 'admin' && req.body.storeId) { + // Admin can create sales for any store + targetStoreId = req.body.storeId; + } else { + // Non-admin users can only create sales for their assigned store + if (!user.storeId) { + return res.status(400).json({ error: "User has no assigned store" }); + } + targetStoreId = user.storeId; + } + const saleData = insertSaleSchema.parse({ ...req.body, - storeId: user.storeId, + storeId: targetStoreId, userId: user.id });