Skip to content

Commit 703fe7f

Browse files
committed
feat: implement chicken inventory management with API integration and real-time updates
1 parent 55454c5 commit 703fe7f

File tree

3 files changed

+197
-189
lines changed

3 files changed

+197
-189
lines changed

src/components/ChickenInventory.tsx

Lines changed: 67 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
import { useState, useEffect } from 'react';
2-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
1+
import { useState } from 'react';
2+
import { Card, CardContent } from "@/components/ui/card";
33
import { Button } from "@/components/ui/button";
44
import { Input } from "@/components/ui/input";
5-
import { Edit, Check, X, Plus, Minus } from "lucide-react";
5+
import { Edit, Check, X, Plus, Minus, RefreshCcw } from "lucide-react";
66
import { Label } from "@/components/ui/label";
77
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
88
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
9-
import { cn } from "@/lib/utils";
9+
import { useChickenInventory, ChickenType, ChickenCounts } from "@/hooks/use-chicken-inventory";
1010

11-
// Define reason types for inventory changes
12-
type InventoryChangeReason = 'purchase' | 'sale' | 'birth' | 'death' | 'gift' | 'other';
13-
type ChickenType = 'hen' | 'cock' | 'chicks';
11+
type InventoryChangeReason = 'birth' | 'death' | 'gift' | 'other';
1412

1513
type InventoryHistoryEntry = {
1614
date: string;
@@ -23,238 +21,123 @@ type InventoryHistoryEntry = {
2321
};
2422

2523
interface ChickenInventoryProps {
26-
externalCounts?: {
27-
hen: number;
28-
cock: number;
29-
chicks: number;
30-
};
3124
onInventoryChange?: (newCounts: {
3225
hen: number;
3326
cock: number;
3427
chicks: number;
3528
}) => void;
3629
}
3730

38-
export function ChickenInventory({ externalCounts, onInventoryChange }: ChickenInventoryProps = {}) {
39-
// State for edit dialog
31+
export function ChickenInventory({ onInventoryChange }: ChickenInventoryProps = {}) {
32+
const { counts: apiCounts, loading, error, fetchChickenInventory, updateChickenInventory } = useChickenInventory();
4033
const [editDialog, setEditDialog] = useState({
4134
isOpen: false,
4235
type: '' as ChickenType,
4336
currentValue: 0,
4437
newValue: 0,
45-
reason: '' as InventoryChangeReason,
46-
notes: ''
47-
});
48-
49-
// State for counts - use external counts if provided, otherwise use localStorage
50-
const [counts, setCountsInternal] = useState(() => {
51-
// If external counts are provided, use them
52-
if (externalCounts) {
53-
return externalCounts;
54-
}
55-
56-
// Otherwise, load from localStorage
57-
const savedCounts = localStorage.getItem('chickenCounts');
58-
return savedCounts ? JSON.parse(savedCounts) : {
59-
hen: 0,
60-
cock: 0,
61-
chicks: 0
62-
};
38+
reason: '' as InventoryChangeReason
6339
});
6440

65-
// Update local state when external counts change
66-
useEffect(() => {
67-
if (externalCounts) {
68-
setCountsInternal(externalCounts);
69-
}
70-
}, [externalCounts]);
41+
const totalCount = apiCounts?.hen + apiCounts?.cock + apiCounts?.chicks || 0;
7142

72-
// Wrapper for setCounts that also calls the onInventoryChange callback
73-
const setCounts = (newCounts: typeof counts) => {
74-
setCountsInternal(newCounts);
43+
const handleInventoryChange = async (type: ChickenType, newValue: number, reason: InventoryChangeReason = 'other') => {
44+
// Always update via API first
45+
await updateChickenInventory(type, newValue, reason);
7546

76-
// If onInventoryChange callback is provided, call it
47+
// Then notify parent component if callback is provided
7748
if (onInventoryChange) {
78-
onInventoryChange(newCounts);
49+
onInventoryChange({...apiCounts, [type]: newValue});
7950
}
8051
};
8152

82-
// Initialize inventory history
83-
const [inventoryHistory, setInventoryHistory] = useState(() => {
84-
const savedHistory = localStorage.getItem('chickenInventoryHistory');
85-
return savedHistory ? JSON.parse(savedHistory) : [];
86-
});
87-
88-
// Calculate total count
89-
const totalCount = counts.hen + counts.cock + counts.chicks;
90-
91-
// Function to open edit dialog
9253
const openEditDialog = (type: ChickenType) => {
9354
setEditDialog({
9455
isOpen: true,
9556
type,
96-
currentValue: counts[type],
97-
newValue: counts[type],
98-
reason: '' as InventoryChangeReason,
99-
notes: ''
57+
currentValue: apiCounts?.[type] || 0,
58+
newValue: apiCounts?.[type] || 0,
59+
reason: '' as InventoryChangeReason
10060
});
10161
};
10262

103-
// Function to close edit dialog
10463
const closeEditDialog = () => {
105-
setEditDialog({
106-
...editDialog,
107-
isOpen: false
108-
});
64+
setEditDialog({...editDialog, isOpen: false});
10965
};
11066

111-
// Function to handle value change in the dialog
11267
const handleValueChange = (value: string) => {
113-
const numValue = parseInt(value) || 0;
114-
const newValue = Math.max(0, numValue); // Prevent negative counts
115-
116-
setEditDialog({
117-
...editDialog,
118-
newValue
119-
});
68+
const newValue = Math.max(0, parseInt(value) || 0);
69+
setEditDialog({...editDialog, newValue});
12070
};
12171

122-
// Function to apply confirmed value change
123-
const applyValueChange = () => {
72+
const applyValueChange = async () => {
12473
if (!editDialog.reason) return;
12574

126-
const { type, currentValue, newValue, reason, notes } = editDialog;
75+
const { type, currentValue, newValue, reason } = editDialog;
12776

128-
// Only proceed if there's an actual change
12977
if (newValue === currentValue) {
13078
closeEditDialog();
13179
return;
13280
}
133-
134-
// Create a history entry
13581
const historyEntry: InventoryHistoryEntry = {
13682
date: new Date().toISOString(),
13783
type,
13884
previousValue: currentValue,
139-
newValue: Math.max(0, newValue),
140-
change: Math.max(0, newValue) - currentValue,
85+
newValue,
86+
change: newValue - currentValue,
14187
reason,
142-
notes
143-
};
144-
145-
// Update inventory history
146-
const updatedHistory = [...inventoryHistory, historyEntry];
147-
setInventoryHistory(updatedHistory);
148-
localStorage.setItem('chickenInventoryHistory', JSON.stringify(updatedHistory));
149-
150-
// Update counts
151-
const newCounts = {
152-
...counts,
153-
[type]: Math.max(0, newValue) // Prevent negative counts
88+
notes: ''
15489
};
15590

156-
setCounts(newCounts);
157-
localStorage.setItem('chickenCounts', JSON.stringify(newCounts));
158-
159-
// Close the dialog
91+
try {
92+
await handleInventoryChange(type, newValue, reason);
93+
} catch (error) {
94+
console.error('Failed to update chicken inventory:', error);
95+
}
16096
closeEditDialog();
16197
};
16298

16399
return (
164100
<>
165-
{/* Edit Dialog with Reason Selection */}
166-
<Dialog open={editDialog.isOpen} onOpenChange={closeEditDialog}>
167-
<DialogContent className="sm:max-w-md">
101+
<Dialog open={editDialog.isOpen} onOpenChange={(open) => !open && closeEditDialog()}>
102+
<DialogContent className="sm:max-w-[425px]">
168103
<DialogHeader>
169-
<DialogTitle className="flex items-center gap-2">
170-
<Edit className="h-5 w-5 text-blue-500" />
171-
Update {editDialog.type ? editDialog.type.charAt(0).toUpperCase() + editDialog.type.slice(1) : ''} Count
172-
</DialogTitle>
173-
<DialogDescription>
174-
Enter the new count and provide a reason for the change.
175-
</DialogDescription>
104+
<DialogTitle>Update {editDialog.type.charAt(0).toUpperCase() + editDialog.type.slice(1)} Count</DialogTitle>
105+
<DialogDescription>Change the number of {editDialog.type} in your inventory.</DialogDescription>
176106
</DialogHeader>
177-
<div className="space-y-4 py-4">
178-
<div className="space-y-2">
179-
<Label htmlFor="new-count">New Count</Label>
180-
<div className="flex items-center space-x-2">
181-
<Button
182-
type="button"
183-
variant="outline"
184-
size="icon"
185-
onClick={() => handleValueChange((editDialog.newValue - 1).toString())}
186-
disabled={editDialog.newValue <= 0}
187-
>
107+
<div className="grid gap-4 py-4">
108+
<div className="grid grid-cols-4 items-center gap-4">
109+
<Label htmlFor="current-value" className="text-right">Current</Label>
110+
<Input id="current-value" value={editDialog.currentValue} className="col-span-3" disabled />
111+
</div>
112+
<div className="grid grid-cols-4 items-center gap-4">
113+
<Label htmlFor="new-value" className="text-right">New Value</Label>
114+
<div className="flex items-center col-span-3">
115+
<Button type="button" variant="outline" size="icon" onClick={() => handleValueChange(Math.max(0, editDialog.newValue - 1).toString())}>
188116
<Minus className="h-4 w-4" />
189117
</Button>
190-
<Input
191-
id="new-count"
192-
type="number"
193-
value={editDialog.newValue}
194-
onChange={(e) => handleValueChange(e.target.value)}
195-
className="text-center"
196-
min="0"
197-
/>
198-
<Button
199-
type="button"
200-
variant="outline"
201-
size="icon"
202-
onClick={() => handleValueChange((editDialog.newValue + 1).toString())}
203-
>
118+
<Input id="new-value" type="number" value={editDialog.newValue} onChange={(e) => handleValueChange(e.target.value)} className="text-center" min="0" />
119+
<Button type="button" variant="outline" size="icon" onClick={() => handleValueChange((editDialog.newValue + 1).toString())}>
204120
<Plus className="h-4 w-4" />
205121
</Button>
206122
</div>
207-
{editDialog.currentValue !== editDialog.newValue && (
208-
<p className="text-sm text-gray-500">
209-
{editDialog.newValue > editDialog.currentValue ? 'Increase' : 'Decrease'} by: {Math.abs(editDialog.newValue - editDialog.currentValue)}
210-
</p>
211-
)}
212123
</div>
213-
214124
<div className="space-y-2">
215-
<Label htmlFor="change-reason">Reason for change</Label>
216-
<Select
217-
value={editDialog.reason}
218-
onValueChange={(value: InventoryChangeReason) =>
219-
setEditDialog({...editDialog, reason: value})
220-
}
221-
>
222-
<SelectTrigger id="change-reason">
223-
<SelectValue placeholder="Select reason" />
224-
</SelectTrigger>
125+
<Label htmlFor="change-reason">Reason for Change</Label>
126+
<Select value={editDialog.reason} onValueChange={(value) => setEditDialog({...editDialog, reason: value as InventoryChangeReason})}>
127+
<SelectTrigger id="change-reason"><SelectValue placeholder="Select a reason" /></SelectTrigger>
225128
<SelectContent>
226-
<SelectItem value="purchase">Purchase</SelectItem>
227-
<SelectItem value="sale">Sale</SelectItem>
228129
<SelectItem value="birth">Birth</SelectItem>
229130
<SelectItem value="death">Death</SelectItem>
230131
<SelectItem value="gift">Gift</SelectItem>
231132
<SelectItem value="other">Other</SelectItem>
232133
</SelectContent>
233134
</Select>
234135
</div>
235-
236-
<div className="space-y-2">
237-
<Label htmlFor="change-notes">Notes (optional)</Label>
238-
<Input
239-
id="change-notes"
240-
value={editDialog.notes}
241-
onChange={(e) => setEditDialog({...editDialog, notes: e.target.value})}
242-
placeholder="Additional details about this change"
243-
/>
244-
</div>
136+
245137
</div>
246-
<DialogFooter className="flex justify-between sm:justify-between">
247-
<Button variant="outline" onClick={closeEditDialog}>
248-
<X className="mr-2 h-4 w-4" />
249-
Cancel
250-
</Button>
251-
<Button
252-
onClick={applyValueChange}
253-
disabled={!editDialog.reason || editDialog.currentValue === editDialog.newValue}
254-
>
255-
<Check className="mr-2 h-4 w-4" />
256-
Save Change
257-
</Button>
138+
<DialogFooter className="flex justify-between">
139+
<Button variant="outline" onClick={closeEditDialog}><X className="mr-2 h-4 w-4" />Cancel</Button>
140+
<Button onClick={applyValueChange} disabled={!editDialog.reason || editDialog.currentValue === editDialog.newValue}><Check className="mr-2 h-4 w-4" />Save Change</Button>
258141
</DialogFooter>
259142
</DialogContent>
260143
</Dialog>
@@ -266,32 +149,28 @@ export function ChickenInventory({ externalCounts, onInventoryChange }: ChickenI
266149
<div className="text-xl font-bold text-gray-900 bg-gray-50 px-3 py-1 rounded-md border border-gray-200">{totalCount}</div>
267150
</div>
268151

152+
<div className="flex justify-between items-center mb-4">
153+
{error && <div className="text-red-500 text-sm">Error loading chicken inventory: {error}</div>}
154+
<Button variant="outline" size="sm" onClick={() => fetchChickenInventory()} disabled={loading} className="ml-auto text-gray-700">
155+
<RefreshCcw className={`h-4 w-4 mr-1 ${loading ? 'animate-spin' : ''}`} />
156+
{loading ? 'Loading...' : 'Refresh'}
157+
</Button>
158+
</div>
159+
269160
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
270-
{/* Chicken Inventory Cards */}
271161
{[
272-
{ type: 'hen', label: 'Hens', count: counts.hen },
273-
{ type: 'cock', label: 'Cocks', count: counts.cock },
274-
{ type: 'chicks', label: 'Chicks', count: counts.chicks }
162+
{ type: 'hen', label: 'Hens', count: apiCounts?.hen },
163+
{ type: 'cock', label: 'Cocks', count: apiCounts?.cock },
164+
{ type: 'chicks', label: 'Chicks', count: apiCounts?.chicks }
275165
].map((item) => (
276-
<div
277-
key={item.type}
278-
className="border border-gray-200 rounded-lg bg-white overflow-hidden hover:shadow-sm transition-shadow"
279-
>
166+
<div key={item.type} className="border border-gray-200 rounded-lg bg-white overflow-hidden hover:shadow-sm transition-shadow">
280167
<div className="px-4 py-3">
281168
<div className="flex items-center justify-between">
282-
<div>
283-
<h3 className="text-md font-medium text-gray-900">{item.label}</h3>
284-
</div>
285-
<div className="text-3xl font-bold text-gray-900 ml-2">{item.count}</div>
169+
<h3 className="text-md font-medium text-gray-900">{item.label}</h3>
170+
<div className="text-3xl font-bold text-gray-900 ml-2">{loading ? '...' : item.count}</div>
286171
</div>
287-
288172
<div className="mt-2">
289-
<Button
290-
variant="outline"
291-
size="sm"
292-
className="w-full border-gray-200 hover:bg-gray-50 text-gray-700 text-xs py-1"
293-
onClick={() => openEditDialog(item.type as ChickenType)}
294-
>
173+
<Button variant="outline" size="sm" className="w-full border-gray-200 hover:bg-gray-50 text-gray-700 text-xs py-1" onClick={() => openEditDialog(item.type as ChickenType)} disabled={loading}>
295174
<Edit className="h-3 w-3 mr-1" /> Update Count
296175
</Button>
297176
</div>

0 commit comments

Comments
 (0)