@@ -8,125 +8,138 @@ import { toast } from "sonner";
88import { type Budgetable , areRowsEqual } from "@/lib/utils" ;
99
1010const DEFAULT_NEW_ROW : Budgetable = {
11- id : "" ,
12- title : "" ,
13- price : 0 ,
14- link : "" ,
15- note : "" ,
16- status : "Unpaid" ,
11+ id : "" ,
12+ title : "" ,
13+ price : 0 ,
14+ link : "" ,
15+ note : "" ,
16+ status : "Unpaid" ,
1717} ;
1818
1919const ENDPOINT = "/pocketbase" ;
2020
2121export default function App ( ) {
22- const [ data , setData ] = useState < Budgetable [ ] > ( [ ] ) ;
23- const [ isEditing , setIsEditing ] = useState ( false ) ;
24- const [ newRow , setNewRow ] = useState < Budgetable > ( DEFAULT_NEW_ROW ) ;
25- const [ recentlyUpdatedRowId , setRecentlyUpdatedRowId ] = useState < string | null > ( null ) ;
26-
27- const fetchData = useCallback ( async ( ) => {
28- try {
29- const res = await fetch ( ENDPOINT ) ;
30- if ( ! res . ok ) throw new Error ( "Failed to fetch data" ) ;
31- const records : Budgetable [ ] = await res . json ( ) ;
32- setData ( records ) ;
33- } catch ( err ) {
34- toast . error ( "Error fetching data. Please try again later." ) ;
35- console . error ( err ) ;
36- }
37- } , [ ] ) ;
38-
39- useEffect ( ( ) => {
40- fetchData ( ) ;
41- } , [ fetchData ] ) ;
42-
43- const handleSave = useCallback ( async ( updatedRow : Budgetable , originalRow : Budgetable ) => {
44- if ( areRowsEqual ( updatedRow , originalRow ) ) return ;
45-
46- try {
47- const res = await fetch ( `${ ENDPOINT } /${ updatedRow . id } ` , {
48- method : "PUT" ,
49- headers : { "Content-Type" : "application/json" } ,
50- body : JSON . stringify ( updatedRow ) ,
51- } ) ;
52- if ( ! res . ok ) throw new Error ( "Failed to update row" ) ;
53-
54- const updatedData = await res . json ( ) ;
55- setData ( ( prev ) => prev . map ( ( row ) => ( row . id === updatedRow . id ? updatedData : row ) ) ) ;
56-
57- setRecentlyUpdatedRowId ( updatedRow . id ) ;
58- setTimeout ( ( ) => setRecentlyUpdatedRowId ( null ) , 500 ) ;
59- toast . success ( "Row updated successfully!" ) ;
60- } catch ( err ) {
61- toast . error ( "Error updating row. Please try again." ) ;
62- console . error ( err ) ;
63- }
64- } , [ ] ) ;
65-
66- const handleAddRow = useCallback ( async ( ) => {
67- if ( ! newRow . title || newRow . price <= 0 ) {
68- toast . error ( "Title and price are required." ) ;
69- return ;
70- }
71-
72- try {
73- const res = await fetch ( ENDPOINT , {
74- method : "POST" ,
75- headers : { "Content-Type" : "application/json" } ,
76- body : JSON . stringify ( newRow ) ,
77- } ) ;
78- if ( ! res . ok ) throw new Error ( "Failed to add row" ) ;
79-
80- const record : Budgetable = await res . json ( ) ;
81- setData ( ( prev ) => [ ...prev , record ] ) ;
82- setNewRow ( DEFAULT_NEW_ROW ) ;
83- toast . success ( "Row added successfully!" ) ;
84- } catch ( err ) {
85- toast . error ( "Error adding row. Please try again." ) ;
86- console . error ( err ) ;
87- }
88- } , [ newRow ] ) ;
89-
90- const handleDeleteRow = useCallback ( async ( id : string ) => {
91- try {
92- const res = await fetch ( `${ ENDPOINT } /${ id } ` , { method : "DELETE" } ) ;
93- if ( ! res . ok ) throw new Error ( "Failed to delete row" ) ;
94-
95- setData ( ( prev ) => prev . filter ( ( row ) => row . id !== id ) ) ;
96- toast . success ( "Row deleted successfully!" ) ;
97- } catch ( err ) {
98- toast . error ( "Error deleting row. Please try again." ) ;
99- console . error ( err ) ;
100- }
101- } , [ ] ) ;
102-
103- const toggleStatus = useCallback ( async ( row : Budgetable ) => {
104- const updatedStatus = row . status === "Paid" ? "Unpaid" : "Paid" ;
105- const updatedRow : Budgetable = { ...row , status : updatedStatus as "Paid" | "Unpaid" } ;
106- await handleSave ( updatedRow , row ) ;
107- } , [ handleSave ] ) ;
108-
109- const total = data . reduce (
110- ( sum , item ) => sum + ( item . status === "Unpaid" ? item . price : 0 ) ,
111- 0 ,
112- ) ;
113-
114- return (
115- < main className = "container mx-auto p-4 max-w-7xl" >
116- < Header isEditing = { isEditing } setIsEditing = { setIsEditing } />
117- < TotalDisplay total = { total } />
118- < TableWrapper
119- data = { data }
120- isEditing = { isEditing }
121- setData = { setData }
122- newRow = { newRow }
123- setNewRow = { setNewRow }
124- recentlyUpdatedRowId = { recentlyUpdatedRowId }
125- handleSave = { handleSave }
126- handleAddRow = { handleAddRow }
127- handleDeleteRow = { handleDeleteRow }
128- toggleStatus = { toggleStatus }
129- />
130- </ main >
131- ) ;
22+ const [ data , setData ] = useState < Budgetable [ ] > ( [ ] ) ;
23+ const [ isEditing , setIsEditing ] = useState ( false ) ;
24+ const [ newRow , setNewRow ] = useState < Budgetable > ( DEFAULT_NEW_ROW ) ;
25+ const [ recentlyUpdatedRowId , setRecentlyUpdatedRowId ] = useState <
26+ string | null
27+ > ( null ) ;
28+
29+ const fetchData = useCallback ( async ( ) => {
30+ try {
31+ const res = await fetch ( ENDPOINT ) ;
32+ if ( ! res . ok ) throw new Error ( "Failed to fetch data" ) ;
33+ const records : Budgetable [ ] = await res . json ( ) ;
34+ setData ( records ) ;
35+ } catch ( err ) {
36+ toast . error ( "Error fetching data. Please try again later." ) ;
37+ console . error ( err ) ;
38+ }
39+ } , [ ] ) ;
40+
41+ useEffect ( ( ) => {
42+ fetchData ( ) ;
43+ } , [ fetchData ] ) ;
44+
45+ const handleSave = useCallback (
46+ async ( updatedRow : Budgetable , originalRow : Budgetable ) => {
47+ if ( areRowsEqual ( updatedRow , originalRow ) ) return ;
48+
49+ try {
50+ const res = await fetch ( `${ ENDPOINT } /${ updatedRow . id } ` , {
51+ method : "PUT" ,
52+ headers : { "Content-Type" : "application/json" } ,
53+ body : JSON . stringify ( updatedRow ) ,
54+ } ) ;
55+ if ( ! res . ok ) throw new Error ( "Failed to update row" ) ;
56+
57+ const updatedData = await res . json ( ) ;
58+ setData ( ( prev ) =>
59+ prev . map ( ( row ) => ( row . id === updatedRow . id ? updatedData : row ) ) ,
60+ ) ;
61+
62+ setRecentlyUpdatedRowId ( updatedRow . id ) ;
63+ setTimeout ( ( ) => setRecentlyUpdatedRowId ( null ) , 500 ) ;
64+ toast . success ( "Row updated successfully!" ) ;
65+ } catch ( err ) {
66+ toast . error ( "Error updating row. Please try again." ) ;
67+ console . error ( err ) ;
68+ }
69+ } ,
70+ [ ] ,
71+ ) ;
72+
73+ const handleAddRow = useCallback ( async ( ) => {
74+ if ( ! newRow . title || newRow . price <= 0 ) {
75+ toast . error ( "Title and price are required." ) ;
76+ return ;
77+ }
78+
79+ try {
80+ const res = await fetch ( ENDPOINT , {
81+ method : "POST" ,
82+ headers : { "Content-Type" : "application/json" } ,
83+ body : JSON . stringify ( newRow ) ,
84+ } ) ;
85+ if ( ! res . ok ) throw new Error ( "Failed to add row" ) ;
86+
87+ const record : Budgetable = await res . json ( ) ;
88+ setData ( ( prev ) => [ ...prev , record ] ) ;
89+ setNewRow ( DEFAULT_NEW_ROW ) ;
90+ toast . success ( "Row added successfully!" ) ;
91+ } catch ( err ) {
92+ toast . error ( "Error adding row. Please try again." ) ;
93+ console . error ( err ) ;
94+ }
95+ } , [ newRow ] ) ;
96+
97+ const handleDeleteRow = useCallback ( async ( id : string ) => {
98+ try {
99+ const res = await fetch ( `${ ENDPOINT } /${ id } ` , { method : "DELETE" } ) ;
100+ if ( ! res . ok ) throw new Error ( "Failed to delete row" ) ;
101+
102+ setData ( ( prev ) => prev . filter ( ( row ) => row . id !== id ) ) ;
103+ toast . success ( "Row deleted successfully!" ) ;
104+ } catch ( err ) {
105+ toast . error ( "Error deleting row. Please try again." ) ;
106+ console . error ( err ) ;
107+ }
108+ } , [ ] ) ;
109+
110+ const toggleStatus = useCallback (
111+ async ( row : Budgetable ) => {
112+ const updatedStatus = row . status === "Paid" ? "Unpaid" : "Paid" ;
113+ const updatedRow : Budgetable = {
114+ ...row ,
115+ status : updatedStatus as "Paid" | "Unpaid" ,
116+ } ;
117+ await handleSave ( updatedRow , row ) ;
118+ } ,
119+ [ handleSave ] ,
120+ ) ;
121+
122+ const total = data . reduce (
123+ ( sum , item ) => sum + ( item . status === "Unpaid" ? item . price : 0 ) ,
124+ 0 ,
125+ ) ;
126+
127+ return (
128+ < main className = "container mx-auto p-4 max-w-7xl" >
129+ < Header isEditing = { isEditing } setIsEditing = { setIsEditing } />
130+ < TotalDisplay total = { total } />
131+ < TableWrapper
132+ data = { data }
133+ isEditing = { isEditing }
134+ setData = { setData }
135+ newRow = { newRow }
136+ setNewRow = { setNewRow }
137+ recentlyUpdatedRowId = { recentlyUpdatedRowId }
138+ handleSave = { handleSave }
139+ handleAddRow = { handleAddRow }
140+ handleDeleteRow = { handleDeleteRow }
141+ toggleStatus = { toggleStatus }
142+ />
143+ </ main >
144+ ) ;
132145}
0 commit comments