mirror of
https://github.com/skidoodle/budgetable.git
synced 2026-04-28 15:57:36 +02:00
i forgor
This commit is contained in:
@@ -12,7 +12,7 @@ interface DeleteDialogProps {
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
const DeleteDialog: React.FC<DeleteDialogProps> = ({ onConfirm }) => (
|
||||
const DeleteDialog = ({ onConfirm }: DeleteDialogProps) => (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="destructive">Delete</Button>
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
|
||||
interface AppHeaderProps {
|
||||
isEditing: boolean;
|
||||
setIsEditing: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const Header: React.FC<AppHeaderProps> = ({ isEditing, setIsEditing }) => (
|
||||
const Header = ({ isEditing, setIsEditing }: AppHeaderProps) => (
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h1 className="text-3xl font-bold">Budgetable</h1>
|
||||
<h1 className="text-3xl font-bold">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-blue-500 hover:text-blue-600 transition-colors"
|
||||
onClick={() => setIsEditing(false)}
|
||||
>
|
||||
Budgetable
|
||||
</Link>
|
||||
</h1>
|
||||
<Button variant="ghost" onClick={() => setIsEditing(!isEditing)}>
|
||||
{isEditing ? "Lock" : "Unlock"} Editing
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
const LoadingSkeleton: React.FC = () => <Skeleton className="h-10 w-full" />;
|
||||
const LoadingSkeleton = () => <Skeleton className="h-10 w-full" />;
|
||||
|
||||
export default LoadingSkeleton;
|
||||
|
||||
@@ -5,9 +5,9 @@ interface StatusBadgeProps {
|
||||
toggleStatus: () => void;
|
||||
}
|
||||
|
||||
const StatusBadge: React.FC<StatusBadgeProps> = ({ status, toggleStatus }) => (
|
||||
const StatusBadge = ({ status, toggleStatus }: StatusBadgeProps) => (
|
||||
<Badge
|
||||
variant={status === "Paid" ? "destructive" : "secondary"}
|
||||
variant={status === "Paid" ? "paid" : "unpaid"}
|
||||
className="cursor-pointer"
|
||||
onClick={toggleStatus}
|
||||
>
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import { TableRow, TableCell } from "@/components/ui/table";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface Budgetable {
|
||||
id: string;
|
||||
title: string;
|
||||
price: number;
|
||||
link: string;
|
||||
note?: string;
|
||||
status: "Paid" | "Unpaid";
|
||||
}
|
||||
import { Budgetable } from '@/lib/utils';
|
||||
|
||||
interface TNewRowProps {
|
||||
newRow: Budgetable;
|
||||
|
||||
+109
-104
@@ -3,15 +3,8 @@ import { TableRow, TableCell } from "@/components/ui/table";
|
||||
import Link from "next/link";
|
||||
import StatusBadge from "@/components/status-badge";
|
||||
import DeleteDialog from "@/components/delete-dialog";
|
||||
|
||||
interface Budgetable {
|
||||
id: string;
|
||||
title: string;
|
||||
price: number;
|
||||
link: string;
|
||||
note?: string;
|
||||
status: "Paid" | "Unpaid";
|
||||
}
|
||||
import { useState } from 'react';
|
||||
import { Budgetable } from '@/lib/utils';
|
||||
|
||||
interface TRowProps {
|
||||
row: Budgetable;
|
||||
@@ -26,7 +19,7 @@ interface TRowProps {
|
||||
toggleStatus: (row: Budgetable) => Promise<void>;
|
||||
}
|
||||
|
||||
const TRow: React.FC<TRowProps> = ({
|
||||
const TRow = ({
|
||||
row,
|
||||
isEditing,
|
||||
setData,
|
||||
@@ -34,101 +27,113 @@ const TRow: React.FC<TRowProps> = ({
|
||||
handleSave,
|
||||
handleDeleteRow,
|
||||
toggleStatus,
|
||||
}) => (
|
||||
<TableRow
|
||||
className={`transition-opacity ${
|
||||
recentlyUpdatedRowId === row.id ? "blur-sm opacity-50" : ""
|
||||
}`}
|
||||
>
|
||||
<TableCell>
|
||||
{isEditing ? (
|
||||
<Input
|
||||
value={row.title}
|
||||
onChange={(e) =>
|
||||
setData((prev) =>
|
||||
prev.map((item) =>
|
||||
item.id === row.id ? { ...item, title: e.target.value } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
onBlur={() => handleSave(row, { ...row })}
|
||||
/>
|
||||
) : (
|
||||
<span>{row.title}</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{isEditing ? (
|
||||
<Input
|
||||
type="number"
|
||||
value={row.price}
|
||||
onChange={(e) =>
|
||||
setData((prev) =>
|
||||
prev.map((item) =>
|
||||
item.id === row.id
|
||||
? { ...item, price: Number.parseFloat(e.target.value) || 0 }
|
||||
: item,
|
||||
),
|
||||
)
|
||||
}
|
||||
onBlur={() => handleSave(row, { ...row })}
|
||||
/>
|
||||
) : (
|
||||
<span>{row.price.toLocaleString()} HUF</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{isEditing ? (
|
||||
<Input
|
||||
value={row.link}
|
||||
onChange={(e) =>
|
||||
setData((prev) =>
|
||||
prev.map((item) =>
|
||||
item.id === row.id ? { ...item, link: e.target.value } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
onBlur={() => handleSave(row, { ...row })}
|
||||
/>
|
||||
) : row.link ? (
|
||||
<Link
|
||||
href={row.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 underline"
|
||||
>
|
||||
Visit
|
||||
</Link>
|
||||
) : (
|
||||
<span className="text-gray-400 italic">No link</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{isEditing ? (
|
||||
<Input
|
||||
value={row.note || ""}
|
||||
onChange={(e) =>
|
||||
setData((prev) =>
|
||||
prev.map((item) =>
|
||||
item.id === row.id ? { ...item, note: e.target.value } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
onBlur={() => handleSave(row, { ...row })}
|
||||
/>
|
||||
) : (
|
||||
<span>{row.note || "-"}</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StatusBadge status={row.status} toggleStatus={() => toggleStatus(row)} />
|
||||
</TableCell>
|
||||
{isEditing && (
|
||||
}: TRowProps) => {
|
||||
const [originalRow, setOriginalRow] = useState<Budgetable>(row);
|
||||
|
||||
const handleInputFocus = () => {
|
||||
setOriginalRow({...row});
|
||||
};
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
className={`transition-opacity ${
|
||||
recentlyUpdatedRowId === row.id ? "blur-sm opacity-50" : ""
|
||||
}`}
|
||||
>
|
||||
<TableCell>
|
||||
<DeleteDialog onConfirm={() => handleDeleteRow(row.id)} />
|
||||
{isEditing ? (
|
||||
<Input
|
||||
value={row.title}
|
||||
onFocus={handleInputFocus}
|
||||
onChange={(e) =>
|
||||
setData((prev) =>
|
||||
prev.map((item) =>
|
||||
item.id === row.id ? { ...item, title: e.target.value } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
onBlur={() => handleSave(row, originalRow)}
|
||||
/>
|
||||
) : (
|
||||
<span>{row.title}</span>
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
);
|
||||
<TableCell>
|
||||
{isEditing ? (
|
||||
<Input
|
||||
type="number"
|
||||
value={row.price}
|
||||
onFocus={handleInputFocus}
|
||||
onChange={(e) =>
|
||||
setData((prev) =>
|
||||
prev.map((item) =>
|
||||
item.id === row.id
|
||||
? { ...item, price: Number.parseFloat(e.target.value) || 0 }
|
||||
: item,
|
||||
),
|
||||
)
|
||||
}
|
||||
onBlur={() => handleSave(row, originalRow)}
|
||||
/>
|
||||
) : (
|
||||
<span>{row.price.toLocaleString()} HUF</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{isEditing ? (
|
||||
<Input
|
||||
value={row.link}
|
||||
onFocus={handleInputFocus}
|
||||
onChange={(e) =>
|
||||
setData((prev) =>
|
||||
prev.map((item) =>
|
||||
item.id === row.id ? { ...item, link: e.target.value } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
onBlur={() => handleSave(row, originalRow)}
|
||||
/>
|
||||
) : row.link ? (
|
||||
<Link
|
||||
href={row.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 underline"
|
||||
>
|
||||
Visit
|
||||
</Link>
|
||||
) : (
|
||||
<span className="text-gray-400 italic">No link</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{isEditing ? (
|
||||
<Input
|
||||
value={row.note || ""}
|
||||
onFocus={handleInputFocus}
|
||||
onChange={(e) =>
|
||||
setData((prev) =>
|
||||
prev.map((item) =>
|
||||
item.id === row.id ? { ...item, note: e.target.value } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
onBlur={() => handleSave(row, originalRow)}
|
||||
/>
|
||||
) : (
|
||||
<span>{row.note || "-"}</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StatusBadge status={row.status} toggleStatus={() => toggleStatus(row)} />
|
||||
</TableCell>
|
||||
{isEditing && (
|
||||
<TableCell>
|
||||
<DeleteDialog onConfirm={() => handleDeleteRow(row.id)} />
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default TRow;
|
||||
|
||||
@@ -7,15 +7,7 @@ import {
|
||||
} from "@/components/ui/table";
|
||||
import TRow from "@/components/table-row";
|
||||
import TNewRow from "@/components/table-new-row";
|
||||
|
||||
interface Budgetable {
|
||||
id: string;
|
||||
title: string;
|
||||
price: number;
|
||||
link: string;
|
||||
note?: string;
|
||||
status: "Paid" | "Unpaid";
|
||||
}
|
||||
import { Budgetable } from '@/lib/utils';
|
||||
|
||||
interface TWrapperProps {
|
||||
data: Budgetable[];
|
||||
@@ -33,7 +25,7 @@ interface TWrapperProps {
|
||||
toggleStatus: (row: Budgetable) => Promise<void>;
|
||||
}
|
||||
|
||||
const TWrapper: React.FC<TWrapperProps> = ({
|
||||
const TWrapper = ({
|
||||
data,
|
||||
isEditing,
|
||||
setData,
|
||||
@@ -44,7 +36,7 @@ const TWrapper: React.FC<TWrapperProps> = ({
|
||||
handleAddRow,
|
||||
handleDeleteRow,
|
||||
toggleStatus,
|
||||
}) => (
|
||||
}: TWrapperProps) => (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import type * as React from "react";
|
||||
import { ComponentProps } from "react";
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NextThemesProvider>) {
|
||||
}: ComponentProps<typeof NextThemesProvider>) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ interface TotalDisplayProps {
|
||||
total: number;
|
||||
}
|
||||
|
||||
const TotalDisplay: React.FC<TotalDisplayProps> = ({ total }) => (
|
||||
const TotalDisplay = ({ total }: TotalDisplayProps) => (
|
||||
<div className="mt-4 text-right font-bold text-lg">
|
||||
Total: {total.toLocaleString()}
|
||||
<p className="text-xs ml-1">
|
||||
|
||||
@@ -15,6 +15,8 @@ const badgeVariants = cva(
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
paid: "border-transparent bg-green-500 text-green-50",
|
||||
unpaid: "border-transparent bg-red-500 text-red-50",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
Reference in New Issue
Block a user