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" ;
33import { Button } from "@/components/ui/button" ;
44import { 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" ;
66import { Label } from "@/components/ui/label" ;
77import { Dialog , DialogContent , DialogDescription , DialogFooter , DialogHeader , DialogTitle } from "@/components/ui/dialog" ;
88import { 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
1513type InventoryHistoryEntry = {
1614 date : string ;
@@ -23,238 +21,123 @@ type InventoryHistoryEntry = {
2321} ;
2422
2523interface 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