refactor: standardize quotation marks across all files and improve code consistency
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
export default function Loading() {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,91 +1,125 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import { useState, useMemo } from "react"
|
||||
import { Sidebar } from "@/components/dashboard/sidebar"
|
||||
import { useBankingData } from "@/lib/hooks"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { useState, useMemo } from "react";
|
||||
import { Sidebar } from "@/components/dashboard/sidebar";
|
||||
import { useBankingData } from "@/lib/hooks";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSeparator,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { OFXImportDialog } from "@/components/import/ofx-import-dialog"
|
||||
import { CategoryIcon } from "@/components/ui/category-icon"
|
||||
import { Search, CheckCircle2, Circle, MoreVertical, Tags, Upload, RefreshCw, ArrowUpDown, Check } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { OFXImportDialog } from "@/components/import/ofx-import-dialog";
|
||||
import { CategoryIcon } from "@/components/ui/category-icon";
|
||||
import {
|
||||
Search,
|
||||
CheckCircle2,
|
||||
Circle,
|
||||
MoreVertical,
|
||||
Tags,
|
||||
Upload,
|
||||
RefreshCw,
|
||||
ArrowUpDown,
|
||||
Check,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type SortField = "date" | "amount" | "description"
|
||||
type SortOrder = "asc" | "desc"
|
||||
type SortField = "date" | "amount" | "description";
|
||||
type SortOrder = "asc" | "desc";
|
||||
|
||||
export default function TransactionsPage() {
|
||||
const { data, isLoading, refresh, update } = useBankingData()
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [selectedAccount, setSelectedAccount] = useState<string>("all")
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>("all")
|
||||
const [showReconciled, setShowReconciled] = useState<string>("all")
|
||||
const [sortField, setSortField] = useState<SortField>("date")
|
||||
const [sortOrder, setSortOrder] = useState<SortOrder>("desc")
|
||||
const [selectedTransactions, setSelectedTransactions] = useState<Set<string>>(new Set())
|
||||
const { data, isLoading, refresh, update } = useBankingData();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [selectedAccount, setSelectedAccount] = useState<string>("all");
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>("all");
|
||||
const [showReconciled, setShowReconciled] = useState<string>("all");
|
||||
const [sortField, setSortField] = useState<SortField>("date");
|
||||
const [sortOrder, setSortOrder] = useState<SortOrder>("desc");
|
||||
const [selectedTransactions, setSelectedTransactions] = useState<Set<string>>(
|
||||
new Set(),
|
||||
);
|
||||
|
||||
const filteredTransactions = useMemo(() => {
|
||||
if (!data) return []
|
||||
if (!data) return [];
|
||||
|
||||
let transactions = [...data.transactions]
|
||||
let transactions = [...data.transactions];
|
||||
|
||||
// Filter by search
|
||||
if (searchQuery) {
|
||||
const query = searchQuery.toLowerCase()
|
||||
const query = searchQuery.toLowerCase();
|
||||
transactions = transactions.filter(
|
||||
(t) => t.description.toLowerCase().includes(query) || t.memo?.toLowerCase().includes(query),
|
||||
)
|
||||
(t) =>
|
||||
t.description.toLowerCase().includes(query) ||
|
||||
t.memo?.toLowerCase().includes(query),
|
||||
);
|
||||
}
|
||||
|
||||
// Filter by account
|
||||
if (selectedAccount !== "all") {
|
||||
transactions = transactions.filter((t) => t.accountId === selectedAccount)
|
||||
transactions = transactions.filter(
|
||||
(t) => t.accountId === selectedAccount,
|
||||
);
|
||||
}
|
||||
|
||||
// Filter by category
|
||||
if (selectedCategory !== "all") {
|
||||
if (selectedCategory === "uncategorized") {
|
||||
transactions = transactions.filter((t) => !t.categoryId)
|
||||
transactions = transactions.filter((t) => !t.categoryId);
|
||||
} else {
|
||||
transactions = transactions.filter((t) => t.categoryId === selectedCategory)
|
||||
transactions = transactions.filter(
|
||||
(t) => t.categoryId === selectedCategory,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by reconciliation status
|
||||
if (showReconciled !== "all") {
|
||||
const isReconciled = showReconciled === "reconciled"
|
||||
transactions = transactions.filter((t) => t.isReconciled === isReconciled)
|
||||
const isReconciled = showReconciled === "reconciled";
|
||||
transactions = transactions.filter(
|
||||
(t) => t.isReconciled === isReconciled,
|
||||
);
|
||||
}
|
||||
|
||||
// Sort
|
||||
transactions.sort((a, b) => {
|
||||
let comparison = 0
|
||||
let comparison = 0;
|
||||
switch (sortField) {
|
||||
case "date":
|
||||
comparison = new Date(a.date).getTime() - new Date(b.date).getTime()
|
||||
break
|
||||
comparison = new Date(a.date).getTime() - new Date(b.date).getTime();
|
||||
break;
|
||||
case "amount":
|
||||
comparison = a.amount - b.amount
|
||||
break
|
||||
comparison = a.amount - b.amount;
|
||||
break;
|
||||
case "description":
|
||||
comparison = a.description.localeCompare(b.description)
|
||||
break
|
||||
comparison = a.description.localeCompare(b.description);
|
||||
break;
|
||||
}
|
||||
return sortOrder === "asc" ? comparison : -comparison
|
||||
})
|
||||
return sortOrder === "asc" ? comparison : -comparison;
|
||||
});
|
||||
|
||||
return transactions
|
||||
}, [data, searchQuery, selectedAccount, selectedCategory, showReconciled, sortField, sortOrder])
|
||||
return transactions;
|
||||
}, [
|
||||
data,
|
||||
searchQuery,
|
||||
selectedAccount,
|
||||
selectedCategory,
|
||||
showReconciled,
|
||||
sortField,
|
||||
sortOrder,
|
||||
]);
|
||||
|
||||
if (isLoading || !data) {
|
||||
return (
|
||||
@@ -95,78 +129,80 @@ export default function TransactionsPage() {
|
||||
<RefreshCw className="w-8 h-8 animate-spin text-muted-foreground" />
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat("fr-FR", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(amount)
|
||||
}
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
return new Date(dateStr).toLocaleDateString("fr-FR", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const toggleReconciled = (transactionId: string) => {
|
||||
const updatedTransactions = data.transactions.map((t) =>
|
||||
t.id === transactionId ? { ...t, isReconciled: !t.isReconciled } : t,
|
||||
)
|
||||
update({ ...data, transactions: updatedTransactions })
|
||||
}
|
||||
);
|
||||
update({ ...data, transactions: updatedTransactions });
|
||||
};
|
||||
|
||||
const setCategory = (transactionId: string, categoryId: string | null) => {
|
||||
const updatedTransactions = data.transactions.map((t) => (t.id === transactionId ? { ...t, categoryId } : t))
|
||||
update({ ...data, transactions: updatedTransactions })
|
||||
}
|
||||
const updatedTransactions = data.transactions.map((t) =>
|
||||
t.id === transactionId ? { ...t, categoryId } : t,
|
||||
);
|
||||
update({ ...data, transactions: updatedTransactions });
|
||||
};
|
||||
|
||||
const bulkReconcile = (reconciled: boolean) => {
|
||||
const updatedTransactions = data.transactions.map((t) =>
|
||||
selectedTransactions.has(t.id) ? { ...t, isReconciled: reconciled } : t,
|
||||
)
|
||||
update({ ...data, transactions: updatedTransactions })
|
||||
setSelectedTransactions(new Set())
|
||||
}
|
||||
);
|
||||
update({ ...data, transactions: updatedTransactions });
|
||||
setSelectedTransactions(new Set());
|
||||
};
|
||||
|
||||
const bulkSetCategory = (categoryId: string | null) => {
|
||||
const updatedTransactions = data.transactions.map((t) =>
|
||||
selectedTransactions.has(t.id) ? { ...t, categoryId } : t,
|
||||
)
|
||||
update({ ...data, transactions: updatedTransactions })
|
||||
setSelectedTransactions(new Set())
|
||||
}
|
||||
);
|
||||
update({ ...data, transactions: updatedTransactions });
|
||||
setSelectedTransactions(new Set());
|
||||
};
|
||||
|
||||
const toggleSelectAll = () => {
|
||||
if (selectedTransactions.size === filteredTransactions.length) {
|
||||
setSelectedTransactions(new Set())
|
||||
setSelectedTransactions(new Set());
|
||||
} else {
|
||||
setSelectedTransactions(new Set(filteredTransactions.map((t) => t.id)))
|
||||
setSelectedTransactions(new Set(filteredTransactions.map((t) => t.id)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const toggleSelectTransaction = (id: string) => {
|
||||
const newSelected = new Set(selectedTransactions)
|
||||
const newSelected = new Set(selectedTransactions);
|
||||
if (newSelected.has(id)) {
|
||||
newSelected.delete(id)
|
||||
newSelected.delete(id);
|
||||
} else {
|
||||
newSelected.add(id)
|
||||
newSelected.add(id);
|
||||
}
|
||||
setSelectedTransactions(newSelected)
|
||||
}
|
||||
setSelectedTransactions(newSelected);
|
||||
};
|
||||
|
||||
const getCategory = (categoryId: string | null) => {
|
||||
if (!categoryId) return null
|
||||
return data.categories.find((c) => c.id === categoryId)
|
||||
}
|
||||
if (!categoryId) return null;
|
||||
return data.categories.find((c) => c.id === categoryId);
|
||||
};
|
||||
|
||||
const getAccount = (accountId: string) => {
|
||||
return data.accounts.find((a) => a.id === accountId)
|
||||
}
|
||||
return data.accounts.find((a) => a.id === accountId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-background">
|
||||
@@ -175,9 +211,12 @@ export default function TransactionsPage() {
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-foreground">Transactions</h1>
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
Transactions
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{filteredTransactions.length} transaction{filteredTransactions.length > 1 ? "s" : ""}
|
||||
{filteredTransactions.length} transaction
|
||||
{filteredTransactions.length > 1 ? "s" : ""}
|
||||
</p>
|
||||
</div>
|
||||
<OFXImportDialog onImportComplete={refresh}>
|
||||
@@ -204,7 +243,10 @@ export default function TransactionsPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Select value={selectedAccount} onValueChange={setSelectedAccount}>
|
||||
<Select
|
||||
value={selectedAccount}
|
||||
onValueChange={setSelectedAccount}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Compte" />
|
||||
</SelectTrigger>
|
||||
@@ -218,13 +260,18 @@ export default function TransactionsPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
|
||||
<Select
|
||||
value={selectedCategory}
|
||||
onValueChange={setSelectedCategory}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Catégorie" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Toutes catégories</SelectItem>
|
||||
<SelectItem value="uncategorized">Non catégorisé</SelectItem>
|
||||
<SelectItem value="uncategorized">
|
||||
Non catégorisé
|
||||
</SelectItem>
|
||||
{data.categories.map((category) => (
|
||||
<SelectItem key={category.id} value={category.id}>
|
||||
{category.name}
|
||||
@@ -233,7 +280,10 @@ export default function TransactionsPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={showReconciled} onValueChange={setShowReconciled}>
|
||||
<Select
|
||||
value={showReconciled}
|
||||
onValueChange={setShowReconciled}
|
||||
>
|
||||
<SelectTrigger className="w-[160px]">
|
||||
<SelectValue placeholder="Pointage" />
|
||||
</SelectTrigger>
|
||||
@@ -253,13 +303,22 @@ export default function TransactionsPage() {
|
||||
<CardContent className="py-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm font-medium">
|
||||
{selectedTransactions.size} sélectionnée{selectedTransactions.size > 1 ? "s" : ""}
|
||||
{selectedTransactions.size} sélectionnée
|
||||
{selectedTransactions.size > 1 ? "s" : ""}
|
||||
</span>
|
||||
<Button size="sm" variant="outline" onClick={() => bulkReconcile(true)}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => bulkReconcile(true)}
|
||||
>
|
||||
<CheckCircle2 className="w-4 h-4 mr-1" />
|
||||
Pointer
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={() => bulkReconcile(false)}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => bulkReconcile(false)}
|
||||
>
|
||||
<Circle className="w-4 h-4 mr-1" />
|
||||
Dépointer
|
||||
</Button>
|
||||
@@ -271,11 +330,21 @@ export default function TransactionsPage() {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={() => bulkSetCategory(null)}>Aucune catégorie</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => bulkSetCategory(null)}>
|
||||
Aucune catégorie
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
{data.categories.map((cat) => (
|
||||
<DropdownMenuItem key={cat.id} onClick={() => bulkSetCategory(cat.id)}>
|
||||
<CategoryIcon icon={cat.icon} color={cat.color} size={14} className="mr-2" />
|
||||
<DropdownMenuItem
|
||||
key={cat.id}
|
||||
onClick={() => bulkSetCategory(cat.id)}
|
||||
>
|
||||
<CategoryIcon
|
||||
icon={cat.icon}
|
||||
color={cat.color}
|
||||
size={14}
|
||||
className="mr-2"
|
||||
/>
|
||||
{cat.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
@@ -291,7 +360,9 @@ export default function TransactionsPage() {
|
||||
<CardContent className="p-0">
|
||||
{filteredTransactions.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-12">
|
||||
<p className="text-muted-foreground">Aucune transaction trouvée</p>
|
||||
<p className="text-muted-foreground">
|
||||
Aucune transaction trouvée
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
@@ -301,7 +372,8 @@ export default function TransactionsPage() {
|
||||
<th className="p-3 text-left">
|
||||
<Checkbox
|
||||
checked={
|
||||
selectedTransactions.size === filteredTransactions.length &&
|
||||
selectedTransactions.size ===
|
||||
filteredTransactions.length &&
|
||||
filteredTransactions.length > 0
|
||||
}
|
||||
onCheckedChange={toggleSelectAll}
|
||||
@@ -311,10 +383,12 @@ export default function TransactionsPage() {
|
||||
<button
|
||||
onClick={() => {
|
||||
if (sortField === "date") {
|
||||
setSortOrder(sortOrder === "asc" ? "desc" : "asc")
|
||||
setSortOrder(
|
||||
sortOrder === "asc" ? "desc" : "asc",
|
||||
);
|
||||
} else {
|
||||
setSortField("date")
|
||||
setSortOrder("desc")
|
||||
setSortField("date");
|
||||
setSortOrder("desc");
|
||||
}
|
||||
}}
|
||||
className="flex items-center gap-1 text-sm font-medium text-muted-foreground hover:text-foreground"
|
||||
@@ -327,10 +401,12 @@ export default function TransactionsPage() {
|
||||
<button
|
||||
onClick={() => {
|
||||
if (sortField === "description") {
|
||||
setSortOrder(sortOrder === "asc" ? "desc" : "asc")
|
||||
setSortOrder(
|
||||
sortOrder === "asc" ? "desc" : "asc",
|
||||
);
|
||||
} else {
|
||||
setSortField("description")
|
||||
setSortOrder("asc")
|
||||
setSortField("description");
|
||||
setSortOrder("asc");
|
||||
}
|
||||
}}
|
||||
className="flex items-center gap-1 text-sm font-medium text-muted-foreground hover:text-foreground"
|
||||
@@ -339,16 +415,22 @@ export default function TransactionsPage() {
|
||||
<ArrowUpDown className="w-3 h-3" />
|
||||
</button>
|
||||
</th>
|
||||
<th className="p-3 text-left text-sm font-medium text-muted-foreground">Compte</th>
|
||||
<th className="p-3 text-left text-sm font-medium text-muted-foreground">Catégorie</th>
|
||||
<th className="p-3 text-left text-sm font-medium text-muted-foreground">
|
||||
Compte
|
||||
</th>
|
||||
<th className="p-3 text-left text-sm font-medium text-muted-foreground">
|
||||
Catégorie
|
||||
</th>
|
||||
<th className="p-3 text-right">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (sortField === "amount") {
|
||||
setSortOrder(sortOrder === "asc" ? "desc" : "asc")
|
||||
setSortOrder(
|
||||
sortOrder === "asc" ? "desc" : "asc",
|
||||
);
|
||||
} else {
|
||||
setSortField("amount")
|
||||
setSortOrder("desc")
|
||||
setSortField("amount");
|
||||
setSortOrder("desc");
|
||||
}
|
||||
}}
|
||||
className="flex items-center gap-1 text-sm font-medium text-muted-foreground hover:text-foreground ml-auto"
|
||||
@@ -357,35 +439,48 @@ export default function TransactionsPage() {
|
||||
<ArrowUpDown className="w-3 h-3" />
|
||||
</button>
|
||||
</th>
|
||||
<th className="p-3 text-center text-sm font-medium text-muted-foreground">Pointé</th>
|
||||
<th className="p-3 text-center text-sm font-medium text-muted-foreground">
|
||||
Pointé
|
||||
</th>
|
||||
<th className="p-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredTransactions.map((transaction) => {
|
||||
const category = getCategory(transaction.categoryId)
|
||||
const account = getAccount(transaction.accountId)
|
||||
const category = getCategory(transaction.categoryId);
|
||||
const account = getAccount(transaction.accountId);
|
||||
|
||||
return (
|
||||
<tr key={transaction.id} className="border-b border-border last:border-0 hover:bg-muted/50">
|
||||
<tr
|
||||
key={transaction.id}
|
||||
className="border-b border-border last:border-0 hover:bg-muted/50"
|
||||
>
|
||||
<td className="p-3">
|
||||
<Checkbox
|
||||
checked={selectedTransactions.has(transaction.id)}
|
||||
onCheckedChange={() => toggleSelectTransaction(transaction.id)}
|
||||
checked={selectedTransactions.has(
|
||||
transaction.id,
|
||||
)}
|
||||
onCheckedChange={() =>
|
||||
toggleSelectTransaction(transaction.id)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td className="p-3 text-sm text-muted-foreground whitespace-nowrap">
|
||||
{formatDate(transaction.date)}
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<p className="font-medium text-sm">{transaction.description}</p>
|
||||
<p className="font-medium text-sm">
|
||||
{transaction.description}
|
||||
</p>
|
||||
{transaction.memo && (
|
||||
<p className="text-xs text-muted-foreground truncate max-w-[300px]">
|
||||
{transaction.memo}
|
||||
</p>
|
||||
)}
|
||||
</td>
|
||||
<td className="p-3 text-sm text-muted-foreground">{account?.name || "-"}</td>
|
||||
<td className="p-3 text-sm text-muted-foreground">
|
||||
{account?.name || "-"}
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -399,26 +494,49 @@ export default function TransactionsPage() {
|
||||
color: category.color,
|
||||
}}
|
||||
>
|
||||
<CategoryIcon icon={category.icon} color={category.color} size={12} />
|
||||
<CategoryIcon
|
||||
icon={category.icon}
|
||||
color={category.color}
|
||||
size={12}
|
||||
/>
|
||||
{category.name}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="outline" className="text-muted-foreground">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
Non catégorisé
|
||||
</Badge>
|
||||
)}
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={() => setCategory(transaction.id, null)}>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
setCategory(transaction.id, null)
|
||||
}
|
||||
>
|
||||
Aucune catégorie
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
{data.categories.map((cat) => (
|
||||
<DropdownMenuItem key={cat.id} onClick={() => setCategory(transaction.id, cat.id)}>
|
||||
<CategoryIcon icon={cat.icon} color={cat.color} size={14} className="mr-2" />
|
||||
<DropdownMenuItem
|
||||
key={cat.id}
|
||||
onClick={() =>
|
||||
setCategory(transaction.id, cat.id)
|
||||
}
|
||||
>
|
||||
<CategoryIcon
|
||||
icon={cat.icon}
|
||||
color={cat.color}
|
||||
size={14}
|
||||
className="mr-2"
|
||||
/>
|
||||
{cat.name}
|
||||
{transaction.categoryId === cat.id && <Check className="w-4 h-4 ml-auto" />}
|
||||
{transaction.categoryId === cat.id && (
|
||||
<Check className="w-4 h-4 ml-auto" />
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
@@ -427,7 +545,9 @@ export default function TransactionsPage() {
|
||||
<td
|
||||
className={cn(
|
||||
"p-3 text-right font-semibold tabular-nums",
|
||||
transaction.amount >= 0 ? "text-emerald-600" : "text-red-600",
|
||||
transaction.amount >= 0
|
||||
? "text-emerald-600"
|
||||
: "text-red-600",
|
||||
)}
|
||||
>
|
||||
{transaction.amount >= 0 ? "+" : ""}
|
||||
@@ -448,19 +568,29 @@ export default function TransactionsPage() {
|
||||
<td className="p-3">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
>
|
||||
<MoreVertical className="w-4 h-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => toggleReconciled(transaction.id)}>
|
||||
{transaction.isReconciled ? "Dépointer" : "Pointer"}
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
toggleReconciled(transaction.id)
|
||||
}
|
||||
>
|
||||
{transaction.isReconciled
|
||||
? "Dépointer"
|
||||
: "Pointer"}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -471,5 +601,5 @@ export default function TransactionsPage() {
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user