Skip to content

Commit 83b3a74

Browse files
committed
feat: enhance ui components and response handling
1 parent 78fbceb commit 83b3a74

File tree

11 files changed

+307
-339
lines changed

11 files changed

+307
-339
lines changed

next.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const nextConfig: NextConfig = {
4848
},
4949
];
5050
},
51+
poweredByHeader: false,
5152
reactStrictMode: true,
5253
output: "standalone",
5354
};

src/app/page.tsx

Lines changed: 122 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,150 +1,132 @@
11
"use client";
22

3-
import { useState, useEffect } from "react";
3+
import { useState, useEffect, useCallback } from "react";
44
import TableWrapper from "@/components/table-wrapper";
55
import Header from "@/components/header";
66
import TotalDisplay from "@/components/total-display";
77
import { toast } from "sonner";
88
import { type Budgetable, areRowsEqual } from "@/lib/utils";
99

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

0 commit comments

Comments
 (0)