mirror of
https://github.com/elisiariocouto/leggen.git
synced 2025-12-29 05:09:39 +00:00
Compare commits
8 Commits
f7d828f669
...
2025.9.13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7da446fa5 | ||
|
|
5a626b5394 | ||
|
|
d9a39c30ab | ||
|
|
155a48d7dc | ||
|
|
8ab760815c | ||
|
|
2825dba2e9 | ||
|
|
3049a8cd2f | ||
|
|
86891441d6 |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(uv sync:*)",
|
||||
"Bash(uv run pytest:*)",
|
||||
"Bash(git commit:*)",
|
||||
"Bash(ruff check:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(mypy:*)",
|
||||
"WebFetch(domain:localhost)",
|
||||
"Bash(npm create:*)",
|
||||
"Bash(npm install)",
|
||||
"Bash(npm install:*)",
|
||||
"Bash(npx tailwindcss init:*)",
|
||||
"Bash(./node_modules/.bin/tailwindcss:*)",
|
||||
"Bash(npm run build:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -164,3 +164,4 @@ sql/
|
||||
leggen.db
|
||||
*.db
|
||||
config.toml
|
||||
.claude/
|
||||
|
||||
17
.mcp.json
Normal file
17
.mcp.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"shadcn": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"shadcn@latest",
|
||||
"mcp"
|
||||
]
|
||||
},
|
||||
"browsermcp": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"@browsermcp/mcp@latest"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
60
CHANGELOG.md
60
CHANGELOG.md
@@ -1,4 +1,64 @@
|
||||
|
||||
## 2025.9.13 (2025/09/17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **frontend:** Resolve linting issue in skeleton component ([fb310a59](https://github.com/elisiariocouto/leggen/commit/fb310a5953cf51d1cac181529311e76a0f4ea9ee))
|
||||
- **frontend:** Add index signature to PieDataPoint interface. ([81d7d163](https://github.com/elisiariocouto/leggen/commit/81d7d16301dafc62a95f63036819565ffb90ddb5))
|
||||
- **frontend:** Resolve dual scroll and excessive whitespace issues on transactions page. ([8ab76081](https://github.com/elisiariocouto/leggen/commit/8ab760815c9ae072b8c2cb2460e31144b193e0b3))
|
||||
- **frontend:** Remove broken running balance feature in transactions table. ([155a48d7](https://github.com/elisiariocouto/leggen/commit/155a48d7dc86b3f453ba6f8c37edf63c0b76c755))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
- **frontend:** Complete shadcn migration of skeleton and styling components ([c83386b1](https://github.com/elisiariocouto/leggen/commit/c83386b1d5b165910abe8b391ca483e5b48cd35f))
|
||||
- **frontend:** Add comprehensive PWA capabilities with dynamic theme support ([86891441](https://github.com/elisiariocouto/leggen/commit/86891441d65e13757f343cabc39ccdb3ca6adc75))
|
||||
- **frontend:** Add PWA install prompts, update notifications, and app shortcuts ([3049a8cd](https://github.com/elisiariocouto/leggen/commit/3049a8cd2fa80c14f970884fb14df2ab88c418dd))
|
||||
- **frontend:** Update brand identity with new logo and color scheme. ([2825dba2](https://github.com/elisiariocouto/leggen/commit/2825dba2e944b3fe31aaa33127b770e7474ce021))
|
||||
- **frontend:** Update analytics cards to match home page design consistency. ([d9a39c30](https://github.com/elisiariocouto/leggen/commit/d9a39c30ab1248a9fdacff068d401c3daff3f6a5))
|
||||
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Enable browsermcp and shadcn MCP servers. ([5a626b53](https://github.com/elisiariocouto/leggen/commit/5a626b53947f7e2d1544faf3ee06f8a0f1fb5d7a))
|
||||
|
||||
|
||||
### Refactor
|
||||
|
||||
- **frontend:** Replace LoadingSpinner with shadcn skeleton components. ([84e609a7](https://github.com/elisiariocouto/leggen/commit/84e609a774ddc0caf9f84eaf1e8cdce021c82785))
|
||||
|
||||
|
||||
|
||||
## 2025.9.13 (2025/09/17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **frontend:** Resolve linting issue in skeleton component ([fb310a59](https://github.com/elisiariocouto/leggen/commit/fb310a5953cf51d1cac181529311e76a0f4ea9ee))
|
||||
- **frontend:** Add index signature to PieDataPoint interface. ([81d7d163](https://github.com/elisiariocouto/leggen/commit/81d7d16301dafc62a95f63036819565ffb90ddb5))
|
||||
- **frontend:** Resolve dual scroll and excessive whitespace issues on transactions page. ([8ab76081](https://github.com/elisiariocouto/leggen/commit/8ab760815c9ae072b8c2cb2460e31144b193e0b3))
|
||||
- **frontend:** Remove broken running balance feature in transactions table. ([155a48d7](https://github.com/elisiariocouto/leggen/commit/155a48d7dc86b3f453ba6f8c37edf63c0b76c755))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
- **frontend:** Complete shadcn migration of skeleton and styling components ([c83386b1](https://github.com/elisiariocouto/leggen/commit/c83386b1d5b165910abe8b391ca483e5b48cd35f))
|
||||
- **frontend:** Add comprehensive PWA capabilities with dynamic theme support ([86891441](https://github.com/elisiariocouto/leggen/commit/86891441d65e13757f343cabc39ccdb3ca6adc75))
|
||||
- **frontend:** Add PWA install prompts, update notifications, and app shortcuts ([3049a8cd](https://github.com/elisiariocouto/leggen/commit/3049a8cd2fa80c14f970884fb14df2ab88c418dd))
|
||||
- **frontend:** Update brand identity with new logo and color scheme. ([2825dba2](https://github.com/elisiariocouto/leggen/commit/2825dba2e944b3fe31aaa33127b770e7474ce021))
|
||||
- **frontend:** Update analytics cards to match home page design consistency. ([d9a39c30](https://github.com/elisiariocouto/leggen/commit/d9a39c30ab1248a9fdacff068d401c3daff3f6a5))
|
||||
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Enable browsermcp and shadcn MCP servers. ([5a626b53](https://github.com/elisiariocouto/leggen/commit/5a626b53947f7e2d1544faf3ee06f8a0f1fb5d7a))
|
||||
|
||||
|
||||
### Refactor
|
||||
|
||||
- **frontend:** Replace LoadingSpinner with shadcn skeleton components. ([84e609a7](https://github.com/elisiariocouto/leggen/commit/84e609a774ddc0caf9f84eaf1e8cdce021c82785))
|
||||
|
||||
|
||||
|
||||
## 2025.9.12 (2025/09/15)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": ["Bash(find:*)"],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import { DataTablePagination } from "./ui/data-table-pagination";
|
||||
import { Card, CardContent } from "./ui/card";
|
||||
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
|
||||
import { Button } from "./ui/button";
|
||||
import type { Account, Transaction, ApiResponse, Balance } from "../types/api";
|
||||
import type { Account, Transaction, ApiResponse } from "../types/api";
|
||||
|
||||
export default function TransactionsTable() {
|
||||
// Filter state consolidated into a single object
|
||||
@@ -47,7 +47,6 @@ export default function TransactionsTable() {
|
||||
const [showRawModal, setShowRawModal] = useState(false);
|
||||
const [selectedTransaction, setSelectedTransaction] =
|
||||
useState<Transaction | null>(null);
|
||||
const [showRunningBalance, setShowRunningBalance] = useState(true);
|
||||
|
||||
// Pagination state
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
@@ -102,11 +101,6 @@ export default function TransactionsTable() {
|
||||
queryFn: apiClient.getAccounts,
|
||||
});
|
||||
|
||||
const { data: balances } = useQuery<Balance[]>({
|
||||
queryKey: ["balances"],
|
||||
queryFn: apiClient.getBalances,
|
||||
enabled: showRunningBalance,
|
||||
});
|
||||
|
||||
const {
|
||||
data: transactionsResponse,
|
||||
@@ -185,53 +179,6 @@ export default function TransactionsTable() {
|
||||
filterState.minAmount ||
|
||||
filterState.maxAmount;
|
||||
|
||||
// Calculate running balances
|
||||
const calculateRunningBalances = (transactions: Transaction[]) => {
|
||||
if (!balances || !showRunningBalance) return {};
|
||||
|
||||
const runningBalances: { [key: string]: number } = {};
|
||||
const accountBalanceMap = new Map<string, number>();
|
||||
|
||||
// Create a map of account current balances
|
||||
balances.forEach((balance) => {
|
||||
if (balance.balance_type === "expected") {
|
||||
accountBalanceMap.set(balance.account_id, balance.balance_amount);
|
||||
}
|
||||
});
|
||||
|
||||
// Group transactions by account
|
||||
const transactionsByAccount = new Map<string, Transaction[]>();
|
||||
transactions.forEach((txn) => {
|
||||
if (!transactionsByAccount.has(txn.account_id)) {
|
||||
transactionsByAccount.set(txn.account_id, []);
|
||||
}
|
||||
transactionsByAccount.get(txn.account_id)!.push(txn);
|
||||
});
|
||||
|
||||
// Calculate running balance for each account
|
||||
transactionsByAccount.forEach((accountTransactions, accountId) => {
|
||||
const currentBalance = accountBalanceMap.get(accountId) || 0;
|
||||
let runningBalance = currentBalance;
|
||||
|
||||
// Sort transactions by date (newest first) to work backwards
|
||||
const sortedTransactions = [...accountTransactions].sort(
|
||||
(a, b) =>
|
||||
new Date(b.transaction_date).getTime() -
|
||||
new Date(a.transaction_date).getTime(),
|
||||
);
|
||||
|
||||
// Calculate running balance by working backwards from current balance
|
||||
sortedTransactions.forEach((txn) => {
|
||||
runningBalances[`${txn.account_id}-${txn.transaction_id}`] =
|
||||
runningBalance;
|
||||
runningBalance -= txn.transaction_value;
|
||||
});
|
||||
});
|
||||
|
||||
return runningBalances;
|
||||
};
|
||||
|
||||
const runningBalances = calculateRunningBalances(transactions);
|
||||
|
||||
// Define columns
|
||||
const columns: ColumnDef<Transaction>[] = [
|
||||
@@ -308,29 +255,6 @@ export default function TransactionsTable() {
|
||||
},
|
||||
sortingFn: "basic",
|
||||
},
|
||||
...(showRunningBalance
|
||||
? [
|
||||
{
|
||||
id: "running_balance",
|
||||
header: "Running Balance",
|
||||
cell: ({ row }: { row: { original: Transaction } }) => {
|
||||
const transaction = row.original;
|
||||
const balanceKey = `${transaction.account_id}-${transaction.transaction_id}`;
|
||||
const balance = runningBalances[balanceKey];
|
||||
|
||||
if (balance === undefined) return null;
|
||||
|
||||
return (
|
||||
<div className="text-right">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
{formatCurrency(balance, transaction.transaction_currency)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
accessorKey: "transaction_date",
|
||||
header: "Date",
|
||||
@@ -438,7 +362,7 @@ export default function TransactionsTable() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-6 max-w-full">
|
||||
{/* New FilterBar */}
|
||||
<FilterBar
|
||||
filterState={filterState}
|
||||
@@ -446,10 +370,6 @@ export default function TransactionsTable() {
|
||||
onClearFilters={handleClearFilters}
|
||||
accounts={accounts}
|
||||
isSearchLoading={isSearchLoading}
|
||||
showRunningBalance={showRunningBalance}
|
||||
onToggleRunningBalance={() =>
|
||||
setShowRunningBalance(!showRunningBalance)
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Results Summary */}
|
||||
@@ -485,93 +405,91 @@ export default function TransactionsTable() {
|
||||
</Card>
|
||||
|
||||
{/* Responsive Table/Cards */}
|
||||
<Card className="overflow-hidden">
|
||||
<Card>
|
||||
{/* Desktop Table View (hidden on mobile) */}
|
||||
<div className="hidden md:block">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-border">
|
||||
<thead className="bg-muted/50">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<th
|
||||
key={header.id}
|
||||
className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider cursor-pointer hover:bg-muted"
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
<table className="min-w-full divide-y divide-border">
|
||||
<thead className="bg-muted/50">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<th
|
||||
key={header.id}
|
||||
className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider cursor-pointer hover:bg-muted"
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
>
|
||||
<div className="flex items-center space-x-1">
|
||||
<span>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</span>
|
||||
{header.column.getCanSort() && (
|
||||
<div className="flex flex-col">
|
||||
<ChevronUp
|
||||
className={`h-3 w-3 ${
|
||||
header.column.getIsSorted() === "asc"
|
||||
? "text-primary"
|
||||
: "text-muted-foreground"
|
||||
}`}
|
||||
/>
|
||||
<ChevronDown
|
||||
className={`h-3 w-3 -mt-1 ${
|
||||
header.column.getIsSorted() === "desc"
|
||||
? "text-primary"
|
||||
: "text-muted-foreground"
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody className="bg-card divide-y divide-border">
|
||||
{table.getRowModel().rows.length === 0 ? (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={columns.length}
|
||||
className="px-6 py-12 text-center"
|
||||
>
|
||||
<div className="text-muted-foreground mb-4">
|
||||
<TrendingUp className="h-12 w-12 mx-auto" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-foreground mb-2">
|
||||
No transactions found
|
||||
</h3>
|
||||
<p className="text-muted-foreground">
|
||||
{hasActiveFilters
|
||||
? "Try adjusting your filters to see more results."
|
||||
: "No transactions are available for the selected criteria."}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<tr key={row.id} className="hover:bg-muted/50">
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className="px-6 py-4 whitespace-nowrap"
|
||||
>
|
||||
<div className="flex items-center space-x-1">
|
||||
<span>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</span>
|
||||
{header.column.getCanSort() && (
|
||||
<div className="flex flex-col">
|
||||
<ChevronUp
|
||||
className={`h-3 w-3 ${
|
||||
header.column.getIsSorted() === "asc"
|
||||
? "text-primary"
|
||||
: "text-muted-foreground"
|
||||
}`}
|
||||
/>
|
||||
<ChevronDown
|
||||
className={`h-3 w-3 -mt-1 ${
|
||||
header.column.getIsSorted() === "desc"
|
||||
? "text-primary"
|
||||
: "text-muted-foreground"
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody className="bg-card divide-y divide-border">
|
||||
{table.getRowModel().rows.length === 0 ? (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={columns.length}
|
||||
className="px-6 py-12 text-center"
|
||||
>
|
||||
<div className="text-muted-foreground mb-4">
|
||||
<TrendingUp className="h-12 w-12 mx-auto" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-foreground mb-2">
|
||||
No transactions found
|
||||
</h3>
|
||||
<p className="text-muted-foreground">
|
||||
{hasActiveFilters
|
||||
? "Try adjusting your filters to see more results."
|
||||
: "No transactions are available for the selected criteria."}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<tr key={row.id} className="hover:bg-muted/50">
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className="px-6 py-4 whitespace-nowrap"
|
||||
>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Mobile Card View (visible only on mobile) */}
|
||||
@@ -671,17 +589,6 @@ export default function TransactionsTable() {
|
||||
transaction.transaction_currency,
|
||||
)}
|
||||
</p>
|
||||
{showRunningBalance && (
|
||||
<p className="text-xs text-muted-foreground mb-1">
|
||||
Balance:{" "}
|
||||
{formatCurrency(
|
||||
runningBalances[
|
||||
`${transaction.account_id}-${transaction.transaction_id}`
|
||||
] || 0,
|
||||
transaction.transaction_currency,
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleViewRaw(transaction)}
|
||||
className="inline-flex items-center px-2 py-1 text-xs bg-muted text-muted-foreground rounded hover:bg-accent transition-colors"
|
||||
|
||||
@@ -12,6 +12,7 @@ interface StatCardProps {
|
||||
isPositive: boolean;
|
||||
};
|
||||
className?: string;
|
||||
iconColor?: "green" | "blue" | "red" | "purple" | "orange" | "default";
|
||||
}
|
||||
|
||||
export default function StatCard({
|
||||
@@ -21,43 +22,58 @@ export default function StatCard({
|
||||
icon: Icon,
|
||||
trend,
|
||||
className,
|
||||
iconColor = "default",
|
||||
}: StatCardProps) {
|
||||
return (
|
||||
<Card className={cn(className)}>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<Icon className="h-8 w-8 text-primary" />
|
||||
</div>
|
||||
<div className="ml-5 w-0 flex-1">
|
||||
<dl>
|
||||
<dt className="text-sm font-medium text-muted-foreground truncate">
|
||||
{title}
|
||||
</dt>
|
||||
<dd className="flex items-baseline">
|
||||
<div className="text-2xl font-semibold text-foreground">
|
||||
{value}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">
|
||||
{title}
|
||||
</p>
|
||||
<div className="flex items-baseline">
|
||||
<p className="text-2xl font-bold text-foreground">
|
||||
{value}
|
||||
</p>
|
||||
{trend && (
|
||||
<div
|
||||
className={cn(
|
||||
"ml-2 flex items-baseline text-sm font-semibold",
|
||||
trend.isPositive
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-red-600 dark:text-red-400",
|
||||
)}
|
||||
>
|
||||
{trend.isPositive ? "+" : ""}
|
||||
{trend.value}%
|
||||
</div>
|
||||
{trend && (
|
||||
<div
|
||||
className={cn(
|
||||
"ml-2 flex items-baseline text-sm font-semibold",
|
||||
trend.isPositive
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-red-600 dark:text-red-400",
|
||||
)}
|
||||
>
|
||||
{trend.isPositive ? "+" : ""}
|
||||
{trend.value}%
|
||||
</div>
|
||||
)}
|
||||
</dd>
|
||||
{subtitle && (
|
||||
<dd className="text-sm text-muted-foreground mt-1">
|
||||
{subtitle}
|
||||
</dd>
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
{subtitle && (
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className={cn(
|
||||
"p-3 rounded-full",
|
||||
iconColor === "green" && "bg-green-100 dark:bg-green-900/20",
|
||||
iconColor === "blue" && "bg-blue-100 dark:bg-blue-900/20",
|
||||
iconColor === "red" && "bg-red-100 dark:bg-red-900/20",
|
||||
iconColor === "purple" && "bg-purple-100 dark:bg-purple-900/20",
|
||||
iconColor === "orange" && "bg-orange-100 dark:bg-orange-900/20",
|
||||
iconColor === "default" && "bg-muted"
|
||||
)}>
|
||||
<Icon className={cn(
|
||||
"h-6 w-6",
|
||||
iconColor === "green" && "text-green-600",
|
||||
iconColor === "blue" && "text-blue-600",
|
||||
iconColor === "red" && "text-red-600",
|
||||
iconColor === "purple" && "text-purple-600",
|
||||
iconColor === "orange" && "text-orange-600",
|
||||
iconColor === "default" && "text-muted-foreground"
|
||||
)} />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -23,8 +23,6 @@ export interface FilterBarProps {
|
||||
onClearFilters: () => void;
|
||||
accounts?: Account[];
|
||||
isSearchLoading?: boolean;
|
||||
showRunningBalance: boolean;
|
||||
onToggleRunningBalance: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -34,8 +32,6 @@ export function FilterBar({
|
||||
onClearFilters,
|
||||
accounts,
|
||||
isSearchLoading = false,
|
||||
showRunningBalance,
|
||||
onToggleRunningBalance,
|
||||
className,
|
||||
}: FilterBarProps) {
|
||||
const hasActiveFilters =
|
||||
@@ -59,13 +55,6 @@ export function FilterBar({
|
||||
<h3 className="text-lg font-semibold text-card-foreground">
|
||||
Transactions
|
||||
</h3>
|
||||
<Button
|
||||
onClick={onToggleRunningBalance}
|
||||
variant={showRunningBalance ? "default" : "outline"}
|
||||
size="sm"
|
||||
>
|
||||
Balance
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Primary Filters Row */}
|
||||
|
||||
@@ -23,7 +23,7 @@ function RootLayout() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-background">
|
||||
<div className="flex min-h-screen bg-background">
|
||||
<Sidebar sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
|
||||
|
||||
{/* Mobile overlay */}
|
||||
@@ -34,18 +34,18 @@ function RootLayout() {
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col flex-1 overflow-hidden">
|
||||
<div className="flex flex-col flex-1">
|
||||
<Header setSidebarOpen={setSidebarOpen} />
|
||||
<main className="flex-1 overflow-y-auto p-6">
|
||||
<main className="flex-1 p-6">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{/* PWA Prompts */}
|
||||
<PWAInstallPrompt onInstall={handlePWAInstall} />
|
||||
<PWAUpdatePrompt
|
||||
updateAvailable={updateAvailable}
|
||||
onUpdate={handlePWAUpdate}
|
||||
<PWAUpdatePrompt
|
||||
updateAvailable={updateAvailable}
|
||||
onUpdate={handlePWAUpdate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -80,20 +80,21 @@ function AnalyticsDashboard() {
|
||||
value={stats?.total_transactions || 0}
|
||||
subtitle={`Last ${stats?.period_days || 0} days`}
|
||||
icon={Activity}
|
||||
iconColor="blue"
|
||||
/>
|
||||
<StatCard
|
||||
title="Total Income"
|
||||
value={`€${(stats?.total_income || 0).toLocaleString()}`}
|
||||
subtitle="Inflows this period"
|
||||
icon={TrendingUp}
|
||||
className="border-green-200"
|
||||
iconColor="green"
|
||||
/>
|
||||
<StatCard
|
||||
title="Total Expenses"
|
||||
value={`€${(stats?.total_expenses || 0).toLocaleString()}`}
|
||||
subtitle="Outflows this period"
|
||||
icon={TrendingDown}
|
||||
className="border-red-200"
|
||||
iconColor="red"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -104,23 +105,21 @@ function AnalyticsDashboard() {
|
||||
value={`€${(stats?.net_change || 0).toLocaleString()}`}
|
||||
subtitle="Income minus expenses"
|
||||
icon={CreditCard}
|
||||
className={
|
||||
(stats?.net_change || 0) >= 0
|
||||
? "border-green-200"
|
||||
: "border-red-200"
|
||||
}
|
||||
iconColor={(stats?.net_change || 0) >= 0 ? "green" : "red"}
|
||||
/>
|
||||
<StatCard
|
||||
title="Average Transaction"
|
||||
value={`€${Math.abs(stats?.average_transaction || 0).toLocaleString()}`}
|
||||
subtitle="Per transaction"
|
||||
icon={Activity}
|
||||
iconColor="purple"
|
||||
/>
|
||||
<StatCard
|
||||
title="Active Accounts"
|
||||
value={stats?.accounts_included || 0}
|
||||
subtitle="With recent activity"
|
||||
icon={Users}
|
||||
iconColor="orange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "leggen"
|
||||
version = "2025.9.12"
|
||||
version = "2025.9.13"
|
||||
description = "An Open Banking CLI"
|
||||
authors = [{ name = "Elisiário Couto", email = "elisiario@couto.io" }]
|
||||
requires-python = "~=3.13.0"
|
||||
|
||||
Reference in New Issue
Block a user