Skip to content

Commit 4324a1a

Browse files
committed
feat: add API integration for chicken inventory history with filtering
1 parent 703fe7f commit 4324a1a

File tree

2 files changed

+128
-48
lines changed

2 files changed

+128
-48
lines changed

src/components/ChickenInventoryHistory.tsx

Lines changed: 61 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,42 @@
1-
import { useState } from 'react';
1+
import { useState, useEffect } from 'react';
22
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
33
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
44
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
5-
import { Input } from "@/components/ui/input";
5+
import { Button } from "@/components/ui/button";
66
import { Label } from "@/components/ui/label";
77
import { format } from 'date-fns';
8-
9-
type InventoryChangeReason = 'purchase' | 'sale' | 'birth' | 'death' | 'gift' | 'other';
10-
11-
type InventoryHistoryEntry = {
12-
date: string;
13-
type: 'hen' | 'cock' | 'chicks';
14-
previousValue: number;
15-
newValue: number;
16-
change: number;
17-
reason: InventoryChangeReason;
18-
notes: string;
19-
};
8+
import { useChickenInventory, ChickenType, ChickenHistoryEntry } from "@/hooks/use-chicken-inventory";
9+
import { RefreshCcw } from "lucide-react";
2010

2111
export function ChickenInventoryHistory() {
12+
const { history, historyLoading, historyError, fetchChickenHistory } = useChickenInventory();
13+
2214
const [filter, setFilter] = useState<{
23-
type: 'all' | 'hen' | 'cock' | 'chicks';
24-
reason: 'all' | InventoryChangeReason;
15+
type: 'all' | ChickenType;
16+
reason: 'all' | string;
2517
}>({ type: 'all', reason: 'all' });
2618

27-
// Load history from localStorage
28-
const inventoryHistory: InventoryHistoryEntry[] = JSON.parse(
29-
localStorage.getItem('chickenInventoryHistory') || '[]'
30-
);
19+
// Fetch history when component mounts or filter changes
20+
useEffect(() => {
21+
const fetchHistory = async () => {
22+
// Only pass type and reason to API if they're not 'all'
23+
const typeParam = filter.type !== 'all' ? filter.type : undefined;
24+
const reasonParam = filter.reason !== 'all' ? filter.reason : undefined;
25+
await fetchChickenHistory(typeParam, reasonParam);
26+
};
27+
28+
fetchHistory();
29+
// Don't include fetchChickenHistory in dependencies to avoid infinite loops
30+
// eslint-disable-next-line react-hooks/exhaustive-deps
31+
}, [filter.type, filter.reason]);
3132

32-
// Filter history based on selected filters
33-
const sortedHistory = [...inventoryHistory].sort((a, b) => {
34-
return new Date(b.date).getTime() - new Date(a.date).getTime();
35-
});
36-
37-
const filteredHistory = sortedHistory.filter(entry => {
38-
if (filter.type !== 'all' && entry.type !== filter.type) return false;
39-
if (filter.reason !== 'all' && entry.reason !== filter.reason) return false;
40-
return true;
33+
// Sort history by date (newest first)
34+
const sortedHistory = [...history].sort((a, b) => {
35+
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
4136
});
4237

4338
// Helper function to get badge class for displaying reason
44-
const getReasonBadgeClass = (reason: InventoryChangeReason): string => {
39+
const getReasonBadgeClass = (reason: string): string => {
4540
return 'bg-gray-100 text-gray-700 py-1 px-2 rounded text-xs font-medium';
4641
};
4742

@@ -51,7 +46,7 @@ export function ChickenInventoryHistory() {
5146
};
5247

5348
// Helper function to format chicken type for display
54-
const formatChickenType = (type: 'hen' | 'cock' | 'chicks'): string => {
49+
const formatChickenType = (type: ChickenType): string => {
5550
switch (type) {
5651
case 'hen': return 'Hen';
5752
case 'cock': return 'Cock';
@@ -93,7 +88,7 @@ export function ChickenInventoryHistory() {
9388
<Label htmlFor="reason-filter" className="text-sm font-medium text-gray-700">Filter by Reason</Label>
9489
<Select
9590
value={filter.reason}
96-
onValueChange={(value: 'all' | InventoryChangeReason) =>
91+
onValueChange={(value: 'all' | string) =>
9792
setFilter({...filter, reason: value})
9893
}
9994
>
@@ -113,8 +108,22 @@ export function ChickenInventoryHistory() {
113108
</div>
114109
</div>
115110

111+
{/* Refresh Button */}
112+
<div className="flex justify-end mb-4">
113+
<Button
114+
variant="outline"
115+
size="sm"
116+
onClick={() => fetchChickenHistory(filter.type !== 'all' ? filter.type as ChickenType : undefined, filter.reason !== 'all' ? filter.reason : undefined)}
117+
disabled={historyLoading}
118+
className="text-gray-700"
119+
>
120+
<RefreshCcw className={`h-4 w-4 mr-1 ${historyLoading ? 'animate-spin' : ''}`} />
121+
{historyLoading ? 'Loading...' : 'Refresh'}
122+
</Button>
123+
</div>
124+
116125
{/* History Table */}
117-
{filteredHistory.length > 0 ? (
126+
{sortedHistory.length > 0 ? (
118127
<div className="border border-gray-200 rounded-lg overflow-hidden">
119128
<Table>
120129
<TableHeader className="bg-gray-50">
@@ -126,20 +135,17 @@ export function ChickenInventoryHistory() {
126135
</TableRow>
127136
</TableHeader>
128137
<TableBody>
129-
{filteredHistory.map((entry, index) => (
130-
<TableRow key={index} className="hover:bg-gray-50 border-t border-gray-100">
138+
{sortedHistory.map((entry, index) => (
139+
<TableRow key={entry.id || index} className="hover:bg-gray-50 border-t border-gray-100">
131140
<TableCell className="font-medium text-gray-700">
132-
{format(new Date(entry.date), 'MMM d, yyyy h:mm a')}
141+
{format(new Date(entry.created_at), 'MMM d, yyyy h:mm a')}
133142
</TableCell>
134143
<TableCell className={getTypeIndicatorClass()}>
135-
{formatChickenType(entry.type)}
144+
{formatChickenType(entry.chicken_type)}
136145
</TableCell>
137146
<TableCell>
138-
<span className={entry.change > 0 ? 'text-gray-900 font-medium' : 'text-gray-900 font-medium'}>
139-
{entry.change > 0 ? '+' : ''}{entry.change}
140-
</span>
141-
<span className="text-gray-500 text-xs ml-1">
142-
({entry.previousValue}{entry.newValue})
147+
<span className={entry.quantity_change > 0 ? 'text-gray-900 font-medium' : 'text-gray-900 font-medium'}>
148+
{entry.quantity_change > 0 ? '+' : ''}{entry.quantity_change}
143149
</span>
144150
</TableCell>
145151
<TableCell>
@@ -154,11 +160,19 @@ export function ChickenInventoryHistory() {
154160
</div>
155161
) : (
156162
<div className="text-center py-8 border border-dashed border-gray-200 rounded-lg bg-gray-50">
157-
<p className="text-gray-500 mb-2">
158-
No history records found{filter.type !== 'all' || filter.reason !== 'all' ? ' matching your filters' : ''}.
159-
</p>
160-
{(filter.type !== 'all' || filter.reason !== 'all') && (
161-
<p className="text-sm text-gray-400">Try adjusting your filters</p>
163+
{historyLoading ? (
164+
<p className="text-gray-500 mb-2">Loading chicken history...</p>
165+
) : historyError ? (
166+
<p className="text-red-500 mb-2">Error: {historyError}</p>
167+
) : (
168+
<>
169+
<p className="text-gray-500 mb-2">
170+
No history records found{filter.type !== 'all' || filter.reason !== 'all' ? ' matching your filters' : ''}.
171+
</p>
172+
{(filter.type !== 'all' || filter.reason !== 'all') && (
173+
<p className="text-sm text-gray-400">Try adjusting your filters</p>
174+
)}
175+
</>
162176
)}
163177
</div>
164178
)}

src/hooks/use-chicken-inventory.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,22 @@ export interface ChickenCounts {
1616
chicks: number;
1717
}
1818

19+
export interface ChickenHistoryEntry {
20+
id: string;
21+
chicken_type: ChickenType;
22+
quantity_change: number;
23+
reason: string;
24+
created_at: string;
25+
}
26+
1927
export function useChickenInventory() {
2028
const { user } = useAuth();
2129
const [counts, setCounts] = useState<ChickenCounts>({ hen: 0, cock: 0, chicks: 0 });
30+
const [history, setHistory] = useState<ChickenHistoryEntry[]>([]);
2231
const [loading, setLoading] = useState<boolean>(true);
32+
const [historyLoading, setHistoryLoading] = useState<boolean>(false);
2333
const [error, setError] = useState<string | null>(null);
34+
const [historyError, setHistoryError] = useState<string | null>(null);
2435

2536
const API_BASE_URL = 'http://localhost:5055/v1';
2637

@@ -120,11 +131,66 @@ export function useChickenInventory() {
120131
}
121132
};
122133

134+
const fetchChickenHistory = async (type?: ChickenType, reason?: string) => {
135+
setHistoryLoading(true);
136+
setHistoryError(null);
137+
138+
if (!user?.token) {
139+
setHistoryError('Authentication required');
140+
setHistoryLoading(false);
141+
return;
142+
}
143+
144+
try {
145+
// Build the URL with optional query parameters
146+
let url = `${API_BASE_URL}/auth/chicken-history`;
147+
const params = new URLSearchParams();
148+
149+
if (type) {
150+
params.append('type', type);
151+
}
152+
153+
if (reason) {
154+
params.append('reason', reason);
155+
}
156+
157+
const queryString = params.toString();
158+
if (queryString) {
159+
url += `?${queryString}`;
160+
}
161+
162+
const response = await fetch(url, {
163+
headers: { 'Authorization': `Bearer ${user.token}` }
164+
});
165+
166+
if (!response.ok) {
167+
throw new Error(`Failed to fetch chicken history: ${response.status}`);
168+
}
169+
170+
const data = await response.json();
171+
setHistory(data);
172+
} catch (err) {
173+
setHistoryError(err instanceof Error ? err.message : 'An unknown error occurred');
174+
} finally {
175+
setHistoryLoading(false);
176+
}
177+
};
178+
123179
useEffect(() => {
124180
if (user?.token) {
125181
fetchChickenInventory();
126182
}
127183
}, [user]);
128184

129-
return { counts, loading, error, fetchChickenInventory, updateChickenInventory };
185+
return {
186+
counts,
187+
history,
188+
loading,
189+
historyLoading,
190+
error,
191+
historyError,
192+
fetchChickenInventory,
193+
fetchChickenHistory,
194+
updateChickenInventory
195+
};
130196
}

0 commit comments

Comments
 (0)