refactor: update imports to use 'type' for TypeScript types and enhance tooltip functionality

This commit is contained in:
skidoodle 2024-12-31 20:27:49 +01:00
parent 8e37da9463
commit 3d082d3092
Signed by: albert
GPG key ID: A06E3070D7D55BF2
14 changed files with 367 additions and 101 deletions

View file

@ -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",

186
pnpm-lock.yaml generated
View file

@ -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: {}

View file

@ -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<Budgetable[]>(() => []);
@ -132,6 +132,7 @@ export default function App() {
<main className="container mx-auto p-4 max-w-5xl">
{loading}
<Header isEditing={isEditing} setIsEditing={setIsEditing} />
<TotalDisplay total={total} />
<TableWrapper
data={data}
isEditing={isEditing}
@ -144,7 +145,6 @@ export default function App() {
handleDeleteRow={handleDeleteRow}
toggleStatus={toggleStatus}
/>
<TotalDisplay total={total} />
</main>
);
}

View file

@ -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" },
},
);
}
}

View file

@ -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" },
},
);
}
}

View file

@ -21,7 +21,9 @@ const DeleteDialog = ({ onConfirm }: DeleteDialogProps) => (
<DialogTitle>Delete Row</DialogTitle>
<DialogHeader>Are you sure you want to delete this row?</DialogHeader>
<DialogFooter>
<Button onClick={onConfirm}>Yes, delete</Button>
<Button onClick={onConfirm} variant="destructive">
Yes, delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View file

@ -13,7 +13,7 @@ const Header = ({ isEditing, setIsEditing }: AppHeaderProps) => (
href="/"
className="text-blue-500 hover:text-blue-600 transition-colors"
onClick={() => setIsEditing(false)}
>
>
Budgetable
</Link>
</h1>

View file

@ -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;

View file

@ -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<Budgetable>(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 ? (
<Link
href={row.link}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 underline"
>
Visit
</Link>
<TooltipProvider delayDuration={150}>
<Tooltip>
<TooltipTrigger className="text-blue-500 underline">
<Link href={row.link} target="_blank" rel="noopener noreferrer">
Visit
</Link>
</TooltipTrigger>
<TooltipContent className="text-white bg-black">
{row.link.replace(/^https?:\/\//, "")}
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
<span className="text-gray-400 italic">No link</span>
)}
@ -125,7 +137,10 @@ const TRow = ({
)}
</TableCell>
<TableCell>
<StatusBadge status={row.status} toggleStatus={() => toggleStatus(row)} />
<StatusBadge
status={row.status}
toggleStatus={() => toggleStatus(row)}
/>
</TableCell>
{isEditing && (
<TableCell>

View file

@ -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[];

View file

@ -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({

View file

@ -1,13 +1,12 @@
import NumberFlow from "@number-flow/react";
interface TotalDisplayProps {
total: number;
}
const TotalDisplay = ({ total }: TotalDisplayProps) => (
<div className="mt-4 text-right font-bold text-lg">
Total: {total.toLocaleString()}
<p className="text-xs ml-1">
({Math.round(total * 1.27).toLocaleString()})
</p>
<div className="mx-2 text-left font-bold text-lg">
Total: <NumberFlow value={total} />
</div>
);

View file

@ -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<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
</TooltipPrimitive.Portal>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

View file

@ -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),
);
};
};