diff --git a/package.json b/package.json index ccc10dc..fef6292 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,12 @@ "format": "biome format --write src/" }, "dependencies": { + "@number-flow/react": "^0.4.4", "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-progress": "^1.1.1", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-toast": "^1.2.4", + "@radix-ui/react-tooltip": "^1.1.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.469.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5edcf1e..4145036 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@number-flow/react': + specifier: ^0.4.4 + version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-dialog': specifier: ^1.1.4 version: 1.1.4(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -20,6 +23,9 @@ importers: '@radix-ui/react-toast': specifier: ^1.2.4 version: 1.2.4(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-tooltip': + specifier: ^1.1.6 + version: 1.1.6(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -181,6 +187,21 @@ packages: resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/core@1.6.8': + resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} + + '@floating-ui/dom@1.6.12': + resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==} + + '@floating-ui/react-dom@2.1.2': + resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.8': + resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -398,6 +419,12 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@number-flow/react@0.4.4': + resolution: {integrity: sha512-m2FW55jnWqiJzKNRETgyxgR7a8x0WYIZ1uyiIW8C5g6HyjIynryJJop1mEcmHG6OyNePkcb8OQIcjKlV5TfHAw==} + peerDependencies: + react: ^18 || ^19 + react-dom: ^18 || ^19 + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -405,6 +432,19 @@ packages: '@radix-ui/primitive@1.1.1': resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} + '@radix-ui/react-arrow@1.1.1': + resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.1': resolution: {integrity: sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==} peerDependencies: @@ -493,6 +533,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-popper@1.2.1': + resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-portal@1.1.3': resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==} peerDependencies: @@ -567,6 +620,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-tooltip@1.1.6': + resolution: {integrity: sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-use-callback-ref@1.1.0': resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} peerDependencies: @@ -603,6 +669,24 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-rect@1.1.0': + resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.0': + resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-visually-hidden@1.1.1': resolution: {integrity: sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==} peerDependencies: @@ -616,6 +700,9 @@ packages: '@types/react-dom': optional: true + '@radix-ui/rect@1.1.0': + resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -1095,6 +1182,9 @@ packages: jiti: optional: true + esm-env@1.2.1: + resolution: {integrity: sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==} + espree@10.3.0: resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1526,6 +1616,9 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + number-flow@0.4.2: + resolution: {integrity: sha512-YLN73/m8BUU4r/6mq9zqLdpFKt3LSPPRectOECheA9jtNWF4PP8EIz0+Z1giqu/x9nS86KnKwwouLXXiqnjhQQ==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2138,6 +2231,23 @@ snapshots: dependencies: levn: 0.4.1 + '@floating-ui/core@1.6.8': + dependencies: + '@floating-ui/utils': 0.2.8 + + '@floating-ui/dom@1.6.12': + dependencies: + '@floating-ui/core': 1.6.8 + '@floating-ui/utils': 0.2.8 + + '@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@floating-ui/dom': 1.6.12 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@floating-ui/utils@0.2.8': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -2296,11 +2406,27 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@number-flow/react@0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + esm-env: 1.2.1 + number-flow: 0.4.2 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + '@pkgjs/parseargs@0.11.0': optional: true '@radix-ui/primitive@1.1.1': {} + '@radix-ui/react-arrow@1.1.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.2 + '@types/react-dom': 19.0.2(@types/react@19.0.2) + '@radix-ui/react-collection@1.1.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.2)(react@19.0.0) @@ -2384,6 +2510,24 @@ snapshots: optionalDependencies: '@types/react': 19.0.2 + '@radix-ui/react-popper@1.2.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-arrow': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-use-rect': 1.1.0(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/rect': 1.1.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.2 + '@types/react-dom': 19.0.2(@types/react@19.0.2) + '@radix-ui/react-portal@1.1.3(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -2450,6 +2594,26 @@ snapshots: '@types/react': 19.0.2 '@types/react-dom': 19.0.2(@types/react@19.0.2) + '@radix-ui/react-tooltip@1.1.6(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-popper': 1.2.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-visually-hidden': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.2 + '@types/react-dom': 19.0.2(@types/react@19.0.2) + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.0.2)(react@19.0.0)': dependencies: react: 19.0.0 @@ -2476,6 +2640,20 @@ snapshots: optionalDependencies: '@types/react': 19.0.2 + '@radix-ui/react-use-rect@1.1.0(@types/react@19.0.2)(react@19.0.0)': + dependencies: + '@radix-ui/rect': 1.1.0 + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.2 + + '@radix-ui/react-use-size@1.1.0(@types/react@19.0.2)(react@19.0.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.2)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.2 + '@radix-ui/react-visually-hidden@1.1.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -2485,6 +2663,8 @@ snapshots: '@types/react': 19.0.2 '@types/react-dom': 19.0.2(@types/react@19.0.2) + '@radix-ui/rect@1.1.0': {} + '@rtsao/scc@1.1.0': {} '@rushstack/eslint-patch@1.10.4': {} @@ -3151,6 +3331,8 @@ snapshots: transitivePeerDependencies: - supports-color + esm-env@1.2.1: {} + espree@10.3.0: dependencies: acorn: 8.14.0 @@ -3587,6 +3769,10 @@ snapshots: normalize-path@3.0.0: {} + number-flow@0.4.2: + dependencies: + esm-env: 1.2.1 + object-assign@4.1.1: {} object-hash@3.0.0: {} diff --git a/src/app/page.tsx b/src/app/page.tsx index 60d747f..a94d78b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -5,7 +5,7 @@ import TableWrapper from "@/components/table-wrapper"; import Header from "@/components/header"; import TotalDisplay from "@/components/total-display"; import { toast } from "sonner"; -import { Budgetable, areRowsEqual } from "@/lib/utils"; +import { type Budgetable, areRowsEqual } from "@/lib/utils"; export default function App() { const [data, setData] = useState(() => []); @@ -132,6 +132,7 @@ export default function App() {
{loading}
+ -
); } diff --git a/src/app/pocketbase/[id]/route.ts b/src/app/pocketbase/[id]/route.ts index 16a56c8..ca1b874 100644 --- a/src/app/pocketbase/[id]/route.ts +++ b/src/app/pocketbase/[id]/route.ts @@ -20,14 +20,17 @@ export async function GET( const id = (await context.params)?.id; if (!id) { - return Response.json({ - error: { - message: "Missing ID in request", + return Response.json( + { + error: { + message: "Missing ID in request", + }, }, - }, { - status: 400, - headers: { "Content-Type": "application/json" }, - }); + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); } const record = await pb.collection("budgetable").getOne(id); @@ -37,14 +40,17 @@ export async function GET( }); } catch (error) { console.error("Error fetching data:", error); - return Response.json({ - error: { - message: "Failed to fetch data", + return Response.json( + { + error: { + message: "Failed to fetch data", + }, }, - }, { - status: 500, - headers: { "Content-Type": "application/json" }, - }) + { + status: 500, + headers: { "Content-Type": "application/json" }, + }, + ); } } @@ -57,33 +63,42 @@ export async function DELETE( const id = (await context.params)?.id; if (!id) { - return Response.json({ - error: { - message: "Missing ID in request", + return Response.json( + { + error: { + message: "Missing ID in request", + }, }, - }, { - status: 400, - headers: { "Content-Type": "application/json" }, - }); + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); } await pb.collection("budgetable").delete(id); - return Response.json({ - success: true, - }, { - status: 200, - headers: { "Content-Type": "application/json" }, - }); + return Response.json( + { + success: true, + }, + { + status: 200, + headers: { "Content-Type": "application/json" }, + }, + ); } catch (error) { console.error("Error deleting data:", error); - return Response.json({ - error: { - message: "Failed to delete data", + return Response.json( + { + error: { + message: "Failed to delete data", + }, }, - }, { - status: 500, - headers: { "Content-Type": "application/json" }, - }); + { + status: 500, + headers: { "Content-Type": "application/json" }, + }, + ); } } @@ -96,26 +111,32 @@ export async function PUT( const id = (await context.params)?.id; if (!id) { - return Response.json({ - error: { - message: "Missing ID in request", + return Response.json( + { + error: { + message: "Missing ID in request", + }, }, - }, { - status: 400, - headers: { "Content-Type": "application/json" }, - }); + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); } const body = await req.json(); if (!body.title || typeof body.price !== "number") { - return Response.json({ - error: { - message: "Invalid data provided", + return Response.json( + { + error: { + message: "Invalid data provided", + }, }, - }, { - status: 400, - headers: { "Content-Type": "application/json" }, - }); + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); } const updatedRecord = await pb.collection("budgetable").update(id, body); @@ -125,13 +146,16 @@ export async function PUT( }); } catch (error) { console.error("Error updating data:", error); - return Response.json({ - error: { - message: "Failed to update data", + return Response.json( + { + error: { + message: "Failed to update data", + }, }, - }, { - status: 500, - headers: { "Content-Type": "application/json" }, - }); + { + status: 500, + headers: { "Content-Type": "application/json" }, + }, + ); } } diff --git a/src/app/pocketbase/route.ts b/src/app/pocketbase/route.ts index 9a50805..7679c77 100644 --- a/src/app/pocketbase/route.ts +++ b/src/app/pocketbase/route.ts @@ -21,14 +21,17 @@ export async function GET() { }); } catch (error) { console.error("Error fetching data:", error); - return Response.json({ - error: { - message: "Failed to fetch data", + return Response.json( + { + error: { + message: "Failed to fetch data", + }, }, - }, { - status: 500, - headers: { "Content-Type": "application/json" }, - }); + { + status: 500, + headers: { "Content-Type": "application/json" }, + }, + ); } } @@ -43,13 +46,16 @@ export async function POST(req: Request) { }); } catch (error) { console.error("Error adding data:", error); - return Response.json({ - error: { - message: "Failed to add data", + return Response.json( + { + error: { + message: "Failed to add data", + }, }, - }, { - status: 500, - headers: { "Content-Type": "application/json" }, - }); + { + status: 500, + headers: { "Content-Type": "application/json" }, + }, + ); } } diff --git a/src/components/delete-dialog.tsx b/src/components/delete-dialog.tsx index ea0c03c..fb12780 100644 --- a/src/components/delete-dialog.tsx +++ b/src/components/delete-dialog.tsx @@ -21,7 +21,9 @@ const DeleteDialog = ({ onConfirm }: DeleteDialogProps) => ( Delete Row Are you sure you want to delete this row? - + diff --git a/src/components/header.tsx b/src/components/header.tsx index 2fe9066..18c937e 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -13,7 +13,7 @@ const Header = ({ isEditing, setIsEditing }: AppHeaderProps) => ( href="/" className="text-blue-500 hover:text-blue-600 transition-colors" onClick={() => setIsEditing(false)} - > + > Budgetable diff --git a/src/components/table-new-row.tsx b/src/components/table-new-row.tsx index cce832c..1b5412f 100644 --- a/src/components/table-new-row.tsx +++ b/src/components/table-new-row.tsx @@ -1,7 +1,7 @@ import { TableRow, TableCell } from "@/components/ui/table"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { Budgetable } from '@/lib/utils'; +import type { Budgetable } from "@/lib/utils"; interface TNewRowProps { newRow: Budgetable; diff --git a/src/components/table-row.tsx b/src/components/table-row.tsx index e2f6439..a9d4278 100644 --- a/src/components/table-row.tsx +++ b/src/components/table-row.tsx @@ -3,8 +3,14 @@ import { TableRow, TableCell } from "@/components/ui/table"; import Link from "next/link"; import StatusBadge from "@/components/status-badge"; import DeleteDialog from "@/components/delete-dialog"; -import { useState } from 'react'; -import { Budgetable } from '@/lib/utils'; +import { useState } from "react"; +import type { Budgetable } from "@/lib/utils"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; interface TRowProps { row: Budgetable; @@ -31,7 +37,7 @@ const TRow = ({ const [originalRow, setOriginalRow] = useState(row); const handleInputFocus = () => { - setOriginalRow({...row}); + setOriginalRow({ ...row }); }; return ( @@ -48,7 +54,9 @@ const TRow = ({ onChange={(e) => setData((prev) => prev.map((item) => - item.id === row.id ? { ...item, title: e.target.value } : item, + item.id === row.id + ? { ...item, title: e.target.value } + : item, ), ) } @@ -94,14 +102,18 @@ const TRow = ({ onBlur={() => handleSave(row, originalRow)} /> ) : row.link ? ( - - Visit - + + + + + Visit + + + + {row.link.replace(/^https?:\/\//, "")} + + + ) : ( No link )} @@ -125,7 +137,10 @@ const TRow = ({ )} - toggleStatus(row)} /> + toggleStatus(row)} + /> {isEditing && ( diff --git a/src/components/table-wrapper.tsx b/src/components/table-wrapper.tsx index 2ad9afc..7361e84 100644 --- a/src/components/table-wrapper.tsx +++ b/src/components/table-wrapper.tsx @@ -7,7 +7,7 @@ import { } from "@/components/ui/table"; import TRow from "@/components/table-row"; import TNewRow from "@/components/table-new-row"; -import { Budgetable } from '@/lib/utils'; +import type { Budgetable } from "@/lib/utils"; interface TWrapperProps { data: Budgetable[]; diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx index 57757ed..35f7393 100644 --- a/src/components/theme-provider.tsx +++ b/src/components/theme-provider.tsx @@ -1,6 +1,6 @@ "use client"; -import { ComponentProps } from "react"; +import type { ComponentProps } from "react"; import { ThemeProvider as NextThemesProvider } from "next-themes"; export function ThemeProvider({ diff --git a/src/components/total-display.tsx b/src/components/total-display.tsx index 2f29e60..58502a6 100644 --- a/src/components/total-display.tsx +++ b/src/components/total-display.tsx @@ -1,13 +1,12 @@ +import NumberFlow from "@number-flow/react"; + interface TotalDisplayProps { total: number; } const TotalDisplay = ({ total }: TotalDisplayProps) => ( -
- Total: {total.toLocaleString()} -

- ({Math.round(total * 1.27).toLocaleString()}) -

+
+ Total:
); diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..888e43d --- /dev/null +++ b/src/components/ui/tooltip.tsx @@ -0,0 +1,32 @@ +"use client"; + +import * as React from "react"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; + +import { cn } from "@/lib/utils"; + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e51e994..b487f94 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -16,14 +16,14 @@ export interface Budgetable { export const areRowsEqual = (row1: Budgetable, row2: Budgetable): boolean => { const normalize = (value: string | number | undefined) => - String(value ?? "").trim(); + String(value ?? "").trim(); const areEqual = (field: keyof Budgetable) => - field === "price" - ? Number(row1[field]) === Number(row2[field]) - : normalize(row1[field]) === normalize(row2[field]); + field === "price" + ? Number(row1[field]) === Number(row2[field]) + : normalize(row1[field]) === normalize(row2[field]); - return ["title", "price", "link", "note", "status"].every(field => - areEqual(field as keyof Budgetable) + return ["title", "price", "link", "note", "status"].every((field) => + areEqual(field as keyof Budgetable), ); - }; +};