-
Notifications
You must be signed in to change notification settings - Fork 0
Codex-generated pull request #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: cursor/project-codespace-compatibility-b14c
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,23 @@ export default function Reports() { | |
| const [filter, setFilter] = useState<ReportFilter>({}); | ||
| const [exporting, setExporting] = useState(false); | ||
|
|
||
| const unwrapApiData = async <T,>(response: Response): Promise<T> => { | ||
| if (!response.ok) { | ||
| throw new Error(`Request failed (${response.status})`); | ||
| } | ||
|
|
||
| const raw = await response.json(); | ||
| if (raw && typeof raw === "object" && "ok" in raw) { | ||
| const envelope = raw as { ok: boolean; data?: T; error?: { message?: string } }; | ||
| if (envelope.ok) { | ||
| return envelope.data as T; | ||
| } | ||
| throw new Error(envelope.error?.message || "Request failed"); | ||
| } | ||
|
|
||
| return raw as T; | ||
| }; | ||
|
|
||
| // Fetch inventory items (normalize to array so reduce/map never see non-array) | ||
| const { data: inventoryItems, isLoading: itemsLoading, isError: itemsError, error: itemsErrorDetail } = useQuery({ | ||
| queryKey: ["/api/inventory"], | ||
|
|
@@ -50,11 +67,8 @@ export default function Reports() { | |
| const { data: lowStockItems, isLoading: lowStockLoading } = useQuery({ | ||
| queryKey: ["/api/inventory/low-stock"], | ||
| queryFn: async () => { | ||
| const response = await fetch("/api/inventory/low-stock"); | ||
| if (!response.ok) { | ||
| throw new Error("Failed to fetch low stock items"); | ||
| } | ||
| const raw = await response.json(); | ||
| const response = await fetch("/api/inventory/low-stock", { credentials: "include" }); | ||
| const raw = await unwrapApiData<unknown>(response); | ||
| return Array.isArray(raw) ? raw : []; | ||
| }, | ||
| }); | ||
|
|
@@ -63,11 +77,8 @@ export default function Reports() { | |
| const { data: categories } = useQuery({ | ||
| queryKey: ["/api/categories"], | ||
| queryFn: async () => { | ||
| const response = await fetch("/api/categories"); | ||
| if (!response.ok) { | ||
| throw new Error("Failed to fetch categories"); | ||
| } | ||
| const raw = await response.json(); | ||
| const response = await fetch("/api/categories", { credentials: "include" }); | ||
| const raw = await unwrapApiData<unknown>(response); | ||
| return Array.isArray(raw) ? raw : []; | ||
| }, | ||
| }); | ||
|
|
@@ -76,23 +87,23 @@ export default function Reports() { | |
| const { data: stats } = useQuery({ | ||
| queryKey: ["/api/inventory/stats"], | ||
| queryFn: async () => { | ||
| const response = await fetch("/api/inventory/stats"); | ||
| if (!response.ok) { | ||
| throw new Error("Failed to fetch inventory stats"); | ||
| } | ||
| return response.json() as Promise<InventoryStats>; | ||
| const response = await fetch("/api/inventory/stats", { credentials: "include" }); | ||
| const rawStats = await unwrapApiData<Partial<InventoryStats>>(response); | ||
| return { | ||
| totalItems: Number(rawStats?.totalItems ?? 0), | ||
| lowStockItems: Number(rawStats?.lowStockItems ?? 0), | ||
| outOfStockItems: Number(rawStats?.outOfStockItems ?? 0), | ||
| inventoryValue: Number(rawStats?.inventoryValue ?? 0), | ||
| } as InventoryStats; | ||
| }, | ||
| }); | ||
|
|
||
| // Fetch warehouses for filtering (normalize to array) | ||
| const { data: warehouses } = useQuery({ | ||
| queryKey: ["/api/warehouses"], | ||
| queryFn: async () => { | ||
| const response = await fetch("/api/warehouses"); | ||
| if (!response.ok) { | ||
| throw new Error("Failed to fetch warehouses"); | ||
| } | ||
| const raw = await response.json(); | ||
| const response = await fetch("/api/warehouses", { credentials: "include" }); | ||
| const raw = await unwrapApiData<unknown>(response); | ||
| return Array.isArray(raw) ? raw : []; | ||
| }, | ||
| }); | ||
|
|
@@ -101,11 +112,8 @@ export default function Reports() { | |
| const { data: suppliers } = useQuery({ | ||
| queryKey: ["/api/suppliers"], | ||
| queryFn: async () => { | ||
| const response = await fetch("/api/suppliers"); | ||
| if (!response.ok) { | ||
| throw new Error("Failed to fetch suppliers"); | ||
| } | ||
| const raw = await response.json(); | ||
| const response = await fetch("/api/suppliers", { credentials: "include" }); | ||
| const raw = await unwrapApiData<unknown>(response); | ||
| return Array.isArray(raw) ? raw : []; | ||
| }, | ||
| }); | ||
|
|
@@ -308,9 +316,7 @@ export default function Reports() { | |
| </p> | ||
| </div> | ||
| <div className="text-sm text-neutral-600 dark:text-neutral-300"> | ||
| {safeInventoryItems.length > 0 | ||
| ? `${safeInventoryItems.length} items • Total Value: ${formatCurrency(calculateTotalValue(safeInventoryItems))}` | ||
| : `${stats?.totalItems || 0} items • Total Value: ${formatCurrency(stats?.inventoryValue || 0)}`} | ||
| {`${safeInventoryItems.length} items • Total Value: ${formatCurrency(calculateTotalValue(safeInventoryItems))}`} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Report preview lost stats fallback during loadingMedium Severity The report preview header and footer previously fell back to Additional Locations (1) |
||
| </div> | ||
| </div> | ||
| <div className="overflow-x-auto"> | ||
|
|
@@ -348,10 +354,10 @@ export default function Reports() { | |
| {item.quantity} | ||
| </td> | ||
| <td className="px-6 py-4 whitespace-nowrap text-sm text-neutral-600 dark:text-neutral-300"> | ||
| {formatCurrency(item.price)} | ||
| {formatCurrency(Number(item.price) || 0)} | ||
| </td> | ||
| <td className="px-6 py-4 whitespace-nowrap text-sm text-neutral-600 dark:text-neutral-300"> | ||
| {formatCurrency(item.price * item.quantity)} | ||
| {formatCurrency((Number(item.price) || 0) * (Number(item.quantity) || 0))} | ||
| </td> | ||
| </tr> | ||
| )) | ||
|
|
@@ -392,7 +398,7 @@ export default function Reports() { | |
| </CardContent> | ||
| <CardFooter className="bg-neutral-50 dark:bg-neutral-800 border-t border-neutral-200 dark:border-neutral-700 flex justify-between"> | ||
| <div className="text-sm text-neutral-600 dark:text-neutral-300"> | ||
| The complete report will include all {stats?.totalItems || 0} inventory items. | ||
| The complete report includes all {safeInventoryItems.length} inventory items from the current inventory feed. | ||
| </div> | ||
| </CardFooter> | ||
| </Card> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -154,16 +154,35 @@ export default function WarehousesPage() { | |
| }, | ||
| }); | ||
|
|
||
| const handleCreateSubmit = (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
| createWarehouse.mutate(formData); | ||
| const handleCreateSubmit = () => { | ||
| if (!formData.name.trim()) { | ||
| toast({ | ||
| variant: 'destructive', | ||
| title: 'Warehouse name is required', | ||
| description: 'Enter a name before creating a warehouse.', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| createWarehouse.mutate({ ...formData, name: formData.name.trim() }); | ||
| }; | ||
|
|
||
| const handleEditSubmit = (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
| if (selectedWarehouse) { | ||
| updateWarehouse.mutate({ id: selectedWarehouse.id, data: formData }); | ||
| const handleEditSubmit = () => { | ||
| if (!selectedWarehouse) return; | ||
|
|
||
| if (!formData.name.trim()) { | ||
| toast({ | ||
| variant: 'destructive', | ||
| title: 'Warehouse name is required', | ||
| description: 'Enter a name before saving changes.', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| updateWarehouse.mutate({ | ||
| id: selectedWarehouse.id, | ||
| data: { ...formData, name: formData.name.trim() }, | ||
| }); | ||
| }; | ||
|
|
||
| const handleDeleteConfirm = () => { | ||
|
|
@@ -317,7 +336,7 @@ export default function WarehousesPage() { | |
| Enter the details for the new warehouse location. | ||
| </DialogDescription> | ||
| </DialogHeader> | ||
| <form onSubmit={handleCreateSubmit}> | ||
| <form noValidate> | ||
| <div className="grid gap-4 py-4"> | ||
| <div className="grid gap-2"> | ||
| <Label htmlFor="name">Warehouse Name *</Label> | ||
|
|
@@ -385,7 +404,7 @@ export default function WarehousesPage() { | |
| <Button variant="outline" type="button" onClick={() => setIsCreateDialogOpen(false)}> | ||
| Cancel | ||
| </Button> | ||
| <Button type="submit" disabled={createWarehouse.isPending}> | ||
| <Button type="button" onClick={handleCreateSubmit} disabled={createWarehouse.isPending}> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Warehouse forms no longer submit on Enter keyMedium Severity Both the create and edit warehouse forms replaced Additional Locations (1) |
||
| {createWarehouse.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} | ||
| Create Warehouse | ||
| </Button> | ||
|
|
@@ -403,7 +422,7 @@ export default function WarehousesPage() { | |
| Update the warehouse details. | ||
| </DialogDescription> | ||
| </DialogHeader> | ||
| <form onSubmit={handleEditSubmit}> | ||
| <form noValidate> | ||
| <div className="grid gap-4 py-4"> | ||
| <div className="grid gap-2"> | ||
| <Label htmlFor="edit-name">Warehouse Name *</Label> | ||
|
|
@@ -466,7 +485,7 @@ export default function WarehousesPage() { | |
| <Button variant="outline" type="button" onClick={() => setIsEditDialogOpen(false)}> | ||
| Cancel | ||
| </Button> | ||
| <Button type="submit" disabled={updateWarehouse.isPending}> | ||
| <Button type="button" onClick={handleEditSubmit} disabled={updateWarehouse.isPending}> | ||
| {updateWarehouse.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} | ||
| Save Changes | ||
| </Button> | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate
unwrapApiDatafunction across two pagesLow Severity
The
unwrapApiDatafunction is identically copy-pasted into bothdashboard.tsxandreports.tsxas a component-scoped helper. No shared utility exists for this pattern inclient/src/libor elsewhere. Extracting it to a shared module would reduce maintenance burden and ensure consistent envelope-unwrapping behavior across all pages.Additional Locations (1)
client/src/pages/reports.tsx#L31-L47