add annual
This commit is contained in:
parent
42b3c0950e
commit
65014ef907
@ -1,6 +1,170 @@
|
||||
import { NextRequest, NextResponse } from "next/server"
|
||||
import { auth } from "@/auth"
|
||||
import { db } from "@/lib/db"
|
||||
import { z } from "zod"
|
||||
|
||||
const updateProductSchema = z.object({
|
||||
principal: z.number().positive("本金必须大于0"),
|
||||
depositDate: z.string().nullable().optional(),
|
||||
endDate: z.string().nullable().optional(),
|
||||
currentNetValue: z.number().nullable().optional(),
|
||||
annualRate: z.number().nullable().optional(),
|
||||
}).refine((data) => {
|
||||
// 如果有开始时间、结束时间、本金、当前净值,则不需要年化利率(会自动计算)
|
||||
// 否则至少需要当前净值或年化利率中的一个
|
||||
const hasCompleteInfo = data.depositDate && data.endDate && data.principal && data.currentNetValue
|
||||
const hasEitherValue = data.currentNetValue !== null || data.annualRate !== null
|
||||
|
||||
return hasCompleteInfo || hasEitherValue
|
||||
}, {
|
||||
message: "请提供足够的信息:要么提供当前净值或年化利率,要么提供完整的时间和净值信息",
|
||||
path: ["currentNetValue", "annualRate"]
|
||||
})
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
}
|
||||
|
||||
// 检查产品是否存在且属于当前用户
|
||||
const existingProduct = await db.financeProduct.findUnique({
|
||||
where: {
|
||||
id: params.id,
|
||||
},
|
||||
})
|
||||
|
||||
if (!existingProduct) {
|
||||
return NextResponse.json({ error: "Product not found" }, { status: 404 })
|
||||
}
|
||||
|
||||
if (existingProduct.userId !== session.user.id) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
console.log("Received update request body:", body)
|
||||
|
||||
// 预处理数据:将空字符串转换为null
|
||||
const processedBody = {
|
||||
...body,
|
||||
currentNetValue: body.currentNetValue === "" || body.currentNetValue === null ? null : Number(body.currentNetValue),
|
||||
annualRate: body.annualRate === "" || body.annualRate === null ? null : Number(body.annualRate),
|
||||
principal: Number(body.principal),
|
||||
depositDate: body.depositDate || null,
|
||||
endDate: body.endDate || null,
|
||||
}
|
||||
|
||||
console.log("Processed update request body:", processedBody)
|
||||
|
||||
const validatedData = updateProductSchema.parse(processedBody)
|
||||
console.log("Validated update data:", validatedData)
|
||||
|
||||
// 计算相关字段
|
||||
let calculatedValues = {
|
||||
profit: null as number | null,
|
||||
dailyProfit: null as number | null,
|
||||
monthlyProfit: null as number | null,
|
||||
calculatedAnnualRate: null as number | null,
|
||||
averageAnnualProfit: null as number | null,
|
||||
}
|
||||
|
||||
// 验证日期
|
||||
let depositDate: Date | null = null
|
||||
let endDate: Date | null = null
|
||||
let days = 0
|
||||
|
||||
if (validatedData.depositDate && validatedData.endDate) {
|
||||
depositDate = new Date(validatedData.depositDate)
|
||||
endDate = new Date(validatedData.endDate)
|
||||
|
||||
if (isNaN(depositDate.getTime()) || isNaN(endDate.getTime())) {
|
||||
return NextResponse.json(
|
||||
{ error: "无效的日期格式" },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
days = Math.max(1, Math.floor((endDate.getTime() - depositDate.getTime()) / (1000 * 60 * 60 * 24)))
|
||||
}
|
||||
|
||||
// 如果有当前净值,计算收益
|
||||
if (validatedData.currentNetValue !== null && validatedData.currentNetValue !== undefined) {
|
||||
calculatedValues.profit = validatedData.currentNetValue - validatedData.principal
|
||||
}
|
||||
|
||||
// 自动计算年化利率(如果没有提供年化利率但有完整信息)
|
||||
if (!validatedData.annualRate && validatedData.currentNetValue && depositDate && endDate && days > 0) {
|
||||
const totalReturn = validatedData.currentNetValue - validatedData.principal
|
||||
const dailyReturn = totalReturn / days
|
||||
const annualReturn = (dailyReturn * 365) / validatedData.principal
|
||||
validatedData.annualRate = annualReturn * 100 // 转换为百分比
|
||||
console.log("Auto-calculated annual rate:", validatedData.annualRate)
|
||||
}
|
||||
|
||||
// 如果有年化利率和日期,计算当前净值(当没有提供当前净值时)
|
||||
if (validatedData.annualRate && depositDate && endDate && days > 0 && !validatedData.currentNetValue) {
|
||||
const calculatedNetValue = validatedData.principal * (1 + ((validatedData.annualRate / 100) * days) / 365)
|
||||
validatedData.currentNetValue = calculatedNetValue
|
||||
calculatedValues.profit = calculatedNetValue - validatedData.principal
|
||||
console.log("Auto-calculated net value:", validatedData.currentNetValue)
|
||||
}
|
||||
|
||||
// 计算日收益和月收益
|
||||
if (calculatedValues.profit !== null && days > 0) {
|
||||
calculatedValues.dailyProfit = calculatedValues.profit / days
|
||||
calculatedValues.monthlyProfit = calculatedValues.dailyProfit * 30
|
||||
calculatedValues.calculatedAnnualRate = ((calculatedValues.dailyProfit * 365) / validatedData.principal) * 100
|
||||
calculatedValues.averageAnnualProfit = calculatedValues.dailyProfit * 365
|
||||
}
|
||||
|
||||
console.log("Calculated values for update:", calculatedValues)
|
||||
|
||||
const updatedProduct = await db.financeProduct.update({
|
||||
where: {
|
||||
id: params.id,
|
||||
},
|
||||
data: {
|
||||
principal: validatedData.principal,
|
||||
depositDate: depositDate,
|
||||
endDate: endDate,
|
||||
currentNetValue: validatedData.currentNetValue || null,
|
||||
annualRate: validatedData.annualRate || null,
|
||||
...calculatedValues,
|
||||
},
|
||||
})
|
||||
|
||||
console.log("Updated product:", updatedProduct)
|
||||
|
||||
return NextResponse.json(updatedProduct)
|
||||
} catch (error) {
|
||||
console.error("Error in PUT /api/products/[id]:", error)
|
||||
|
||||
if (error instanceof z.ZodError) {
|
||||
console.error("Zod validation errors:", error.errors)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "数据验证失败",
|
||||
details: error.errors.map(err => ({
|
||||
field: err.path.join('.'),
|
||||
message: err.message
|
||||
}))
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: "Internal server error" },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
|
@ -10,10 +10,14 @@ const createProductSchema = z.object({
|
||||
currentNetValue: z.number().nullable().optional(),
|
||||
annualRate: z.number().nullable().optional(),
|
||||
}).refine((data) => {
|
||||
// 至少有一个:当前净值或年化利率
|
||||
return data.currentNetValue !== null || data.annualRate !== null
|
||||
// 如果有开始时间、结束时间、本金、当前净值,则不需要年化利率(会自动计算)
|
||||
// 否则至少需要当前净值或年化利率中的一个
|
||||
const hasCompleteInfo = data.depositDate && data.endDate && data.principal && data.currentNetValue
|
||||
const hasEitherValue = data.currentNetValue !== null || data.annualRate !== null
|
||||
|
||||
return hasCompleteInfo || hasEitherValue
|
||||
}, {
|
||||
message: "当前净值和年化利率不能同时为空",
|
||||
message: "请提供足够的信息:要么提供当前净值或年化利率,要么提供完整的时间和净值信息",
|
||||
path: ["currentNetValue", "annualRate"]
|
||||
})
|
||||
|
||||
@ -76,19 +80,18 @@ export async function POST(request: NextRequest) {
|
||||
dailyProfit: null as number | null,
|
||||
monthlyProfit: null as number | null,
|
||||
calculatedAnnualRate: null as number | null,
|
||||
averageAnnualProfit: null as number | null,
|
||||
}
|
||||
|
||||
// 如果有当前净值,计算收益
|
||||
if (validatedData.currentNetValue !== null && validatedData.currentNetValue !== undefined) {
|
||||
calculatedValues.profit = validatedData.currentNetValue - validatedData.principal
|
||||
}
|
||||
// 验证日期
|
||||
let depositDate: Date | null = null
|
||||
let endDate: Date | null = null
|
||||
let days = 0
|
||||
|
||||
// 如果有年化利率和日期,计算当前净值
|
||||
if (validatedData.annualRate && validatedData.depositDate && validatedData.endDate) {
|
||||
const depositDate = new Date(validatedData.depositDate)
|
||||
const endDate = new Date(validatedData.endDate)
|
||||
if (validatedData.depositDate && validatedData.endDate) {
|
||||
depositDate = new Date(validatedData.depositDate)
|
||||
endDate = new Date(validatedData.endDate)
|
||||
|
||||
// 验证日期是否有效
|
||||
if (isNaN(depositDate.getTime()) || isNaN(endDate.getTime())) {
|
||||
return NextResponse.json(
|
||||
{ error: "无效的日期格式" },
|
||||
@ -96,24 +99,37 @@ export async function POST(request: NextRequest) {
|
||||
)
|
||||
}
|
||||
|
||||
const days = Math.max(1, Math.floor((endDate.getTime() - depositDate.getTime()) / (1000 * 60 * 60 * 24)))
|
||||
|
||||
if (!validatedData.currentNetValue) {
|
||||
const calculatedNetValue = validatedData.principal * (1 + ((validatedData.annualRate / 100) * days) / 365)
|
||||
validatedData.currentNetValue = calculatedNetValue
|
||||
calculatedValues.profit = calculatedNetValue - validatedData.principal
|
||||
}
|
||||
days = Math.max(1, Math.floor((endDate.getTime() - depositDate.getTime()) / (1000 * 60 * 60 * 24)))
|
||||
}
|
||||
|
||||
// 如果有当前净值,计算收益
|
||||
if (validatedData.currentNetValue !== null && validatedData.currentNetValue !== undefined) {
|
||||
calculatedValues.profit = validatedData.currentNetValue - validatedData.principal
|
||||
}
|
||||
|
||||
// 自动计算年化利率(如果没有提供年化利率但有完整信息)
|
||||
if (!validatedData.annualRate && validatedData.currentNetValue && depositDate && endDate && days > 0) {
|
||||
const totalReturn = validatedData.currentNetValue - validatedData.principal
|
||||
const dailyReturn = totalReturn / days
|
||||
const annualReturn = (dailyReturn * 365) / validatedData.principal
|
||||
validatedData.annualRate = annualReturn * 100 // 转换为百分比
|
||||
console.log("Auto-calculated annual rate:", validatedData.annualRate)
|
||||
}
|
||||
|
||||
// 如果有年化利率和日期,计算当前净值(当没有提供当前净值时)
|
||||
if (validatedData.annualRate && depositDate && endDate && days > 0 && !validatedData.currentNetValue) {
|
||||
const calculatedNetValue = validatedData.principal * (1 + ((validatedData.annualRate / 100) * days) / 365)
|
||||
validatedData.currentNetValue = calculatedNetValue
|
||||
calculatedValues.profit = calculatedNetValue - validatedData.principal
|
||||
console.log("Auto-calculated net value:", validatedData.currentNetValue)
|
||||
}
|
||||
|
||||
// 计算日收益和月收益
|
||||
if (calculatedValues.profit !== null && validatedData.depositDate && validatedData.endDate) {
|
||||
const depositDate = new Date(validatedData.depositDate)
|
||||
const endDate = new Date(validatedData.endDate)
|
||||
const days = Math.max(1, Math.floor((endDate.getTime() - depositDate.getTime()) / (1000 * 60 * 60 * 24)))
|
||||
|
||||
if (calculatedValues.profit !== null && days > 0) {
|
||||
calculatedValues.dailyProfit = calculatedValues.profit / days
|
||||
calculatedValues.monthlyProfit = calculatedValues.dailyProfit * 30
|
||||
calculatedValues.calculatedAnnualRate = ((calculatedValues.dailyProfit * 365) / validatedData.principal) * 100
|
||||
calculatedValues.averageAnnualProfit = calculatedValues.dailyProfit * 365
|
||||
}
|
||||
|
||||
console.log("Calculated values:", calculatedValues)
|
||||
@ -122,8 +138,8 @@ export async function POST(request: NextRequest) {
|
||||
data: {
|
||||
userId: session.user.id,
|
||||
principal: validatedData.principal,
|
||||
depositDate: validatedData.depositDate ? new Date(validatedData.depositDate) : null,
|
||||
endDate: validatedData.endDate ? new Date(validatedData.endDate) : null,
|
||||
depositDate: depositDate,
|
||||
endDate: endDate,
|
||||
currentNetValue: validatedData.currentNetValue || null,
|
||||
annualRate: validatedData.annualRate || null,
|
||||
...calculatedValues,
|
||||
|
@ -7,7 +7,7 @@ import { Input } from "@/components/ui/input"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { CalendarIcon, PlusCircle, Trash2, LogIn } from "lucide-react"
|
||||
import { CalendarIcon, PlusCircle, Trash2, LogIn, Edit, Check, X } from "lucide-react"
|
||||
import { Calendar } from "@/components/ui/calendar"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||
import { cn } from "@/lib/utils"
|
||||
@ -27,6 +27,7 @@ interface FinanceProduct {
|
||||
dailyProfit: number | null
|
||||
monthlyProfit: number | null
|
||||
calculatedAnnualRate: number | null
|
||||
averageAnnualProfit: number | null
|
||||
createdAt?: Date
|
||||
updatedAt?: Date
|
||||
}
|
||||
@ -46,6 +47,11 @@ export default function FinanceCalculator() {
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
// 编辑相关状态
|
||||
const [editingProductId, setEditingProductId] = useState<string | null>(null)
|
||||
const [editingProduct, setEditingProduct] = useState<NewProduct>(createEmptyProduct())
|
||||
const [isUpdating, setIsUpdating] = useState(false)
|
||||
|
||||
function createEmptyProduct(): NewProduct {
|
||||
const lastProduct = products.length > 0 ? products[products.length - 1] : null
|
||||
@ -106,9 +112,12 @@ export default function FinanceCalculator() {
|
||||
return
|
||||
}
|
||||
|
||||
// 验证输入
|
||||
if (newProduct.currentNetValue === null && newProduct.annualRate === null) {
|
||||
setError("当前净值和年化利率不能同时为空")
|
||||
// 更新验证逻辑:如果有完整的时间和净值信息,则不需要年化利率
|
||||
const hasCompleteInfo = newProduct.depositDate && newProduct.endDate && newProduct.principal && newProduct.currentNetValue
|
||||
const hasEitherValue = newProduct.currentNetValue !== null || newProduct.annualRate !== null
|
||||
|
||||
if (!hasCompleteInfo && !hasEitherValue) {
|
||||
setError("请提供足够的信息:要么提供当前净值或年化利率,要么提供完整的时间和净值信息以自动计算年化利率")
|
||||
return
|
||||
}
|
||||
|
||||
@ -172,6 +181,101 @@ export default function FinanceCalculator() {
|
||||
}
|
||||
}
|
||||
|
||||
// 开始编辑产品
|
||||
function handleEditProduct(product: FinanceProduct) {
|
||||
setEditingProductId(product.id)
|
||||
setEditingProduct({
|
||||
principal: product.principal,
|
||||
depositDate: product.depositDate,
|
||||
endDate: product.endDate,
|
||||
currentNetValue: product.currentNetValue,
|
||||
annualRate: product.annualRate,
|
||||
})
|
||||
}
|
||||
|
||||
// 取消编辑
|
||||
function handleCancelEdit() {
|
||||
setEditingProductId(null)
|
||||
setEditingProduct(createEmptyProduct())
|
||||
}
|
||||
|
||||
// 保存编辑
|
||||
async function handleSaveEdit() {
|
||||
if (!session?.user?.id || !editingProductId) {
|
||||
toast.error('请先登录')
|
||||
return
|
||||
}
|
||||
|
||||
if (editingProduct.principal <= 0) {
|
||||
toast.error("本金必须大于0")
|
||||
return
|
||||
}
|
||||
|
||||
setIsUpdating(true)
|
||||
|
||||
try {
|
||||
const requestData = {
|
||||
principal: editingProduct.principal,
|
||||
depositDate: editingProduct.depositDate?.toISOString() || null,
|
||||
endDate: editingProduct.endDate?.toISOString() || null,
|
||||
currentNetValue: editingProduct.currentNetValue,
|
||||
annualRate: editingProduct.annualRate,
|
||||
}
|
||||
|
||||
console.log('Updating product with data:', requestData)
|
||||
|
||||
const response = await fetch(`/api/products/${editingProductId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestData),
|
||||
})
|
||||
|
||||
const responseData = await response.json()
|
||||
console.log('Update API response:', responseData)
|
||||
|
||||
if (response.ok) {
|
||||
// 转换日期字符串为Date对象
|
||||
const updatedProductWithDates = {
|
||||
...responseData,
|
||||
depositDate: responseData.depositDate ? new Date(responseData.depositDate) : null,
|
||||
endDate: responseData.endDate ? new Date(responseData.endDate) : null,
|
||||
createdAt: responseData.createdAt ? new Date(responseData.createdAt) : undefined,
|
||||
updatedAt: responseData.updatedAt ? new Date(responseData.updatedAt) : undefined,
|
||||
}
|
||||
|
||||
// 更新产品列表
|
||||
setProducts(products.map(p =>
|
||||
p.id === editingProductId ? updatedProductWithDates : p
|
||||
))
|
||||
|
||||
// 清除编辑状态
|
||||
setEditingProductId(null)
|
||||
setEditingProduct(createEmptyProduct())
|
||||
|
||||
toast.success('产品更新成功')
|
||||
} else {
|
||||
console.error('Update API error response:', responseData)
|
||||
|
||||
if (responseData.details && Array.isArray(responseData.details)) {
|
||||
// 显示详细的验证错误
|
||||
const errorMessages = responseData.details.map((detail: any) =>
|
||||
`${detail.field}: ${detail.message}`
|
||||
).join(', ')
|
||||
toast.error(`数据验证失败: ${errorMessages}`)
|
||||
} else {
|
||||
toast.error(responseData.error || '更新产品失败')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Update request error:', error)
|
||||
toast.error('网络请求失败,请重试')
|
||||
} finally {
|
||||
setIsUpdating(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRemoveProduct(id: string) {
|
||||
if (!session?.user?.id) {
|
||||
toast.error('请先登录')
|
||||
@ -201,6 +305,13 @@ export default function FinanceCalculator() {
|
||||
})
|
||||
}
|
||||
|
||||
function handleUseCurrentTimeForEdit() {
|
||||
setEditingProduct({
|
||||
...editingProduct,
|
||||
endDate: new Date(),
|
||||
})
|
||||
}
|
||||
|
||||
// 当产品列表变化时,更新新产品的默认本金
|
||||
useEffect(() => {
|
||||
if (products.length > 0) {
|
||||
@ -348,7 +459,7 @@ export default function FinanceCalculator() {
|
||||
annualRate: e.target.value ? Number.parseFloat(e.target.value) : null,
|
||||
})
|
||||
}
|
||||
placeholder="输入年化利率"
|
||||
placeholder="输入年化利率(可自动计算)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -385,8 +496,8 @@ export default function FinanceCalculator() {
|
||||
<TableHead>收益</TableHead>
|
||||
<TableHead>平均日收益</TableHead>
|
||||
<TableHead>平均月收益</TableHead>
|
||||
<TableHead>实际年化 (%)</TableHead>
|
||||
<TableHead className="w-[80px]">操作</TableHead>
|
||||
<TableHead>平均年收益</TableHead>
|
||||
<TableHead className="w-[120px]">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@ -399,46 +510,165 @@ export default function FinanceCalculator() {
|
||||
) : (
|
||||
products.map((product) => (
|
||||
<TableRow key={product.id}>
|
||||
<TableCell>{product.principal.toFixed(2)}</TableCell>
|
||||
<TableCell>
|
||||
{product.depositDate ? format(product.depositDate, "yyyy-MM-dd", { locale: zhCN }) : "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{product.endDate ? format(product.endDate, "yyyy-MM-dd", { locale: zhCN }) : "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{product.currentNetValue !== null ? product.currentNetValue.toFixed(2) : "-"}
|
||||
</TableCell>
|
||||
<TableCell>{product.annualRate !== null ? product.annualRate.toFixed(2) : "-"}</TableCell>
|
||||
<TableCell
|
||||
className={cn(
|
||||
product.profit && product.profit > 0
|
||||
? "text-green-600"
|
||||
: product.profit && product.profit < 0
|
||||
? "text-red-600"
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
{product.profit !== null ? product.profit.toFixed(2) : "-"}
|
||||
</TableCell>
|
||||
<TableCell>{product.dailyProfit !== null ? product.dailyProfit.toFixed(2) : "-"}</TableCell>
|
||||
<TableCell>{product.monthlyProfit !== null ? product.monthlyProfit.toFixed(2) : "-"}</TableCell>
|
||||
<TableCell
|
||||
className={cn(
|
||||
product.calculatedAnnualRate && product.calculatedAnnualRate > 0
|
||||
? "text-green-600"
|
||||
: product.calculatedAnnualRate && product.calculatedAnnualRate < 0
|
||||
? "text-red-600"
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
{product.calculatedAnnualRate !== null ? product.calculatedAnnualRate.toFixed(2) : "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Button variant="ghost" size="icon" onClick={() => handleRemoveProduct(product.id)}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
{editingProductId === product.id ? (
|
||||
// 编辑模式的行
|
||||
<>
|
||||
<TableCell>
|
||||
<Input
|
||||
type="number"
|
||||
value={editingProduct.principal || ""}
|
||||
onChange={(e) => setEditingProduct({
|
||||
...editingProduct,
|
||||
principal: Number.parseFloat(e.target.value) || 0
|
||||
})}
|
||||
className="w-20"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<CalendarIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={editingProduct.depositDate || undefined}
|
||||
onSelect={(date) => setEditingProduct({
|
||||
...editingProduct,
|
||||
depositDate: date || null
|
||||
})}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex gap-1">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<CalendarIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={editingProduct.endDate || undefined}
|
||||
onSelect={(date) => setEditingProduct({
|
||||
...editingProduct,
|
||||
endDate: date || null
|
||||
})}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleUseCurrentTimeForEdit}
|
||||
>
|
||||
现在
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Input
|
||||
type="number"
|
||||
value={editingProduct.currentNetValue !== null ? editingProduct.currentNetValue : ""}
|
||||
onChange={(e) => setEditingProduct({
|
||||
...editingProduct,
|
||||
currentNetValue: e.target.value ? Number.parseFloat(e.target.value) : null
|
||||
})}
|
||||
className="w-24"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Input
|
||||
type="number"
|
||||
value={editingProduct.annualRate !== null ? editingProduct.annualRate : ""}
|
||||
onChange={(e) => setEditingProduct({
|
||||
...editingProduct,
|
||||
annualRate: e.target.value ? Number.parseFloat(e.target.value) : null
|
||||
})}
|
||||
className="w-20"
|
||||
placeholder="自动计算"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>-</TableCell>
|
||||
<TableCell>-</TableCell>
|
||||
<TableCell>-</TableCell>
|
||||
<TableCell>-</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleSaveEdit}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleCancelEdit}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</>
|
||||
) : (
|
||||
// 普通显示模式的行
|
||||
<>
|
||||
<TableCell>{product.principal.toFixed(2)}</TableCell>
|
||||
<TableCell>
|
||||
{product.depositDate ? format(product.depositDate, "yyyy-MM-dd", { locale: zhCN }) : "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{product.endDate ? format(product.endDate, "yyyy-MM-dd", { locale: zhCN }) : "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{product.currentNetValue !== null ? product.currentNetValue.toFixed(2) : "-"}
|
||||
</TableCell>
|
||||
<TableCell>{product.annualRate !== null ? product.annualRate.toFixed(2) : "-"}</TableCell>
|
||||
<TableCell
|
||||
className={cn(
|
||||
product.profit && product.profit > 0
|
||||
? "text-green-600"
|
||||
: product.profit && product.profit < 0
|
||||
? "text-red-600"
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
{product.profit !== null ? product.profit.toFixed(2) : "-"}
|
||||
</TableCell>
|
||||
<TableCell>{product.dailyProfit !== null ? product.dailyProfit.toFixed(2) : "-"}</TableCell>
|
||||
<TableCell>{product.monthlyProfit !== null ? product.monthlyProfit.toFixed(2) : "-"}</TableCell>
|
||||
<TableCell>{product.averageAnnualProfit !== null ? product.averageAnnualProfit.toFixed(2) : "-"}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleEditProduct(product)}
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleRemoveProduct(product.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</>
|
||||
)}
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
|
@ -70,6 +70,7 @@ model FinanceProduct {
|
||||
dailyProfit Float?
|
||||
monthlyProfit Float?
|
||||
calculatedAnnualRate Float?
|
||||
averageAnnualProfit Float?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user